/* * SKY MPEG driver - 1.1 * Written by Mark Watson 2004 * * Based on the USB test driver which is: * Copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com) * * This program 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, version 2. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dmxdev.h" #include "dvb_demux.h" #include "dvb_frontend.h" #include "dvb_skympeg_fe.h" #include #ifdef CONFIG_USB_DEBUG static int debug = 1; #else static int debug; #endif /* Use our own dbg macro */ #undef dbg #define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0) /* Version Information */ #define DRIVER_VERSION "v1.0" #define DRIVER_AUTHOR "Mark Watson, skympeg@foft.fsnet.co.uk" #define DRIVER_DESC "SKY MPEG Driver" /* Module parameters */ MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug enabled or not"); #define URBS 128 /********************************************************************** * * Memory management * **********************************************************************/ /* Define these values to match your devices */ #define USB_SKEL_VENDOR_ID 0x04b4 #define USB_SKEL_PRODUCT_ID 0x8613 /* table of devices that work with this driver */ static struct usb_device_id skympeg_table [] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, skympeg_table); /* Get a minor range for your devices from the usb maintainer */ #define USB_SKEL_MINOR_BASE 192 /* Structure to hold all of our device specific stuff */ struct usb_skympeg { struct dvb_demux dvb_demux; struct usb_device * udev; /* save off the usb device pointer */ struct usb_interface * interface; /* the interface for this device */ unsigned char minor; /* the starting minor number for this device */ unsigned char num_ports; /* the number of ports this device has */ char num_interrupt_in; /* number of interrupt in endpoints we have */ char num_bulk_in; /* number of bulk in endpoints we have */ unsigned char * bulk_in_buffer[URBS]; /* the buffer to receive data */ size_t bulk_in_size; /* the size of the receive buffer */ __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ int open; /* if the port is open or not */ int present; /* if the device is not disconnected */ struct semaphore sem; /* locks this structure */ struct urb * input_urb[URBS]; wait_queue_head_t wq; /* Some handy params to substitue for my hardware... */ /* It sends 16-bit with some valid bits... - convert to an mpeg stream */ int validCount; int packetNum; unsigned char buffer[1024]; /* Only need 188, but just in case! */ /* dvb stuff*/ struct dvb_adapter adapter; struct dmxdev dmxdev; struct dmx_frontend fe_hw; struct dmx_frontend fe_mem; struct dvb_frontend * dvb_frontend; int dvbsend; }; /* prevent races between open() and disconnect() */ static DECLARE_MUTEX (disconnect_sem); /* local function prototypes */ static int skympeg_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static int skympeg_open (struct inode *inode, struct file *file); static int skympeg_release (struct inode *inode, struct file *file); static int skympeg_probe (struct usb_interface *interface, const struct usb_device_id *id); static void skympeg_disconnect (struct usb_interface *interface); /* * File operations needed when we register this driver. * This assumes that this driver NEEDS file operations, * of course, which means that the driver is expected * to have a node in the /dev directory. If the USB * device were for a network interface then the driver * would use "struct net_driver" instead, and a serial * device would use "struct tty_driver". */ static struct file_operations skympeg_fops = { /* * The owner field is part of the module-locking * mechanism. The idea is that the kernel knows * which module to increment the use-counter of * BEFORE it calls the device's open() function. * This also means that the kernel can decrement * the use-counter again before calling release() * or should the open() function fail. */ .owner = THIS_MODULE, .ioctl = skympeg_ioctl, .open = skympeg_open, .release = skympeg_release, }; /* * usb class driver info in order to get a minor number from the usb core, * and to have the device registered with devfs and the driver core */ static struct usb_class_driver skympeg_class = { .name = "usb/skympeg%d", .fops = &skympeg_fops, .minor_base = USB_SKEL_MINOR_BASE, }; /* usb specific object needed to register this driver with the usb subsystem */ static struct usb_driver skympeg_driver = { .owner = THIS_MODULE, .name = "skympegeton", .probe = skympeg_probe, .disconnect = skympeg_disconnect, .id_table = skympeg_table, }; /** * usb_skympeg_debug_data */ static inline void usb_skympeg_debug_data (const char *function, int size, const unsigned char *data) { int i; if (!debug) return; printk (KERN_DEBUG __FILE__": %s - length = %d, data = ", function, size); for (i = 0; i < size; ++i) { printk ("%.2x ", data[i]); } printk ("\n"); } /******************************************************************** * dvb feed stop/start * ******************************************************************/ static int dvb_enable(struct dvb_demux_feed * dvbdmxfeed) { struct usb_skympeg * dev = (struct usb_skympeg *)dvbdmxfeed->demux; switch (dvbdmxfeed->type) { case DMX_TYPE_TS: // know what this is.. break; case DMX_TYPE_SEC: // Other one? break; default: return -EINVAL; } //err("dvb_enable - really"); down(&dev->sem); dev->dvbsend++; //err("dvb_enable:%d", dev->dvbsend); up(&dev->sem); return 0; } static int dvb_disable(struct dvb_demux_feed * dvbdmxfeed) { struct usb_skympeg * dev = (struct usb_skympeg *)dvbdmxfeed->demux; down(&dev->sem); dev->dvbsend--; //err("dvb_disable:%d", dev->dvbsend); up(&dev->sem); return 0; } /** * skympeg_delete */ static inline void skympeg_delete (struct usb_skympeg *dev) { dev->dvb_demux.dmx.close(&dev->dvb_demux.dmx); dev->dvb_demux.dmx.remove_frontend(&dev->dvb_demux.dmx, &dev->fe_hw); dev->dvb_demux.dmx.remove_frontend(&dev->dvb_demux.dmx, &dev->fe_mem); err("dvb_demux..."); dvb_dmxdev_release(&dev->dmxdev); dvb_dmx_release(&dev->dvb_demux); err("dvb_dmx_release"); if (dev->dvb_frontend) dvb_unregister_frontend(dev->dvb_frontend); dev->dvb_frontend = 0; err("dvb_unregister_frontend"); dvb_unregister_adapter(&dev->adapter); err("dvb_unregister_adapter"); int i; for (i=0; ibulk_in_buffer[i]); kfree (dev); } /** * skympeg_open */ static int skympeg_open (struct inode *inode, struct file *file) { struct usb_skympeg *dev = NULL; struct usb_interface *interface; int subminor; int retval = 0; dbg("%s", __FUNCTION__); subminor = iminor(inode); /* prevent disconnects */ down (&disconnect_sem); interface = usb_find_interface (&skympeg_driver, subminor); if (!interface) { err ("%s - error, can't find device for minor %d", __FUNCTION__, subminor); retval = -ENODEV; goto exit_no_device; } dev = usb_get_intfdata(interface); if (!dev) { retval = -ENODEV; goto exit_no_device; } /* lock this device */ down (&dev->sem); /* increment our usage count for the driver */ ++dev->open; /* save our object in the file's private structure */ file->private_data = dev; /* unlock this device */ up (&dev->sem); exit_no_device: up (&disconnect_sem); return retval; } /** * skympeg_release */ static int skympeg_release (struct inode *inode, struct file *file) { struct usb_skympeg *dev; int retval = 0; dev = (struct usb_skympeg *)file->private_data; if (dev == NULL) { dbg ("%s - object is NULL", __FUNCTION__); return -ENODEV; } dbg("%s - minor %d", __FUNCTION__, dev->minor); /* lock our device */ down (&dev->sem); if (dev->open <= 0) { dbg ("%s - device not opened", __FUNCTION__); retval = -ENODEV; goto exit_not_opened; } --dev->open; if (!dev->present && !dev->open) { /* the device was unplugged before the file was released */ up (&dev->sem); skympeg_delete (dev); return 0; } exit_not_opened: up (&dev->sem); return retval; } /** * skympeg_ioctl */ static int skympeg_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct usb_skympeg *dev; dev = (struct usb_skympeg *)file->private_data; /* lock this object */ down (&dev->sem); /* verify that the device wasn't unplugged */ if (!dev->present) { up (&dev->sem); return -ENODEV; } // dbg("%s - minor %d, cmd 0x%.4x, arg %ld", __FUNCTION__, // dev->minor, cmd, arg); /* fill in your device specific stuff here */ int result = -ENOTTY; /* unlock the device */ up (&dev->sem); /* return that we did not understand this ioctl call */ return result; } static void urbResult(struct urb * urb, struct pt_regs * regs) { struct usb_skympeg * dev = urb->context; int status; if (!dev->udev) { err("urbResult: device gone"); return; } // Extract contents of this one switch (urb->status) { case 0: /* success */ break; case -ECONNRESET: case -ENOENT: case -ESHUTDOWN: /* this urb is terminated, clean up */ err("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); return; default: err("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); goto exit; } static int count = 0; count++; int bufferPos = urb->actual_length/2; int sizeDone = 0; unsigned short * transfer = urb->transfer_buffer; static unsigned int total=0; while(bufferPos--) { unsigned short data = *transfer++; if ((data&0x500) == (0x500)) // valid && start { if ((data&0xff)!=0x47) { err("Bad packet start:%x\n", data&0xff); } else { // Start of packet dev->validCount = 0; } } if (data&0x400) { if (dev->validCount == -1) { err("Valid outside packet\n"); dev->validCount = -2; // Ignore till next pkt! } else if (dev->validCount>=0 && dev->validCount<188) { // Valid data -> store... (not must no be >188 or we trash our structure!) dev->buffer[dev->validCount++] = data&0xff; } } else { if (dev->validCount>=0) { if (dev->validCount!=188) { err("Bad packet size:%d\n", dev->validCount); } else if (dev->packetNum<64) { dev->packetNum++; //Ignore first 64... } else { // Chuck to dvb here if (dev->dvbsend) { if ((dev->buffer[3]&0xc0) == 0) // Ignore scrambled packets { dvb_dmx_swfilter_packets(&dev->dvb_demux, dev->buffer,1); total++; } } sizeDone+=188; } dev->validCount = -1; } } } static int iterations =0; static int iterLogCount = 0; if (iterations++>5000000) { iterLogCount++; iterations = 0; err("skympeg(%x): 5000000 iterations - packets:%d", iterLogCount,total); } wake_up(&dev->wq); exit: // Submit next urb urb->status=0; urb->dev=dev->udev; status = usb_submit_urb (urb, GFP_ATOMIC); if (status) err ("%s - usb_submit_urb failed with result %d", __FUNCTION__, status); } /** * skympeg_probe * * Called by the usb core when a new device is connected that it thinks * this driver might be interested in. */ static int skympeg_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_device *udev = interface_to_usbdev(interface); struct usb_skympeg *dev = NULL; struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; size_t buffer_size =0; int i; int retval = -ENOMEM; /* See if the device offered us matches what we can accept */ if ((udev->descriptor.idVendor != USB_SKEL_VENDOR_ID) || (udev->descriptor.idProduct != USB_SKEL_PRODUCT_ID)) { return -ENODEV; } /* allocate memory for our device state and initialize it */ dev = kmalloc (sizeof(struct usb_skympeg), GFP_KERNEL); if (dev == NULL) { err ("Out of memory"); return -ENOMEM; } memset (dev, 0x00, sizeof (*dev)); init_MUTEX (&dev->sem); dev->udev = udev; dev->interface = interface; dev->validCount = -1; dev->packetNum = 0; /* set up the endpoint information */ /* check out the endpoints */ /* use only the specified (hardcoded at present!) bulk-in endpoint */ int alt = 1; // 2- INT 1-BULK iface_desc = &interface->altsetting[alt]; for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { endpoint = &iface_desc->endpoint[i].desc; err("Looking at endpoint:%d %x\n", i, endpoint->bEndpointAddress); if ((i==4) && !dev->bulk_in_endpointAddr && (endpoint->bEndpointAddress & USB_DIR_IN) && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)) { err("Found IN\n"); /* we found a bulk in endpoint */ buffer_size = endpoint->wMaxPacketSize; dev->bulk_in_size = buffer_size; dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; break; } } if (!dev->bulk_in_endpointAddr) { err("Couldn't find both bulk-in endpoint: %x", dev->bulk_in_endpointAddr); goto error; } /* init waitqueue */ init_waitqueue_head(&dev->wq); /* allow device read, write and ioctl */ dev->present = 1; /* we can register the device now, as it is ready */ usb_set_intfdata (interface, dev); retval = usb_register_dev (interface, &skympeg_class); if (retval) { /* something prevented us from registering this driver */ err ("Not able to get a minor for this device."); usb_set_intfdata (interface, NULL); goto error; } dev->minor = interface->minor; /* register dvb demux thingy... */ // TODO // dev->adaptor... /* General dvb register */ int res = dvb_register_adapter(&dev->adapter, "SkyMPEG", THIS_MODULE); err("dvb register returned:%d\n", res); dev->dvb_frontend = dvb_skympeg_fe_attach(); if (dev->dvb_frontend) { err("Attached to frontend OK\n"); if (dvb_register_frontend(&dev->adapter, dev->dvb_frontend)) { err("Frontend registration failed\n"); } else { err("Frontend registration ok\n"); } } else { err("Failed to attach to frontend\n"); } /* Register software demux */ memset(&dev->dvb_demux, 0, sizeof(dev->dvb_demux)); dev->dvb_demux.dmx.capabilities = DMX_TS_FILTERING|DMX_SECTION_FILTERING; dev->dvb_demux.priv = NULL; dev->dvb_demux.filternum = 255; // ? dev->dvb_demux.feednum = 255; dev->dvb_demux.start_feed = dvb_enable; dev->dvb_demux.stop_feed = dvb_disable; dev->dvb_demux.write_to_decoder = 0; if ((dvb_dmx_init(&dev->dvb_demux)) < 0) { err("dvb_dmx_init failed"); goto error; } /* WTF? */ dev->dmxdev.filternum = 256; dev->dmxdev.demux = &dev->dvb_demux.dmx; dev->dmxdev.capabilities = 0; if ((dvb_dmxdev_init(&dev->dmxdev, &dev->adapter)) < 0) { err("dmxdev init failed"); goto error; } // Some stuff nicked from the dummy device... int result; dev->fe_hw.source = DMX_FRONTEND_0; if ((result = dev->dvb_demux.dmx.add_frontend(&dev->dvb_demux.dmx, &dev->fe_hw)) < 0) { err("add_frontend fe_hw failed (errno = %d)\n", result); return result; } dev->fe_mem.source = DMX_MEMORY_FE; if ((result = dev->dvb_demux.dmx.add_frontend(&dev->dvb_demux.dmx, &dev->fe_mem)) < 0) { err("add_frontend fe_mem failed (errno = %d)\n", result); return result; } if ((result = dev->dvb_demux.dmx.connect_frontend(&dev->dvb_demux.dmx, &dev->fe_hw)) < 0) { printk("dummyadap: connect frontend failed (errno = %d)\n", result); return result; } /* We want interface 1 for now for the bulk endpoint...*/ usb_set_interface(dev->udev, 0, alt); /* Set up a urbResult for the bulk endpoint */ int urbCount = URBS; while(urbCount--) { dev->input_urb[urbCount] = usb_alloc_urb(0, GFP_KERNEL); if (!dev->input_urb[urbCount]) { err ("Unable to allocate urb"); goto error; } dev->bulk_in_buffer[urbCount] = kmalloc (buffer_size, GFP_KERNEL); if (!dev->bulk_in_buffer[urbCount]) { err("Couldn't allocate bulk_in_buffer"); goto error; } usb_fill_bulk_urb ( dev->input_urb[urbCount], dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), dev->bulk_in_buffer[urbCount], 512, urbResult, dev ); } int err; int j; for (j=0; jinput_urb[j], GFP_KERNEL); if (err) err("urb burned down"); } /* let the user know what node this device is now attached to */ info ("SKY MPEG device now attached to USBSkel-%d", dev->minor); return 0; error: /*skympeg_delete (dev);*/ return retval; } /** * skympeg_disconnect * * Called by the usb core when the device is removed from the system. * * This routine guarantees that the driver will not submit any more urbs * by clearing dev->udev. It is also supposed to terminate any currently * active urbs. Unfortunately, usb_bulk_msg(), used in skympeg_read(), does * not provide any way to do this. But at least we can cancel an active * write. */ static void skympeg_disconnect(struct usb_interface *interface) { struct usb_skympeg *dev; int minor; /* prevent races with open() */ down (&disconnect_sem); dev = usb_get_intfdata (interface); usb_set_intfdata (interface, NULL); down (&dev->sem); minor = dev->minor; /* give back our minor */ usb_deregister_dev (interface, &skympeg_class); /* prevent device read, write and ioctl */ dev->present = 0; up (&dev->sem); /* if the device is opened, skympeg_release will clean this up */ if (!dev->open) skympeg_delete (dev); up (&disconnect_sem); info("SKY MPEG #%d now disconnected", minor); } /** * usb_skympeg_init */ static int __init usb_skympeg_init(void) { int result; /* register this driver with the USB subsystem */ result = usb_register(&skympeg_driver); if (result) { err("usb_register failed. Error number %d", result); return result; } info(DRIVER_DESC " " DRIVER_VERSION); return 0; } /** * usb_skympeg_exit */ static void __exit usb_skympeg_exit(void) { /* deregister this driver with the USB subsystem */ usb_deregister(&skympeg_driver); } module_init (usb_skympeg_init); module_exit (usb_skympeg_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL");