summaryrefslogtreecommitdiff
path: root/cesar/ecos/packages/io/fileio/current/src/select.cxx
blob: 03782e149d6d79ba6aec717926197efc737df546 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
//==========================================================================
//
//      select.cxx
//
//      Fileio select() support
//
//==========================================================================
//####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
// Copyright (C) 2002 Nick Garnett
//
// eCos is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 or (at your option) any later version.
//
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with eCos; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
//
// As a special exception, if other files instantiate templates or use macros
// or inline functions from this file, or you compile this file and link it
// with other works to produce a work based on this file, this file does not
// by itself cause the resulting work to be covered by the GNU General Public
// License. However the source code for this file must still be made available
// in accordance with section (3) of the GNU General Public License.
//
// This exception does not invalidate any other reasons why a work based on
// this file might be covered by the GNU General Public License.
//
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
// at http://sources.redhat.com/ecos/ecos-license/
// -------------------------------------------
//####ECOSGPLCOPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):           nickg
// Contributors:        nickg
// Date:                2000-05-25
// Purpose:             Fileio select() support
// Description:         Support for select().
//                      
//              
//              
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/hal.h>
#include <pkgconf/kernel.h>
#include <pkgconf/io_fileio.h>

#include <cyg/kernel/ktypes.h>         // base kernel types
#include <cyg/infra/cyg_trac.h>        // tracing macros
#include <cyg/infra/cyg_ass.h>         // assertion macros

#include <stdarg.h>                    // for fcntl()

#include "fio.h"                       // Private header

#include <sys/select.h>                // select header

#include <cyg/kernel/sched.hxx>        // scheduler definitions
#include <cyg/kernel/thread.hxx>       // thread definitions
#include <cyg/kernel/flag.hxx>         // flag definitions
#include <cyg/kernel/clock.hxx>        // clock definitions

#include <cyg/kernel/sched.inl>
#include <cyg/kernel/thread.inl>
#include <cyg/kernel/clock.inl>

//==========================================================================
// File object locking

#define LOCK_FILE( fp ) cyg_file_lock( fp )

#define UNLOCK_FILE( fp ) cyg_file_unlock( fp )

// Get a flag based on the thread's unique ID. Note: In a system with a large
// number of threads, the same flag may be used by more than one thread.
#define SELECT_WAIT_FLAG_GET() (1 << (Cyg_Thread::self()->get_unique_id() \
                                & (sizeof (Cyg_FlagValue) * NBBY - 1)))

//==========================================================================
// Local variables

static volatile cyg_uint32 selwake_count = 0;

// A flag is used to block a thread until data from the device is available. This
// prevents all threads from waking up at the same time and polling for changes.
// Each thread is allocated a flag bit via the SELECT_WAIT_FLAG_GET() macro when 
// the thread registers for selection via cyg_selrecord (). The flag is stored in
// the driver's select info block. Only those threads specified via the flags in 
// the select info are woken up by cyg_selwakeup (). 
// If there are more than 32 threads in the system, then there is a chance that
// cyg_selwakeup () may wake up more than one thread. Each thread then polls for
// changes.
static Cyg_Flag select_flag CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO_FS);

//==========================================================================
// Timeval to ticks conversion support

// Converters from sec and us to ticks
static struct Cyg_Clock::converter us_converter, sec_converter;

static cyg_bool converters_initialized = false;

externC cyg_tick_count cyg_timeval_to_ticks( const struct timeval *tv )
{
    if( !converters_initialized )
    {
        // Create the converters we need.
        Cyg_Clock::real_time_clock->get_other_to_clock_converter( 1000, &us_converter );
        Cyg_Clock::real_time_clock->get_other_to_clock_converter( 1000000000, &sec_converter );

        converters_initialized = true;
    }
    
    // Short circuit zero timeval
    if( tv->tv_sec == 0 && tv->tv_usec == 0 )
    {
        return 0;
    }
        
    // Convert the seconds field to ticks.
    cyg_tick_count ticks = Cyg_Clock::convert( tv->tv_sec, &sec_converter );

    // Convert the nanoseconds. This will round down to nearest whole tick.
    ticks += Cyg_Clock::convert( (cyg_tick_count)tv->tv_usec, &us_converter );

    return ticks;
}

//==========================================================================
// Select API function

static int
cyg_pselect(int nfd, fd_set *in, fd_set *out, fd_set *ex,
           struct timeval *tv, const sigset_t *mask)
{
    FILEIO_ENTRY();

    int error = ENOERR;
    int fd, mode, num;
    cyg_file *fp;
    fd_set in_res, out_res, ex_res;  // Result sets
    fd_set *selection[3], *result[3];
    cyg_tick_count ticks;
    int mode_type[] = {CYG_FREAD, CYG_FWRITE, 0};
    cyg_uint32 wake_count;
    sigset_t oldmask;

    Cyg_FlagValue myFlag = SELECT_WAIT_FLAG_GET ();
    int    maxFdIndex = __howmany(nfd, __NFDBITS); // size of fd sets

    // Make sure the nfd < FD_SETSIZE, a value greater than FD_SETSIZE 
    // would break the results sets
    if(nfd > FD_SETSIZE)
    {
        FILEIO_RETURN(EINVAL);
    }

    FD_ZERO(&in_res);
    FD_ZERO(&out_res);
    FD_ZERO(&ex_res);

    // Set up sets
    selection[0] = in;   result[0] = &in_res;
    selection[1] = out;  result[1] = &out_res;
    selection[2] = ex;   result[2] = &ex_res;

    // Compute end time
    if (tv)
        ticks = cyg_timeval_to_ticks( tv );
    else ticks = 0;

    // Scan sets for possible I/O until something found, timeout or error.
    while (!error)
    {
        wake_count = selwake_count;

        num = 0;  // Total file descriptors "ready"
        for (mode = 0;  !error && mode < 3;  mode++)
        {
            if (selection[mode]) 
            {
                fd_mask *fds_bits = selection[mode]->fds_bits;
                int      index, fdbase;
                for(index = 0, fdbase = 0; !error && index < maxFdIndex; index++, fdbase += __NFDBITS)
                {
                    fd_mask mask = fds_bits[index];
                    for(fd = fdbase; mask != 0; fd++, mask >>= 1)
                    {
                        if(mask & 1)
                        {
                            fp = cyg_fp_get( fd );
                            if( fp == NULL )
                            {
                                error = EBADF;
                                break;
                            }

                            if ((*fp->f_ops->fo_select)(fp, mode_type[mode], 0))
                            {
                                FD_SET(fd, result[mode]);
                                num++;
                            }
                            cyg_fp_free( fp );
                        }
                    }
                }
            }
        }

        if (error)
            break;
        
        if (num)
        {
            // Found something, update user's sets
            if (in)  FD_COPY( &in_res, in );
            if (out) FD_COPY( &out_res, out );
            if (ex)  FD_COPY( &ex_res, ex );
            CYG_FILEIO_DELIVER_SIGNALS( mask );
            FILEIO_RETURN_VALUE(num);
        }

        Cyg_Scheduler::lock();

        // Switch to the supplied signal mask. This will permit delivery
        // of any signals that might terminate this select operation.
        
        CYG_FILEIO_SIGMASK_SET( mask, &oldmask );
    
        do
        {

            // We need to see if any signals have been posted while we
            // were testing all those files. The handlers will not
            // have run because we have ASRs inhibited but the signal
            // will have been set pending.

            if( CYG_FILEIO_SIGPENDING() )
            {
                // There are pending signals so we need to terminate
                // the select operation and return EINTR. Handlers for
                // the pending signals will be called just before we
                // return.

                error = EINTR;
                break;
            }
            
            if( wake_count == selwake_count )
            {
                // Nothing found, see if we want to wait
                if (tv)
                {
                    // Special case of "poll"
                    if (ticks == 0)
                    {
                        error = EAGAIN;
                        break;
                    }

                    ticks += Cyg_Clock::real_time_clock->current_value();
                
                    if( !select_flag.wait (myFlag, Cyg_Flag::OR, ticks) )
                    {
                        // A non-standard wakeup, if the current time is equal to
                        // or past the timeout, return zero. Otherwise return
                        // EINTR, since we have been released.

                        if( Cyg_Clock::real_time_clock->current_value() >= ticks )
                        {
                            error = EAGAIN;
                            break;
                        }
                        else error = EINTR;
                    }

                    ticks -= Cyg_Clock::real_time_clock->current_value();
                }
                else
                {
                    // Wait forever (until something happens)
                    if( !select_flag.wait (myFlag, Cyg_Flag::OR) )
                        error = EINTR;
                }
            }

        } while(0);

        CYG_FILEIO_SIGMASK_SET( &oldmask, NULL );
        
        Cyg_Scheduler::unlock();
        
    } // while(!error)

    // If the error code is EAGAIN, this means that a timeout has
    // happened. We return zero in that case, rather than a proper
    // error code.
    // If the error code is EINTR, then a signal may be pending
    // delivery. Call back into the POSIX package to handle it.
    
    if( error == EAGAIN )
        FILEIO_RETURN_VALUE(0);
    else if( error == EINTR )
        CYG_FILEIO_DELIVER_SIGNALS( mask );

    FILEIO_RETURN(error);
}

// -------------------------------------------------------------------------
// Select API function

__externC int
select(int nfd, fd_set *in, fd_set *out, fd_set *ex, struct timeval *tv)
{
	return cyg_pselect(nfd, in, out, ex, tv, NULL);
}

// -------------------------------------------------------------------------
// Pselect API function
//
// This is derived from the POSIX-200X specification.

__externC int
pselect(int nfd, fd_set *in, fd_set *out, fd_set *ex,
	const struct timespec *ts, const sigset_t *sigmask)
{
	struct timeval tv;

#ifndef CYGPKG_POSIX_SIGNALS
        CYG_ASSERT( sigmask == NULL,
                    "pselect called with non-null sigmask without POSIX signal support"
                    );
#endif

	if (ts != NULL)
        {
            tv.tv_sec = ts->tv_sec;
            tv.tv_usec = ts->tv_nsec/1000;
        }

	return cyg_pselect(nfd, in, out, ex, ts ? &tv : NULL, sigmask);
}

//==========================================================================
// Select support functions.

// -------------------------------------------------------------------------
// cyg_selinit() is used to initialize a selinfo structure

void cyg_selinit( struct CYG_SELINFO_TAG *sip )
{
    sip->si_info = 0;
    sip->si_waitFlag = 0;
}

// -------------------------------------------------------------------------
// cyg_selrecord() is called when a client device needs to register
// the current thread for selection. Save the flag that identifies the thread. 
void cyg_selrecord( CYG_ADDRWORD info, struct CYG_SELINFO_TAG *sip )
{
    sip->si_info = info;
    Cyg_Scheduler::lock();
    sip->si_waitFlag |= SELECT_WAIT_FLAG_GET ();
    Cyg_Scheduler::unlock();    
}

// -------------------------------------------------------------------------
// cyg_selwakeup() is called when the client device matches the select
// criterion, and needs to wake up a thread.
void cyg_selwakeup( struct CYG_SELINFO_TAG *sip )
{
    // We don't actually use the si_info field of selinfo at present.
    Cyg_Scheduler::lock();
 
    if( sip->si_waitFlag != 0 )
    {
        // If the flag is still present, this selection has not fired before. 
        // Only wake up the threads waiting on the flags specified in si_waitFlag. 
        // There is no need to wake threads that are not waiting for this data.
        select_flag.setbits (sip->si_waitFlag); 
        sip->si_waitFlag = 0;     // clear all flags
        select_flag.maskbits (sip->si_waitFlag); 
        selwake_count++;
    }
    Cyg_Scheduler::unlock();    
}

// -------------------------------------------------------------------------
// EOF select.cxx