[4] | 1 | /* This file contains a driver for:
|
---|
| 2 | * /dev/klog - system log device
|
---|
| 3 | *
|
---|
| 4 | * Changes:
|
---|
| 5 | * 21 July 2005 - Support for diagnostic messages (Jorrit N. Herder)
|
---|
| 6 | * 7 July 2005 - Created (Ben Gras)
|
---|
| 7 | */
|
---|
| 8 |
|
---|
| 9 | #include "log.h"
|
---|
| 10 | #include <sys/time.h>
|
---|
| 11 | #include <sys/select.h>
|
---|
| 12 | #include "../../kernel/const.h"
|
---|
| 13 | #include "../../kernel/type.h"
|
---|
| 14 |
|
---|
| 15 | #define LOG_DEBUG 0 /* enable/ disable debugging */
|
---|
| 16 |
|
---|
| 17 | #define NR_DEVS 1 /* number of minor devices */
|
---|
| 18 | #define MINOR_KLOG 0 /* /dev/klog */
|
---|
| 19 |
|
---|
| 20 | #define LOGINC(n, i) do { (n) = (((n) + (i)) % LOG_SIZE); } while(0)
|
---|
| 21 |
|
---|
| 22 | PUBLIC struct logdevice logdevices[NR_DEVS];
|
---|
| 23 | PRIVATE struct device log_geom[NR_DEVS]; /* base and size of devices */
|
---|
| 24 | PRIVATE int log_device = -1; /* current device */
|
---|
| 25 |
|
---|
| 26 | FORWARD _PROTOTYPE( char *log_name, (void) );
|
---|
| 27 | FORWARD _PROTOTYPE( struct device *log_prepare, (int device) );
|
---|
| 28 | FORWARD _PROTOTYPE( int log_transfer, (int proc_nr, int opcode, off_t position,
|
---|
| 29 | iovec_t *iov, unsigned nr_req) );
|
---|
| 30 | FORWARD _PROTOTYPE( int log_do_open, (struct driver *dp, message *m_ptr) );
|
---|
| 31 | FORWARD _PROTOTYPE( int log_cancel, (struct driver *dp, message *m_ptr) );
|
---|
| 32 | FORWARD _PROTOTYPE( int log_select, (struct driver *dp, message *m_ptr) );
|
---|
| 33 | FORWARD _PROTOTYPE( void log_signal, (struct driver *dp, message *m_ptr) );
|
---|
| 34 | FORWARD _PROTOTYPE( int log_other, (struct driver *dp, message *m_ptr) );
|
---|
| 35 | FORWARD _PROTOTYPE( void log_geometry, (struct partition *entry) );
|
---|
| 36 | FORWARD _PROTOTYPE( int subread, (struct logdevice *log, int count, int proc_nr, vir_bytes user_vir) );
|
---|
| 37 |
|
---|
| 38 | /* Entry points to this driver. */
|
---|
| 39 | PRIVATE struct driver log_dtab = {
|
---|
| 40 | log_name, /* current device's name */
|
---|
| 41 | log_do_open, /* open or mount */
|
---|
| 42 | do_nop, /* nothing on a close */
|
---|
| 43 | do_nop, /* ioctl nop */
|
---|
| 44 | log_prepare, /* prepare for I/O on a given minor device */
|
---|
| 45 | log_transfer, /* do the I/O */
|
---|
| 46 | nop_cleanup, /* no need to clean up */
|
---|
| 47 | log_geometry, /* geometry */
|
---|
| 48 | log_signal, /* handle system signal */
|
---|
| 49 | nop_alarm, /* no alarm */
|
---|
| 50 | log_cancel, /* CANCEL request */
|
---|
| 51 | log_select, /* DEV_SELECT request */
|
---|
| 52 | log_other, /* Unrecognized messages */
|
---|
| 53 | NULL /* HW int */
|
---|
| 54 | };
|
---|
| 55 |
|
---|
| 56 | extern int device_caller;
|
---|
| 57 |
|
---|
| 58 | /*===========================================================================*
|
---|
| 59 | * main *
|
---|
| 60 | *===========================================================================*/
|
---|
| 61 | PUBLIC int main(void)
|
---|
| 62 | {
|
---|
| 63 | int i;
|
---|
| 64 | for(i = 0; i < NR_DEVS; i++) {
|
---|
| 65 | log_geom[i].dv_size = cvul64(LOG_SIZE);
|
---|
| 66 | log_geom[i].dv_base = cvul64((long)logdevices[i].log_buffer);
|
---|
| 67 | logdevices[i].log_size = logdevices[i].log_read =
|
---|
| 68 | logdevices[i].log_write =
|
---|
| 69 | logdevices[i].log_select_alerted =
|
---|
| 70 | logdevices[i].log_selected =
|
---|
| 71 | logdevices[i].log_select_ready_ops = 0;
|
---|
| 72 | logdevices[i].log_proc_nr = 0;
|
---|
| 73 | logdevices[i].log_revive_alerted = 0;
|
---|
| 74 | }
|
---|
| 75 | driver_task(&log_dtab);
|
---|
| 76 | return(OK);
|
---|
| 77 | }
|
---|
| 78 |
|
---|
| 79 | /*===========================================================================*
|
---|
| 80 | * log_name *
|
---|
| 81 | *===========================================================================*/
|
---|
| 82 | PRIVATE char *log_name()
|
---|
| 83 | {
|
---|
| 84 | /* Return a name for the current device. */
|
---|
| 85 | static char name[] = "log";
|
---|
| 86 | return name;
|
---|
| 87 | }
|
---|
| 88 |
|
---|
| 89 | /*===========================================================================*
|
---|
| 90 | * log_prepare *
|
---|
| 91 | *===========================================================================*/
|
---|
| 92 | PRIVATE struct device *log_prepare(device)
|
---|
| 93 | int device;
|
---|
| 94 | {
|
---|
| 95 | /* Prepare for I/O on a device: check if the minor device number is ok. */
|
---|
| 96 |
|
---|
| 97 | if (device < 0 || device >= NR_DEVS) return(NIL_DEV);
|
---|
| 98 | log_device = device;
|
---|
| 99 |
|
---|
| 100 | return(&log_geom[device]);
|
---|
| 101 | }
|
---|
| 102 |
|
---|
| 103 | /*===========================================================================*
|
---|
| 104 | * subwrite *
|
---|
| 105 | *===========================================================================*/
|
---|
| 106 | PRIVATE int
|
---|
| 107 | subwrite(struct logdevice *log, int count, int proc_nr, vir_bytes user_vir)
|
---|
| 108 | {
|
---|
| 109 | char *buf;
|
---|
| 110 | int r;
|
---|
| 111 | if (log->log_write + count > LOG_SIZE)
|
---|
| 112 | count = LOG_SIZE - log->log_write;
|
---|
| 113 | buf = log->log_buffer + log->log_write;
|
---|
| 114 |
|
---|
| 115 | if(proc_nr == SELF) {
|
---|
| 116 | memcpy(buf, (char *) user_vir, count);
|
---|
| 117 | }
|
---|
| 118 | else {
|
---|
| 119 | if((r=sys_vircopy(proc_nr,D,user_vir, SELF,D,(int)buf, count)) != OK)
|
---|
| 120 | return r;
|
---|
| 121 | }
|
---|
| 122 |
|
---|
| 123 | LOGINC(log->log_write, count);
|
---|
| 124 | log->log_size += count;
|
---|
| 125 |
|
---|
| 126 | if(log->log_size > LOG_SIZE) {
|
---|
| 127 | int overflow;
|
---|
| 128 | overflow = log->log_size - LOG_SIZE;
|
---|
| 129 | log->log_size -= overflow;
|
---|
| 130 | LOGINC(log->log_read, overflow);
|
---|
| 131 | }
|
---|
| 132 |
|
---|
| 133 | if(log->log_size > 0 && log->log_proc_nr && !log->log_revive_alerted) {
|
---|
| 134 | /* Someone who was suspended on read can now
|
---|
| 135 | * be revived.
|
---|
| 136 | */
|
---|
| 137 | log->log_status = subread(log, log->log_iosize,
|
---|
| 138 | log->log_proc_nr, log->log_user_vir);
|
---|
| 139 | notify(log->log_source);
|
---|
| 140 | log->log_revive_alerted = 1;
|
---|
| 141 | }
|
---|
| 142 |
|
---|
| 143 | if(log->log_size > 0)
|
---|
| 144 | log->log_select_ready_ops |= SEL_RD;
|
---|
| 145 |
|
---|
| 146 | if(log->log_size > 0 && log->log_selected &&
|
---|
| 147 | !(log->log_select_alerted)) {
|
---|
| 148 | /* Someone(s) who was/were select()ing can now
|
---|
| 149 | * be awoken. If there was a blocking read (above),
|
---|
| 150 | * this can only happen if the blocking read didn't
|
---|
| 151 | * swallow all the data (log_size > 0).
|
---|
| 152 | */
|
---|
| 153 | if(log->log_selected & SEL_RD) {
|
---|
| 154 | notify(log->log_select_proc);
|
---|
| 155 | log->log_select_alerted = 1;
|
---|
| 156 | #if LOG_DEBUG
|
---|
| 157 | printf("log notified %d\n", log->log_select_proc);
|
---|
| 158 | #endif
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | return count;
|
---|
| 163 | }
|
---|
| 164 |
|
---|
| 165 | /*===========================================================================*
|
---|
| 166 | * log_append *
|
---|
| 167 | *===========================================================================*/
|
---|
| 168 | PUBLIC void
|
---|
| 169 | log_append(char *buf, int count)
|
---|
| 170 | {
|
---|
| 171 | int w = 0, skip = 0;
|
---|
| 172 |
|
---|
| 173 | if(count < 1) return;
|
---|
| 174 | if(count > LOG_SIZE) skip = count - LOG_SIZE;
|
---|
| 175 | count -= skip;
|
---|
| 176 | buf += skip;
|
---|
| 177 | w = subwrite(&logdevices[0], count, SELF, (vir_bytes) buf);
|
---|
| 178 |
|
---|
| 179 | if(w > 0 && w < count)
|
---|
| 180 | subwrite(&logdevices[0], count-w, SELF, (vir_bytes) buf+w);
|
---|
| 181 | return;
|
---|
| 182 | }
|
---|
| 183 |
|
---|
| 184 | /*===========================================================================*
|
---|
| 185 | * subread *
|
---|
| 186 | *===========================================================================*/
|
---|
| 187 | PRIVATE int
|
---|
| 188 | subread(struct logdevice *log, int count, int proc_nr, vir_bytes user_vir)
|
---|
| 189 | {
|
---|
| 190 | char *buf;
|
---|
| 191 | int r;
|
---|
| 192 | if (count > log->log_size)
|
---|
| 193 | count = log->log_size;
|
---|
| 194 | if (log->log_read + count > LOG_SIZE)
|
---|
| 195 | count = LOG_SIZE - log->log_read;
|
---|
| 196 |
|
---|
| 197 | buf = log->log_buffer + log->log_read;
|
---|
| 198 | if((r=sys_vircopy(SELF,D,(int)buf,proc_nr,D,user_vir, count)) != OK)
|
---|
| 199 | return r;
|
---|
| 200 |
|
---|
| 201 | LOGINC(log->log_read, count);
|
---|
| 202 | log->log_size -= count;
|
---|
| 203 |
|
---|
| 204 | return count;
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | /*===========================================================================*
|
---|
| 208 | * log_transfer *
|
---|
| 209 | *===========================================================================*/
|
---|
| 210 | PRIVATE int log_transfer(proc_nr, opcode, position, iov, nr_req)
|
---|
| 211 | int proc_nr; /* process doing the request */
|
---|
| 212 | int opcode; /* DEV_GATHER or DEV_SCATTER */
|
---|
| 213 | off_t position; /* offset on device to read or write */
|
---|
| 214 | iovec_t *iov; /* pointer to read or write request vector */
|
---|
| 215 | unsigned nr_req; /* length of request vector */
|
---|
| 216 | {
|
---|
| 217 | /* Read or write one the driver's minor devices. */
|
---|
| 218 | unsigned count;
|
---|
| 219 | vir_bytes user_vir;
|
---|
| 220 | struct device *dv;
|
---|
| 221 | unsigned long dv_size;
|
---|
| 222 | int accumulated_read = 0;
|
---|
| 223 | struct logdevice *log;
|
---|
| 224 |
|
---|
| 225 | if(log_device < 0 || log_device >= NR_DEVS)
|
---|
| 226 | return EIO;
|
---|
| 227 |
|
---|
| 228 | /* Get minor device number and check for /dev/null. */
|
---|
| 229 | dv = &log_geom[log_device];
|
---|
| 230 | dv_size = cv64ul(dv->dv_size);
|
---|
| 231 | log = &logdevices[log_device];
|
---|
| 232 |
|
---|
| 233 | while (nr_req > 0) {
|
---|
| 234 | /* How much to transfer and where to / from. */
|
---|
| 235 | count = iov->iov_size;
|
---|
| 236 | user_vir = iov->iov_addr;
|
---|
| 237 |
|
---|
| 238 | switch (log_device) {
|
---|
| 239 |
|
---|
| 240 | case MINOR_KLOG:
|
---|
| 241 | if (opcode == DEV_GATHER) {
|
---|
| 242 | if (log->log_proc_nr || count < 1) {
|
---|
| 243 | /* There's already someone hanging to read, or
|
---|
| 244 | * no real I/O requested.
|
---|
| 245 | */
|
---|
| 246 | return(OK);
|
---|
| 247 | }
|
---|
| 248 |
|
---|
| 249 | if (!log->log_size) {
|
---|
| 250 | if(accumulated_read)
|
---|
| 251 | return OK;
|
---|
| 252 | /* No data available; let caller block. */
|
---|
| 253 | log->log_proc_nr = proc_nr;
|
---|
| 254 | log->log_iosize = count;
|
---|
| 255 | log->log_user_vir = user_vir;
|
---|
| 256 | log->log_revive_alerted = 0;
|
---|
| 257 |
|
---|
| 258 | /* Device_caller is a global in drivers library. */
|
---|
| 259 | log->log_source = device_caller;
|
---|
| 260 | #if LOG_DEBUG
|
---|
| 261 | printf("blocked %d (%d)\n",
|
---|
| 262 | log->log_source, log->log_proc_nr);
|
---|
| 263 | #endif
|
---|
| 264 | return(SUSPEND);
|
---|
| 265 | }
|
---|
| 266 | count = subread(log, count, proc_nr, user_vir);
|
---|
| 267 | if(count < 0) {
|
---|
| 268 | return count;
|
---|
| 269 | }
|
---|
| 270 | accumulated_read += count;
|
---|
| 271 | } else {
|
---|
| 272 | count = subwrite(log, count, proc_nr, user_vir);
|
---|
| 273 | if(count < 0)
|
---|
| 274 | return count;
|
---|
| 275 | }
|
---|
| 276 | break;
|
---|
| 277 | /* Unknown (illegal) minor device. */
|
---|
| 278 | default:
|
---|
| 279 | return(EINVAL);
|
---|
| 280 | }
|
---|
| 281 |
|
---|
| 282 | /* Book the number of bytes transferred. */
|
---|
| 283 | iov->iov_addr += count;
|
---|
| 284 | if ((iov->iov_size -= count) == 0) { iov++; nr_req--; }
|
---|
| 285 | }
|
---|
| 286 | return(OK);
|
---|
| 287 | }
|
---|
| 288 |
|
---|
| 289 | /*============================================================================*
|
---|
| 290 | * log_do_open *
|
---|
| 291 | *============================================================================*/
|
---|
| 292 | PRIVATE int log_do_open(dp, m_ptr)
|
---|
| 293 | struct driver *dp;
|
---|
| 294 | message *m_ptr;
|
---|
| 295 | {
|
---|
| 296 | if (log_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO);
|
---|
| 297 | return(OK);
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | /*============================================================================*
|
---|
| 301 | * log_geometry *
|
---|
| 302 | *============================================================================*/
|
---|
| 303 | PRIVATE void log_geometry(entry)
|
---|
| 304 | struct partition *entry;
|
---|
| 305 | {
|
---|
| 306 | /* take a page from the fake memory device geometry */
|
---|
| 307 | entry->heads = 64;
|
---|
| 308 | entry->sectors = 32;
|
---|
| 309 | entry->cylinders = div64u(log_geom[log_device].dv_size, SECTOR_SIZE) /
|
---|
| 310 | (entry->heads * entry->sectors);
|
---|
| 311 | }
|
---|
| 312 |
|
---|
| 313 | /*============================================================================*
|
---|
| 314 | * log_cancel *
|
---|
| 315 | *============================================================================*/
|
---|
| 316 | PRIVATE int log_cancel(dp, m_ptr)
|
---|
| 317 | struct driver *dp;
|
---|
| 318 | message *m_ptr;
|
---|
| 319 | {
|
---|
| 320 | int d;
|
---|
| 321 | d = m_ptr->TTY_LINE;
|
---|
| 322 | if(d < 0 || d >= NR_DEVS)
|
---|
| 323 | return EINVAL;
|
---|
| 324 | logdevices[d].log_proc_nr = 0;
|
---|
| 325 | logdevices[d].log_revive_alerted = 0;
|
---|
| 326 | return(OK);
|
---|
| 327 | }
|
---|
| 328 |
|
---|
| 329 | /*============================================================================*
|
---|
| 330 | * do_status *
|
---|
| 331 | *============================================================================*/
|
---|
| 332 | PRIVATE void do_status(message *m_ptr)
|
---|
| 333 | {
|
---|
| 334 | int d;
|
---|
| 335 | message m;
|
---|
| 336 |
|
---|
| 337 | /* Caller has requested pending status information, which currently
|
---|
| 338 | * can be pending available select()s, or REVIVE events. One message
|
---|
| 339 | * is returned for every event, or DEV_NO_STATUS if no (more) events
|
---|
| 340 | * are to be returned.
|
---|
| 341 | */
|
---|
| 342 |
|
---|
| 343 | for(d = 0; d < NR_DEVS; d++) {
|
---|
| 344 | /* Check for revive callback. */
|
---|
| 345 | if(logdevices[d].log_proc_nr && logdevices[d].log_revive_alerted
|
---|
| 346 | && logdevices[d].log_source == m_ptr->m_source) {
|
---|
| 347 | m.m_type = DEV_REVIVE;
|
---|
| 348 | m.REP_PROC_NR = logdevices[d].log_proc_nr;
|
---|
| 349 | m.REP_STATUS = logdevices[d].log_status;
|
---|
| 350 | send(m_ptr->m_source, &m);
|
---|
| 351 | logdevices[d].log_proc_nr = 0;
|
---|
| 352 | logdevices[d].log_revive_alerted = 0;
|
---|
| 353 | #if LOG_DEBUG
|
---|
| 354 | printf("revived %d with %d bytes\n",
|
---|
| 355 | m.REP_PROC_NR, m.REP_STATUS);
|
---|
| 356 | #endif
|
---|
| 357 | return;
|
---|
| 358 | }
|
---|
| 359 |
|
---|
| 360 | /* Check for select callback. */
|
---|
| 361 | if(logdevices[d].log_selected && logdevices[d].log_select_proc == m_ptr->m_source
|
---|
| 362 | && logdevices[d].log_select_alerted) {
|
---|
| 363 | m.m_type = DEV_IO_READY;
|
---|
| 364 | m.DEV_SEL_OPS = logdevices[d].log_select_ready_ops;
|
---|
| 365 | m.DEV_MINOR = d;
|
---|
| 366 | #if LOG_DEBUG
|
---|
| 367 | printf("select sending sent\n");
|
---|
| 368 | #endif
|
---|
| 369 | send(m_ptr->m_source, &m);
|
---|
| 370 | logdevices[d].log_selected &= ~logdevices[d].log_select_ready_ops;
|
---|
| 371 | logdevices[d].log_select_alerted = 0;
|
---|
| 372 | #if LOG_DEBUG
|
---|
| 373 | printf("select send sent\n");
|
---|
| 374 | #endif
|
---|
| 375 | return;
|
---|
| 376 | }
|
---|
| 377 | }
|
---|
| 378 |
|
---|
| 379 | /* No event found. */
|
---|
| 380 | m.m_type = DEV_NO_STATUS;
|
---|
| 381 | send(m_ptr->m_source, &m);
|
---|
| 382 |
|
---|
| 383 | return;
|
---|
| 384 | }
|
---|
| 385 |
|
---|
| 386 | /*============================================================================*
|
---|
| 387 | * log_signal *
|
---|
| 388 | *============================================================================*/
|
---|
| 389 | PRIVATE void log_signal(dp, m_ptr)
|
---|
| 390 | struct driver *dp;
|
---|
| 391 | message *m_ptr;
|
---|
| 392 | {
|
---|
| 393 | sigset_t sigset = m_ptr->NOTIFY_ARG;
|
---|
| 394 | if (sigismember(&sigset, SIGKMESS)) {
|
---|
| 395 | do_new_kmess(m_ptr);
|
---|
| 396 | }
|
---|
| 397 | }
|
---|
| 398 |
|
---|
| 399 |
|
---|
| 400 | /*============================================================================*
|
---|
| 401 | * log_other *
|
---|
| 402 | *============================================================================*/
|
---|
| 403 | PRIVATE int log_other(dp, m_ptr)
|
---|
| 404 | struct driver *dp;
|
---|
| 405 | message *m_ptr;
|
---|
| 406 | {
|
---|
| 407 | int r;
|
---|
| 408 |
|
---|
| 409 | /* This function gets messages that the generic driver doesn't
|
---|
| 410 | * understand.
|
---|
| 411 | */
|
---|
| 412 | switch(m_ptr->m_type) {
|
---|
| 413 | case DIAGNOSTICS: {
|
---|
| 414 | r = do_diagnostics(m_ptr);
|
---|
| 415 | break;
|
---|
| 416 | }
|
---|
| 417 | case DEV_STATUS: {
|
---|
| 418 | do_status(m_ptr);
|
---|
| 419 | r = EDONTREPLY;
|
---|
| 420 | break;
|
---|
| 421 | }
|
---|
| 422 | default:
|
---|
| 423 | r = EINVAL;
|
---|
| 424 | break;
|
---|
| 425 | }
|
---|
| 426 | return r;
|
---|
| 427 | }
|
---|
| 428 |
|
---|
| 429 | /*============================================================================*
|
---|
| 430 | * log_select *
|
---|
| 431 | *============================================================================*/
|
---|
| 432 | PRIVATE int log_select(dp, m_ptr)
|
---|
| 433 | struct driver *dp;
|
---|
| 434 | message *m_ptr;
|
---|
| 435 | {
|
---|
| 436 | int d, ready_ops = 0, ops = 0;
|
---|
| 437 | d = m_ptr->TTY_LINE;
|
---|
| 438 | if(d < 0 || d >= NR_DEVS) {
|
---|
| 439 | #if LOG_DEBUG
|
---|
| 440 | printf("line %d? EINVAL\n", d);
|
---|
| 441 | #endif
|
---|
| 442 | return EINVAL;
|
---|
| 443 | }
|
---|
| 444 |
|
---|
| 445 | ops = m_ptr->PROC_NR & (SEL_RD|SEL_WR|SEL_ERR);
|
---|
| 446 |
|
---|
| 447 | /* Read blocks when there is no log. */
|
---|
| 448 | if((m_ptr->PROC_NR & SEL_RD) && logdevices[d].log_size > 0) {
|
---|
| 449 | #if LOG_DEBUG
|
---|
| 450 | printf("log can read; size %d\n", logdevices[d].log_size);
|
---|
| 451 | #endif
|
---|
| 452 | ready_ops |= SEL_RD; /* writes never block */
|
---|
| 453 | }
|
---|
| 454 |
|
---|
| 455 | /* Write never blocks. */
|
---|
| 456 | if(m_ptr->PROC_NR & SEL_WR) ready_ops |= SEL_WR;
|
---|
| 457 |
|
---|
| 458 | /* Enable select calback if no operations were
|
---|
| 459 | * ready to go, but operations were requested,
|
---|
| 460 | * and notify was enabled.
|
---|
| 461 | */
|
---|
| 462 | if((m_ptr->PROC_NR & SEL_NOTIFY) && ops && !ready_ops) {
|
---|
| 463 | logdevices[d].log_selected |= ops;
|
---|
| 464 | logdevices[d].log_select_proc = m_ptr->m_source;
|
---|
| 465 | #if LOG_DEBUG
|
---|
| 466 | printf("log setting selector.\n");
|
---|
| 467 | #endif
|
---|
| 468 | }
|
---|
| 469 |
|
---|
| 470 | #if LOG_DEBUG
|
---|
| 471 | printf("log returning ops %d\n", ready_ops);
|
---|
| 472 | #endif
|
---|
| 473 |
|
---|
| 474 | return(ready_ops);
|
---|
| 475 | }
|
---|