int dvblo_char_exit(void) { int rv = SUCCESS, i, j; switch(initlev) { case 3: #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) class_destroy(dvblo_class); #else class_simple_destroy(dvblo_class); #endif case 2: /* NOTE: we have to lock dvblo_char_chardevs_sem to avoid that after * releasing all devices a new device is created again, becaus our char * device driver is still registered. */ down(&dvblo_char_chardevs_sem); for(j=0; j<DVBLO_CHAR_DEVMAX; j++) { if(chardevs[j].used != 0) { // Wait, until all open file handles have been closed int waitc = 0; while(chardevs[j].hcount > 0) { mprintk(KERN_INFO, "There are %i open file handles for device %s. %s...\n", chardevs[j].hcount, chardevs[j].name, waitc > 0 ? "Still waiting" : "Waiting"); up(&dvblo_char_chardevs_sem); wait_event(dvblo_char_chardevs_wq, (chardevs[j].hcount == 0)); down(&dvblo_char_chardevs_sem); waitc++; } // now that all file handles are closed, release the device i = dvblo_chardev_release(&chardevs[j]); if(i != SUCCESS) { mprintk(KERN_ALERT, "failed to release char device %s: %d\n", chardevs[j].name, i); if(rv == 0) rv = i; } } } #if defined(USE_CDEV) && USE_CDEV != 0 cdev_del(&dvblo_char_cdev); #else i = unregister_chrdev(dvblo_char_major, DVBLO_NAME); if (i < 0) { mprintk(KERN_ALERT, "failed to unregister char device: %d\n", i); if(rv == 0) rv = i; } #endif up(&dvblo_char_chardevs_sem); case 1: #if defined(USE_CDEV) && USE_CDEV != 0 unregister_chrdev_region(MKDEV(dvblo_char_major, 0), DVBLO_CHAR_DEVMAX); #endif dvblo_char_major = 0; default: break; } initlev = 0; return rv; }
static ssize_t clsdev_op_store(int what, struct class_device *dev, const char *buf, size_t count) { char sbuf[64]; ssize_t rv = SUCCESS, minor = MINOR(dev->devt); dprintk(DBGLEV_CHAR3, "what=%d, dev=%p, buf=%p, count=%u\n", what, dev, buf, count); if(minor >= DVBLO_CHAR_DEVMAX || chardevs[minor].used == 0) rv = -ENODEV; else if(down_interruptible(&dvblo_char_chardevs_sem) != 0) rv = -ERESTARTSYS; else { strncat(sbuf, buf, min(sizeof(sbuf)-1, count)); if(chardevs[minor].used == 0) rv = -ENODEV; else switch(what) { case 1: /* Set the L2-framing mode */ { char *end = NULL; unsigned long val = simple_strtoul(sbuf, &end, 0); if(end == NULL || (end[0] != '\0' && !isspace(end[0]))) rv = -EINVAL; else if(val != 188) { mprintk(KERN_ERR, "Invalid value for TS packet size: the supplied value (%lu) was wrong: must be 188\n", val); rv = -EINVAL; } else { chardevs[minor].cfg.ts_sz = val; rv = count; } break; } case 2: /* Set the (default) per-handle write buffer size */ { char *end = NULL; unsigned long val = simple_strtoul(sbuf, &end, 0); if(end == NULL || (end[0] != '\0' && !isspace(end[0]))) rv = -EINVAL; else if((val % chardevs[minor].cfg.ts_sz) != 0) { mprintk(KERN_ERR, "Invalid value for per-handle write buffer size: the supplied value (%lu) was wrong: must be a multiple of %u\n", val, chardevs[minor].cfg.ts_sz); rv = -EINVAL; } else { chardevs[minor].cfg.hwrbuf_sz = val; rv = count; } break; } default: rv = -EINVAL; break; } up(&dvblo_char_chardevs_sem); } return rv; }
static int dvblo_chardev_release(struct dvblo_chardev *chardev) { int rv = SUCCESS, i; if(chardev->used == 0) { rv = -EINVAL; } else if(chardev->hcount > 0) { rv = -EAGAIN; } else { if(chardev->initdone != 0) dprintk(0, "releasing char device with minor device number %u\n", chardev->minor); switch(chardev->initlev) { case 3: #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) for(i=0; i<(sizeof(clsdev_attrs)/sizeof(clsdev_attrs[0])) && rv == SUCCESS; i++) class_device_remove_file(chardev->clsdev, &clsdev_attrs[i]); #endif case 2: #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) class_device_destroy(dvblo_class, MKDEV(dvblo_char_major, chardev->minor)); #else /* The old-style "simple" class API */ class_simple_device_remove(MKDEV(dvblo_char_major, chardev->minor)); #endif case 1: i = dvblo_adap_destroy(chardev->dvblo); if(i < 0) { mprintk(KERN_ALERT, "failed to destroy virtual DVB adapter: %d\n", i); if(rv == 0) rv = i; } default: break; } if(chardev->initdone != 0) mprintk(KERN_INFO, "removed character device %s\n", chardev->name); chardev->initlev = 0; chardev->initdone = 0; chardev->used = 0; } return rv; }
/* wait until all locks are released */ void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line) { int max_count = 5 * HZ; if (atomic_read(lockp) < 0) { mprintk(KERN_WARNING "seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line); // MJR return; } while (atomic_read(lockp) > 0) { if (max_count == 0) { snd_mprintk(KERN_WARNING "seq_lock: timeout [%d left] in %s:%d\n", atomic_read(lockp), file, line); // MJR break; } schedule_timeout_uninterruptible(1); max_count--; } }
int snd_opl3_init(struct snd_opl3 *opl3) { if (! opl3->command) { mprintk(KERN_ERR "snd_opl3_init: command not defined!\n"); // MJR return -EINVAL; } opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT); /* Melodic mode */ opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00); switch (opl3->hardware & OPL3_HW_MASK) { case OPL3_HW_OPL2: opl3->max_voices = MAX_OPL2_VOICES; break; case OPL3_HW_OPL3: case OPL3_HW_OPL4: opl3->max_voices = MAX_OPL3_VOICES; /* Enter OPL3 mode */ opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE); } return 0; }
int dvblo_char_init(void) { int rv = SUCCESS, i; do { if(initlev > 0) { rv = -EINVAL; break; } /* initialize array of char devices */ for(i=0; i<DVBLO_CHAR_DEVMAX; i++) { chardevs[i].used = 0; chardevs[i].minor = i; } #if defined(USE_CDEV) && USE_CDEV != 0 if(dvblo_char_major > 0) { /* we explicitly request the major device number dvblo_char_major */ i = register_chrdev_region(MKDEV(dvblo_char_major, 0), DVBLO_CHAR_DEVMAX, DVBLO_NAME); if(i < 0) { mprintk(KERN_ALERT, "failed to register char device number region (major=%d, count=%d): %d\n", dvblo_char_major, DVBLO_CHAR_DEVMAX, i); rv = dvblo_char_major; break; } } else { /* dynamic allocation of the major device number */ dev_t dev; i = alloc_chrdev_region(&dev, 0, DVBLO_CHAR_DEVMAX, DVBLO_NAME); if(i < 0) { mprintk(KERN_ALERT, "failed to allocate char device number region (count=%d): %d\n", DVBLO_CHAR_DEVMAX, i); rv = dvblo_char_major; break; } dvblo_char_major = MAJOR(dev); } #endif initlev++; /* 1 */ #if defined(USE_CDEV) && USE_CDEV != 0 cdev_init(&dvblo_char_cdev, &dvblo_char_fops); // fops are assigned in cdev_init() //dvblo_char_cdev.ops = &dvblo_char_fops; dvblo_char_cdev.owner = THIS_MODULE; i = cdev_add(&dvblo_char_cdev, MKDEV(dvblo_char_major, 0), DVBLO_CHAR_DEVMAX); if(i < 0) { mprintk(KERN_ALERT, "failed to add char device: %d\n", i); rv = dvblo_char_major; break; } #else /* NOTE: by setting <major> to 0 we request a dynamic major device number */ i = register_chrdev(dvblo_char_major, DVBLO_NAME, &dvblo_char_fops); if(i < 0) { mprintk(KERN_ALERT, "failed to register char device: %d\n", i); rv = i; break; } else if(dvblo_char_major == 0) dvblo_char_major = i; // else requested device major number was accepted #endif initlev++; /* 2 */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) dvblo_class = class_create(THIS_MODULE, DVBLO_NAME); #else dvblo_class = class_simple_create(THIS_MODULE, DVBLO_NAME); #endif if(IS_ERR(dvblo_class)) { rv = PTR_ERR(dvblo_class); mprintk(KERN_ALERT, "failed to create class \"" DVBLO_NAME "\": %d\n", rv); break; } initlev++; /* 3 */ } while(0); if(rv != 0) dvblo_char_exit(); // err cleanup return rv; }
static int dvblo_chardev_init(struct dvblo_chardev *chardev, struct dvblo_chardev_config *cfg) { int rv = SUCCESS, i; if(chardev->used != 0) { rv = -EINVAL; } else { chardev->used = 1; chardev->initlev = 0; chardev->initdone = 0; do { dprintk(DBGLEV_ALL, "initializing char device with minor device number %u\n", chardev->minor); i = snprintf(chardev->name, sizeof(chardev->name), DVBLO_NAME "%d", chardev->minor); if(i < 0 || i >= sizeof(chardev->name)) { if(i < 0) rv = i; else rv = -ENOBUFS; break; } chardev->hcount = 0; chardev->cfg.ts_sz = DVBLO_TS_SZ; chardev->cfg.hwrbuf_sz = 20 * chardev->cfg.ts_sz; i = dvblo_adap_create(chardev->minor, cfg ? &cfg->dvbcfg : NULL, &chardev->dvblo); if(i < 0) { mprintk(KERN_ALERT, "failed to create virtual DVB adapter: %d\n", i); rv = i; break; } chardev->initlev++; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) /* In 2.6.15, class_device_create() got a pointer to the parent device (if any) as its second param */ chardev->clsdev = class_device_create(dvblo_class, NULL, MKDEV(dvblo_char_major, chardev->minor), NULL, chardev->name); #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) /* class_device_create() first appeared in 2.6.13 */ chardev->clsdev = class_device_create(dvblo_class, MKDEV(dvblo_char_major, chardev->minor), NULL, chardev->name); #else /* The old-style "simple" class API */ chardev->clsdev = class_simple_device_add(dvblo_class, MKDEV(dvblo_char_major, chardev->minor), NULL, chardev->name); #endif if(IS_ERR(chardev->clsdev)) { rv = PTR_ERR(chardev->clsdev); mprintk(KERN_ALERT, "failed to create device class \"%s\": %d\n", chardev->name, rv); break; } chardev->initlev++; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) for(i=0; i<(sizeof(clsdev_attrs)/sizeof(clsdev_attrs[0])) && rv == SUCCESS; i++) rv = class_device_create_file(chardev->clsdev, &clsdev_attrs[i]); if(rv != SUCCESS) break; #endif chardev->initlev++; mprintk(KERN_INFO, "added character device %s\n", chardev->name); } while(0); if(rv != 0) dvblo_chardev_release(chardev); // error cleanup } return rv; }
/* dvblo_char_write * * This function forwards TS packets written to the char device to the virtual DVB adapter */ static ssize_t dvblo_char_op_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { ssize_t rv = 0, cbytes; struct dvblo_chardev_handle *handle = (struct dvblo_chardev_handle*) filp->private_data; size_t ts_sz = handle->chardev->cfg.ts_sz, wrleft, rdleft, cbytes_req, bytes_left; dprintk(DBGLEV_CHAR3, "filp=%p, buf=%p, len=%u, off=%p\n", filp, buf, len, off); /* we only accept complete TS packets */ bytes_left = (len / ts_sz) * ts_sz; while(bytes_left > 0) { do { wrleft = handle->buf.wr < handle->buf.rd ? (handle->buf.rd - handle->buf.wr) : (handle->buf.size - handle->buf.wr); if(wrleft == 0) { /* we can't write anymore */ if(rv == 0) { if((filp->f_flags & O_NONBLOCK) != 0) rv = -EAGAIN; /* we were not able to write anything */ else { ///@todo support blocking operation rv = -ENOSYS; } } } } while(wrleft == 0 && rv >= 0); if(wrleft == 0 || rv < 0) break; /* no buffer space left */ if((wrleft % ts_sz) != 0) { mprintk(KERN_ERR, "Lost TS packet boundary (write) in ring buffer. Please report this to the maintainer\n"); rv = -EBADFD; break; } /* calc. how much we can write, at most */ cbytes_req = bytes_left < wrleft ? bytes_left : wrleft; /* NOTE: copy_from_user() returns number of bytes that could not be copied. * On success, this will be zero. */ if(copy_from_user(&handle->buf.base[handle->buf.wr], &buf[rv], cbytes_req) != 0) { rv = -EFAULT; break; /* error while copying */ } dprintk(DBGLEV_CHAR3, "wr=%u, wrleft=%u, bytes_left=%u, cbytes_req=%u, rv=%d\n", handle->buf.wr, wrleft, bytes_left, cbytes_req, rv); /* update write offset (with wrap-around) */ handle->buf.wr = (handle->buf.wr + cbytes_req) % handle->buf.size; rv += cbytes_req; /* @note At this point we could invoke a "virtual multiplexer" which reads from * different input streams */ rdleft = handle->buf.rd > handle->buf.wr ? (handle->buf.size - handle->buf.rd) : (handle->buf.wr - handle->buf.rd); if((rdleft % ts_sz) != 0) { mprintk(KERN_ERR, "Lost read TS packet boundary in ring buffer. Please report this to the maintainer\n"); rv = -EBADFD; break; } cbytes_req = rdleft; cbytes = dvblo_adap_deliver_packets(handle->chardev->dvblo, &handle->buf.base[handle->buf.rd], cbytes_req); if(cbytes < 0) { rv = cbytes; break; /* failed while delivering TS packets to DVB adapter */ } else if((cbytes % ts_sz) != 0) { rv = -EBADFD; break; } dprintk(DBGLEV_CHAR3, "rd=%u, rdleft=%u, cbytes_req=%u, cbytes=%d\n", handle->buf.rd, rdleft, cbytes_req, cbytes); /* update read offset (with wrap-around) */ handle->buf.rd = (handle->buf.rd + cbytes) % handle->buf.size; bytes_left -= cbytes; } return rv; }