diff --git a/98-ctn91xx.rules b/98-ctn91xx.rules new file mode 100644 index 0000000..a69bebf --- /dev/null +++ b/98-ctn91xx.rules @@ -0,0 +1 @@ +KERNEL=="ctn91xx_*", NAME="ceton/%k", MODE="0666",OWNER="root",GROUP="root" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6987bca --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +KERNEL_VERSION := $(shell uname -r) +KERNEL_DIR := /lib/modules/$(KERNEL_VERSION)/build + +PWD := $(shell pwd) + +targets= + +SOURCES := ctn91xx_driver.o ctn91xx_interrupt.o ctn91xx_ioctl.o \ + ctn91xx_util.o ctn91xx_event.o ctn91xx_mpeg.o \ + ctn91xx_net.o ctn91xx_reset.o \ + \ + ctn91xx_rpc.o + +#assuming built-in if cross compiling +ifdef CROSS_COMPILE +targets=ctn91xx +obj-y := ctn91xx_builtin.o +EXTRA_CFLAGS := -DLINUX -DUSE_PCI=0 -DUSE_LEON=1 -DHAS_MPEG_DMA=1 -DUSE_INTERNAL=0 +SOURCES += ctn91xx_leon.o +else +targets=ctn91xx_module +obj-m := ctn91xx.o +EXTRA_CFLAGS := -DLINUX -DUSE_PCI=1 -DUSE_LEON=0 -DHAS_MPEG_DMA=1 -DUSE_INTERNAL=0 +SOURCES += ctn91xx_pci.o ctn91xx_rtp.o +endif + +ctn91xx_builtin-objs := $(SOURCES) + +ctn91xx-objs := $(SOURCES) + +all: $(targets) + +ctn91xx: + @echo "Building ctn91xx driver..." + @(cd $(KERNEL_DIR) && make -j15 -C $(KERNEL_DIR) SUBDIRS=$(PWD)) + +ctn91xx_module: + @(cd $(KERNEL_DIR) && make -j15 -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules) + + +install: + @echo "Installing ctn91xx driver..." + @(cd $(KERNEL_DIR) && make -j20 -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules_install) + cp 98-ctn91xx.rules /etc/udev/rules.d/ + /sbin/depmod -a + +clean: + -rm -f *.o *.ko .*.cmd .*.flags *.mod.c Modules.symvers Module.symvers + -rm -rf .tmp_versions *.ko.unsigned modules.order + +uninstall: + rm -f /lib/modules/$(KERNEL_VERSION)/extra/ctn91xx.ko + @rm /etc/udev/rules.d/98-ctn91xx.rules + /sbin/depmod -a diff --git a/README b/README new file mode 100644 index 0000000..3c67de0 --- /dev/null +++ b/README @@ -0,0 +1,48 @@ +***************************************************************************** +* Installation +***************************************************************************** + +Install make, gcc, and kernel headers for your distribution. Then run: +make +sudo make install +sudo modprobe ctn91xx + +***************************************************************************** +* Usage +***************************************************************************** + +You should see a network interface when you run ifconfig -a called ctn0 + +You can set a static IP address on the 192.168.200.0/24 subnet (don't +use 192.168.200.1) or just use a dhcp client to get an IP address for it. + +The InfiniTV device webpage will be available at http://192.168.200.1 +From there you can tune via frequency or channel number. Only ClearQAM and +CCI=0 content is available on linux due to lack of DRM support. + +Access video via a special device file created under: +/dev/ceton/ctn91xx_mpeg0_0-5 + +You can run "mplayer -cache 8192 /dev/ceton/ctn91xx_mpeg0_0" to play video +off the first tuner. Depending on your system, extra buffering via the shell +might improve performance. E.g. +cat /dev/ceton/ctn91xx_mpeg0_0 | mplayer -cache 8192 - + + +***************************************************************************** +* Multiple Cards +***************************************************************************** +More than one InfiniTV is handled by creating more network interfaces +(ctn1,ctn2,etc...). The IP assignment scheme is: +192.168.200.1 +192.168.201.2 +192.168.202.3 +192.168.203.4 +etc.. + +Run make as normal user, and then 'make install' as root. Then either reboot +or "modprobe ctn91xx". Once installed, you should see a network device called +ctn0. The +InfiniTV web page will be at http://192.168.200.1. Only ClearQAM and CCI=0 +channels are available in linux due to lack of DRM support. diff --git a/ctn91xx.h b/ctn91xx.h new file mode 100644 index 0000000..652bcee --- /dev/null +++ b/ctn91xx.h @@ -0,0 +1,145 @@ +#ifndef CTN91XX_H +#define CTN91XX_H + +#include + +#define PRINT_RW 0 + +#define USE_MPEG_NOTIFY 1 +#define USE_SPIT_I2C 1 +#define USE_I2C_BURST 1 +#define SPIT_WRITE_BUFFER_SIZE 4096 +#define SPIT_READ_BUFFER_SIZE 4096 +#define SPIT_HW_BUFFER_SIZE (512) +#define SPIT_BULK_BUFFER_SIZE (2*1024*1024) +#define RPC_BUFFER_SIZE 516 +#define READ_FOR_EVERY_WRITE USE_PCI +#define PRINT_DROP_IN_ISR 0 + +#define CHECK_NOTIFY_CC 0 +#if USE_LEON + #undef CHECK_NOTIFY_CC + #define CHECK_NOTIFY_CC 0 +#endif + +#define NUM_RX_BUFFERS 100 +#define RX_BUFFER_LENGTH 2048 +#define MAX_DESC_PER_INTERFACE 200 + +//values for which_proc +#define CC_PROC (1<<0) +#define DRM_PROC (1<<1) +#define DMA_PROC (1<<2) +#define EXT_PROC (1<<3) + +#define MPEG_BUFFER_NPAGES 256 +#define FILTER_BUFFER_NPAGES 16 +#if USE_LEON + #undef MPEG_BUFFER_NPAGES + #undef FILTER_BUFFER_NPAGES + #define MPEG_BUFFER_NPAGES 8 + #define FILTER_BUFFER_NPAGES 16 +#endif + +#define MAX_NUMBER_DEVICES 16 +#if USE_LEON + #undef MAX_NUMBER_DEVICES + #define MAX_NUMBER_DEVICES 1 +#endif + +#define NUM_MPEG_DEVICES 12 + +#define REQUEST_LONG_TIMEOUT 5100 +#define I2C_MSEC_DELAY 2000 +#define SPIT_MSEC_DELAY 1000 +#define SPI_MSEC_DELAY 1000 +#define SCARD_MSEC_DELAY 5100 +#define MCARD_MSEC_DELAY 5100 +#define RPC_SEND_TIMEOUT 200 +#define POWER_SETTING_DELAY 200 + +#define DEVICE_NAME "ctn91xx" +#define CTRL_DEVICE_NUMBER 0 +#define MPEG_DEVICE_NUMBER 1 +#define CETON_MAJOR 231 +#define CETON_MINOR( top, bot ) ( (((top) << 4)&0xf0) | ((bot)&0x0f) ) +#define CETON_MINOR_BOARD_NUMBER( minor ) ( ( (minor) >> 4) & 0x0f ) +#define CETON_MINOR_DEVICE_NUMBER( minor ) ( (minor)&0x0f ) +#define MKDEV_CTRL( dev ) MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, CTRL_DEVICE_NUMBER ) ) + +#define CTN91XX_SPI_BUFFER_MAX 8 + +#define CTN91XX_MMAP_IO_OFFSET 0 +#define CTN91XX_MMAP_TRANSLATION_IO_OFFSET CTN91XX_MMAP_IO_OFFSET+CTN91XX_REG_REGION_SIZE +#define CTN91XX_MMAP_BULK_BUFFER_OFFSET CTN91XX_MMAP_TRANSLATION_IO_OFFSET+CTN91XX_TRANSLATION_REG_REGION_SIZE +#define CTN91XX_MMAP_SPIT_READ_BUFFER_OFFSET (CTN91XX_MMAP_BULK_BUFFER_OFFSET + SPIT_BULK_BUFFER_SIZE) +#define CTN91XX_MMAP_SPIT_WRITE_BUFFER_OFFSET (CTN91XX_MMAP_SPIT_READ_BUFFER_OFFSET + SPIT_READ_BUFFER_SIZE) +#define CTN91XX_MMAP_SPIT_OFFSET_SKIP (SPIT_READ_BUFFER_SIZE+SPIT_WRITE_BUFFER_SIZE) + +/* card inserted types */ +#define CARD_TYPE_PCMCIA 0 +#define CARD_TYPE_MCARD 1 +#define CARD_TYPE_UNKNOWN 2 + +#define I2C_STATE_WRITE_START 0x00 +#define I2C_STATE_WRITE 0x01 +#define I2C_STATE_STOPPING_WRITE 0x02 +#define I2C_STATE_READ_START 0x03 +#define I2C_STATE_READ 0x04 +#define I2C_STATE_STOPPING 0x05 +#define I2C_STATE_DONE 0x06 + +#define I2C_ERROR_NO_ERROR 0x00 +#define I2C_ERROR_ARBITRATION_LOST 0x01 +#define I2C_ERROR_SLAVE_BUSY 0x02 +#define I2C_ERROR_TIMED_OUT 0x03 +#define I2C_ERROR_INVALID_BUS 0x04 +#define I2C_ERROR_NOT_CONNECTED 0x05 + +#define SPIT_ERROR_NO_ERROR 0x00 +#define SPIT_ERROR_TIMED_OUT 0x01 +#define SPIT_ERROR_INVALID_BUS 0x02 +#define SPIT_ERROR_NOT_CONNECTED 0x03 + +#include "ctn91xx_ioctl.h" +#include "ctn91xx_registers.h" + +#if defined(__KERNEL__) || defined(NT) + +#include "ctn91xx_kal.h" +#include "ctn91xx_structs.h" + +#define DEBUG 1 + +#if DEBUG +#define CETON_PRINTF(args...) printk( args ) +#else +#define CETON_PRINTF(args...) +#endif + +#define ERROR(s, args...) CETON_PRINTF( KERN_ERR "%s:%i ERROR: (%d) " s "\n", __FUNCTION__, __LINE__, (dev ? dev->board_number : -1), ## args) +#define WARNING(s, args...) CETON_PRINTF( KERN_WARNING "%s:%i WARNING: (%d) " s "\n", __FUNCTION__, __LINE__, (dev ? dev->board_number : -1), ## args) +#define INFO(s, args...) CETON_PRINTF( KERN_INFO "%s:%i : (%d) " s "\n", __FUNCTION__, __LINE__, (dev ? dev->board_number : -1), ## args) +#define SUCCESS(s, args...) CETON_PRINTF( KERN_DEBUG "%s:%i : (%d) " s "\n", __FUNCTION__, __LINE__, (dev ? dev->board_number : -1), ## args) + +#define ERROR_INLINE(s, args...) CETON_PRINTF( KERN_ERR s "", ## args) +#define WARNING_INLINE(s, args...) CETON_PRINTF( KERN_WARNING s "", ## args) +#define INFO_INLINE(s, args...) CETON_PRINTF( KERN_DEBUG s "", ## args) +#define SUCCESS_INLINE(s, args...) CETON_PRINTF( KERN_DEBUG s "", ## args) + +#define NOT_IMPLEMENTED( s, args...) CETON_PRINTF( KERN_DEBUG "%s:%i : " "Not Implemented: " s "\n", __FUNCTION__, __LINE__, ## args) +#define QUIETLY_NOT_IMPLEMENTED( s, args...) + +#if defined __cplusplus +extern "C" { +#endif + + int ctn91xx_ioctl_handle(uint32_t cmd, unsigned long arg, ctn91xx_dev_t* dev, int compat); + +#if defined __cplusplus +} +#endif + +#endif //__KERNEL__ || WIN32 + +#endif //CTN91XX_H diff --git a/ctn91xx_driver.c b/ctn91xx_driver.c new file mode 100644 index 0000000..7078d5c --- /dev/null +++ b/ctn91xx_driver.c @@ -0,0 +1,258 @@ +#include "ctn91xx.h" +#include "ctn91xx_interrupt.h" +#include "ctn91xx_ioctl.h" +#include "ctn91xx_util.h" +#include "ctn91xx_driver.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_event.h" + +#if USE_PCI +#include "ctn91xx_pci.h" +#endif + + +void ctn91xx_print_compilation(void) +{ + ctn91xx_dev_t* dev = NULL; + INFO("driver compiled at %s on %s", __TIME__, __DATE__); +} + +ctn91xx_dev_t* ctn91xx_lookup_dev_from_file(struct inode* inode, struct file* file) +{ + if(file->private_data) { + ctn91xx_dev_fd_state_t* fd_state = (ctn91xx_dev_fd_state_t*)file->private_data; + return fd_state->dev; + } else if(inode) { + ctn91xx_dev_fd_state_t* fd_state = kmalloc( sizeof( ctn91xx_dev_fd_state_t ), GFP_KERNEL ); + + if( !fd_state ) { + return NULL; + } + + if( CETON_MINOR_DEVICE_NUMBER( iminor(inode) ) == 0 ) { + fd_state->dev = container_of( inode->i_cdev, ctn91xx_dev_t, ctrl_cdev ); + } else { + fd_state->dev = container_of( inode->i_cdev, ctn91xx_dev_t, mpeg_cdev ); + } + fd_state->minor = iminor(inode); + file->private_data = fd_state; + return fd_state->dev; + } else { + BUG_ON(!inode); + return NULL; + } +} + +int ctn91xx_lookup_minor_from_file(struct inode* inode, struct file* file) +{ + if(inode) { + return iminor(inode); + } else if(file && file->private_data) { + ctn91xx_dev_fd_state_t* fd_state = (ctn91xx_dev_fd_state_t*)file->private_data; + return fd_state->minor; + } else { + BUG(); + return -1; + } +} + +void ctn91xx_cleanup_dev_from_file(struct inode* inode, struct file* file) +{ + if(file->private_data) { + kfree(file->private_data); + } +} + +static int ctn91xx_open(struct inode * inode, struct file * file) +{ + int ret = 0; + ctn91xx_dev_t* dev = NULL; + + dev = ctn91xx_lookup_dev_from_file(inode, file); + + mutex_lock(&dev->fd_mutex); + dev->event_user_cnt++; + if(dev->event_user_cnt == 1) { + dev->always_scard = 0; + } + mutex_unlock(&dev->fd_mutex); + + return ret; +} + +static int ctn91xx_release(struct inode * inode, struct file * file) +{ + ctn91xx_dev_t* dev = NULL; + + dev = ctn91xx_lookup_dev_from_file(inode, file); + + mutex_lock(&dev->fd_mutex); + dev->event_user_cnt--; + if(dev->event_user_cnt == 0) { + + ctn91xx_event_cleanup_waiting(dev); + } + mutex_unlock(&dev->fd_mutex); + + ctn91xx_cleanup_dev_from_file(inode, file); + + return 0; +} + + +static long ctn91xx_ioctl(struct file *filp, uint cmd, ulong arg) +{ + ctn91xx_dev_t* dev = ctn91xx_lookup_dev_from_file( NULL, filp ); + return ctn91xx_ioctl_handle( cmd, arg, dev, 0 ); +} + +static long ctn91xx_compat_ioctl(struct file *filp, uint cmd, ulong arg) +{ + ctn91xx_dev_t* dev = ctn91xx_lookup_dev_from_file( NULL, filp ); + return ctn91xx_ioctl_handle( cmd, arg, dev, 1 ); +} + + + +static int ctn91xx_mmap(struct file* filp, struct vm_area_struct* vma) +{ + int ret = 0; + ctn91xx_dev_t* dev = NULL; + unsigned long pfn = 0; + unsigned long size = vma->vm_end - vma->vm_start; + uint32_t page_offset_addr = vma->vm_pgoff * PAGE_SIZE; + + dev = ctn91xx_lookup_dev_from_file(NULL, filp); + + switch( page_offset_addr ) { + case CTN91XX_MMAP_IO_OFFSET: + { + if( size > CTN91XX_REG_REGION_SIZE ) { + return -EINVAL; + } + + pfn = (unsigned long)dev->hw_reg_base >> PAGE_SHIFT; + vma->vm_flags |= VM_IO | VM_RESERVED; + + if(io_remap_pfn_range(vma, vma->vm_start, + pfn, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + ERROR("failed to mmap pfn=0x%08lx -> vm_start=0x%08lx, vm_end=0x%08lx", pfn, vma->vm_start, vma->vm_end); + return -EINVAL; + } + break; + } + case CTN91XX_MMAP_TRANSLATION_IO_OFFSET: + { + if( size > CTN91XX_TRANSLATION_REG_REGION_SIZE ) { + return -EINVAL; + } + + pfn = (unsigned long)dev->translation_hw_reg_base >> PAGE_SHIFT; + vma->vm_flags |= VM_IO | VM_RESERVED; + + if(io_remap_pfn_range(vma, vma->vm_start, + pfn, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + ERROR("failed to mmap pfn=0x%08lx -> vm_start=0x%08lx, vm_end=0x%08lx", pfn, vma->vm_start, vma->vm_end); + return -EINVAL; + } + break; + break; + } + default: + { + ERROR("unknown mmap offset %d", page_offset_addr ); + return -EINVAL; + } + } + + return ret; +} + +static struct file_operations ctn91xx_ctl_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ctn91xx_ioctl, + .compat_ioctl = ctn91xx_compat_ioctl, + .open = ctn91xx_open, + .release = ctn91xx_release, + .mmap = ctn91xx_mmap, + .poll = ctn91xx_event_poll, + .llseek = no_llseek, + .read = ctn91xx_event_read +}; + +static struct class* ctn91xx_class; + +int ctn91xx_register_ctrl_device( ctn91xx_dev_t* dev ) +{ + cdev_init( &dev->ctrl_cdev, &ctn91xx_ctl_fops ); + dev->ctrl_cdev.owner = THIS_MODULE; + cdev_add( &dev->ctrl_cdev, MKDEV_CTRL( dev ), 1 ); + + dev->class = ctn91xx_class; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + device_create( dev->class, NULL, MKDEV_CTRL( dev ), "ctn91xx_ctl%d", dev->board_number ); +#else + device_create( dev->class, NULL, MKDEV_CTRL( dev ), dev, "ctn91xx_ctl%d", dev->board_number ); +#endif + return 0; +} + +void ctn91xx_unregister_ctrl_device( ctn91xx_dev_t* dev ) +{ + device_destroy( dev->class, MKDEV_CTRL( dev ) ); + cdev_del( &dev->ctrl_cdev ); +} + +static int __init ctn91xx_init(void) +{ + int ret; + + ctn91xx_dev_t* dev = NULL; + + ctn91xx_print_compilation(); + + + ret = register_chrdev_region( MKDEV( CETON_MAJOR, 0 ), 255 , DEVICE_NAME ); + + if( ret != 0 ) { + return ret; + } + + ctn91xx_class = class_create( THIS_MODULE, DEVICE_NAME ); + + if( IS_ERR( ctn91xx_class ) ) { + ERROR("failed to create class"); + unregister_chrdev_region( MKDEV( CETON_MAJOR, 0 ), 255 ); + return PTR_ERR( ctn91xx_class ); + } + +#if USE_PCI + ret = ctn91xx_register_pci_driver(); +#endif + + + return ret; +} + +static void __exit ctn91xx_exit(void) +{ +#if USE_PCI + ctn91xx_unregister_pci_driver(); +#endif + + + class_destroy( ctn91xx_class ); + unregister_chrdev_region( MKDEV( CETON_MAJOR, 0 ), 255 ); +} + +module_init(ctn91xx_init); +module_exit(ctn91xx_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Austin Foxley"); +MODULE_DESCRIPTION("Ceton HW Driver"); + diff --git a/ctn91xx_driver.h b/ctn91xx_driver.h new file mode 100644 index 0000000..ad46c19 --- /dev/null +++ b/ctn91xx_driver.h @@ -0,0 +1,18 @@ +#ifndef CTN91XX_DRIVER_H +#define CTN91XX_DRIVER_H + +#include "ctn91xx_kal.h" + +void ctn91xx_print_compliation(void); + +int ctn91xx_register_ctrl_device( ctn91xx_dev_t* dev ); +void ctn91xx_unregister_ctrl_device( ctn91xx_dev_t* dev ); + +ctn91xx_dev_t* ctn91xx_lookup_dev_from_file(struct inode* inode, struct file* file); +void ctn91xx_cleanup_dev_from_file(struct inode* inode, struct file* file); +int ctn91xx_lookup_minor_from_file(struct inode* inode, struct file* file); + +void ctn91xx_destroy_board(ctn91xx_dev_t* dev); + +#endif + diff --git a/ctn91xx_event.c b/ctn91xx_event.c new file mode 100644 index 0000000..69db212 --- /dev/null +++ b/ctn91xx_event.c @@ -0,0 +1,351 @@ +#include "ctn91xx.h" +#include "ctn91xx_event.h" +#include "ctn91xx_util.h" +#include "ctn91xx_driver.h" +#include "ctn91xx_rpc.h" + + +void ctn91xx_event_cleanup_waiting(ctn91xx_dev_t* dev) +{ + struct list_head* i = NULL; + ctn91xx_event_t* event = NULL; + int not_done = 1; + int spin_count = 0; + spin_lock_w_flags(&dev->event_queue_lock); + + if(list_empty(&dev->event_queue)) { + not_done = 0; + } + + while(not_done) { + list_for_each(i, &dev->event_queue) { + event = list_entry(i, ctn91xx_event_t, list); + + if(event->list.next == &dev->event_queue) { + not_done = 0; + } + + //cleanup + list_del(&event->list); + ctn91xx_event_cleanup(dev, event); + break; + } + spin_count++; + if( spin_count > 100 ) { + WARNING("event cleanup took too long, breaking out"); + break; + } + } + spin_unlock_w_flags(&dev->event_queue_lock); +} + + +void ctn91xx_event_reset(ctn91xx_dev_t* dev) +{ + ctn91xx_event_cleanup_waiting(dev); +} + +int ctn91xx_event_ready(ctn91xx_dev_t* dev) +{ + int ret = 0; + spin_lock_w_flags(&dev->event_queue_lock); + if(!list_empty(&dev->event_queue)) { + ret = 1; + } + spin_unlock_w_flags(&dev->event_queue_lock); + return ret; +} + +ctn91xx_event_t* ctn91xx_remove_first_event(ctn91xx_dev_t* dev) +{ + ctn91xx_event_t* event = NULL; + spin_lock_w_flags(&dev->event_queue_lock); + if(!list_empty(&dev->event_queue)) { + event = list_entry(dev->event_queue.next, ctn91xx_event_t, list); + list_del(&event->list); + } + + spin_unlock_w_flags(&dev->event_queue_lock); + return event; +} + +ctn91xx_event_t* ctn91xx_remove_first_delayed_event(ctn91xx_dev_t* dev) +{ + ctn91xx_event_t* event = NULL; + spin_lock_w_flags(&dev->delayed_event_queue_lock); + if(!list_empty(&dev->delayed_event_queue)) { + event = list_entry(dev->delayed_event_queue.next, ctn91xx_event_t, list); + list_del(&event->list); + } + + spin_unlock_w_flags(&dev->delayed_event_queue_lock); + return event; +} + + +void ctn91xx_event_cleanup(ctn91xx_dev_t* dev, ctn91xx_event_t* event) +{ + if(event) { + kfree(event); + } +} + +unsigned int ctn91xx_event_poll(struct file* filp, struct poll_table_struct* wait) +{ + ctn91xx_dev_t* dev = NULL; + unsigned int mask = 0; + + dev = ctn91xx_lookup_dev_from_file(NULL, filp); + + if(ctn91xx_event_ready(dev) && dev->event_user_cnt) { + mask = POLLIN | POLLRDNORM; + } + + poll_wait(filp, &dev->event_waitqueue, wait); + + return mask; +} + +ssize_t ctn91xx_event_read_common(ctn91xx_dev_t* dev, char __user * data, size_t count, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + ssize_t retval = 0; + ctn91xx_event_t* event = NULL; + add_wait_queue(&dev->event_waitqueue, &wait); + + do { + int should_break = 0; +restart_wait: + should_break = 0; + + __set_current_state(TASK_INTERRUPTIBLE); + + + should_break = ctn91xx_event_ready(dev); + + if(should_break) + break; + + if(!dev->event_user_cnt) { + retval = -EAGAIN; + goto cleanup; + } + + if(nonblock) { + retval = -EAGAIN; + goto cleanup; + } + + if(signal_pending(current)) { + retval = -ERESTARTSYS; + INFO("event read interrupted"); + goto cleanup; + } + + schedule(); + } while(1); + + if(count < SIZEOF_EVENT_HEADER) { + ERROR("event read buffer too small, needs to be %d", + SIZEOF_EVENT_HEADER); + retval = -EAGAIN; + goto cleanup; + } + + event = ctn91xx_remove_first_event(dev); + + if(!event) { + goto restart_wait; + } + + if(copy_to_user((void __user*)data, &event->type, 1)) { + retval = -EIO; + goto cleanup; + } + if(copy_to_user((void __user*)data + 1, &event->length, 2)) { + retval = -EIO; + goto cleanup; + } + if(copy_to_user((void __user*)data + 3, &event->origin, 1)) { + retval = -EIO; + goto cleanup; + } + if(copy_to_user((void __user*)data + 4, &event->control_byte, 1)) { + retval = -EIO; + goto cleanup; + } + if(copy_to_user((void __user*)data + 5, &event->seqno, 1)) { + retval = -EIO; + goto cleanup; + } + + retval = SIZEOF_EVENT_HEADER; + +cleanup: + if(event) { + ctn91xx_event_cleanup(dev, event); + } + + current->state = TASK_RUNNING; + remove_wait_queue(&dev->event_waitqueue, &wait); + + return retval; +} + +ssize_t ctn91xx_event_read(struct file* filp, char __user * data, size_t count, loff_t* offset) +{ + ctn91xx_dev_t* dev = ctn91xx_lookup_dev_from_file( NULL, filp ); + return ctn91xx_event_read_common( dev, data, count, filp->f_flags & O_NONBLOCK ); +} + +int ctn91xx_ioctl_event_read(ctn91xx_dev_t* dev, unsigned long arg, int compat) +{ + if( compat ) { + event_read_compat_t read_data; + if( copy_from_user( &read_data, (event_read_compat_t*) arg, sizeof(event_read_compat_t) ) ) { + ERROR("input"); + return -EIO; + } + + return ctn91xx_event_read_common( dev, (char __user*)(unsigned long)read_data.data, read_data.count, 0 ); + } else { + event_read_t read_data; + if( copy_from_user( &read_data, (event_read_t*) arg, sizeof(event_read_t) ) ) { + ERROR("input"); + return -EIO; + } + + return ctn91xx_event_read_common( dev, read_data.data, read_data.count, 0 ); + } +} + +ctn91xx_event_t* ctn91xx_event_new(ctn91xx_dev_t* dev, cablecard_interrupt_t* data) +{ + return ctn91xx_event_new_full(dev, data->type, data->length, data->origin, data->control_byte, data->seqno); +} + +ctn91xx_event_t* ctn91xx_event_new_full( + ctn91xx_dev_t* dev, + uint8_t type, + uint16_t length, + uint8_t origin, + uint8_t control_byte, + uint8_t seqno) +{ + ctn91xx_event_t* event = NULL; + uint8_t should_queue = 1; + + if(!dev->event_user_cnt) { + + if( type == CTN91XX_EVENT_RPC_RECVD ) { +#if USE_LEON + should_queue = 1; +#else + return NULL; +#endif + } else { + return NULL; + } + } + + event = kmalloc(sizeof(ctn91xx_event_t), GFP_ATOMIC); + if(!event) { + ERROR("kmalloc failed. event dropped"); + return NULL; + } + + memset(event, 0, sizeof(ctn91xx_event_t)); + event->type = type; + event->length = length; + event->origin = origin; + event->control_byte = control_byte; + event->seqno = seqno; + event->should_queue = should_queue; + + return event; +} + +void ctn91xx_queue_event(ctn91xx_dev_t* dev, ctn91xx_event_t* event) +{ + spin_lock_w_flags(&dev->event_queue_lock); + list_add_tail(&event->list,&dev->event_queue); + spin_unlock_w_flags(&dev->event_queue_lock); + + wake_up_interruptible(&dev->event_waitqueue); +} + +void ctn91xx_queue_delayed_event(ctn91xx_dev_t* dev, ctn91xx_event_t* event) +{ + spin_lock_w_flags(&dev->delayed_event_queue_lock); + list_add_tail(&event->list,&dev->delayed_event_queue); + spin_unlock_w_flags(&dev->delayed_event_queue_lock); + + schedule_work(&dev->delayed_event_worker); +} + + +void ctn91xx_handle_delayed_event( ctn91xx_dev_t* dev, ctn91xx_event_t* event ) +{ +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +void ctn91xx_delayed_event_worker(void* work) +#else +void ctn91xx_delayed_event_worker(struct work_struct* work) +#endif +{ + ctn91xx_dev_t* dev = container_of(work, ctn91xx_dev_t, delayed_event_worker); + ctn91xx_event_t* event = ctn91xx_remove_first_delayed_event(dev); + + ctn91xx_handle_delayed_event( dev, event ); + + if(event->should_queue) { + ctn91xx_queue_event(dev, event); + } else { + ctn91xx_event_cleanup(dev, event); + } +} + +#define CONTROL_MSG_SLOT_NUMBER 0x8 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +void ctn91xx_delayed_slot_number_worker( void* work ) +#else +void ctn91xx_delayed_slot_number_worker( struct work_struct* work ) +#endif +{ + rpc_send_t rpc; + ctn91xx_dev_t* dev = container_of(work, ctn91xx_dev_t, delayed_slot_number_worker); + + INFO("sending slot number %d", dev->board_number); + + rpc.msg = kmalloc( 8, GFP_ATOMIC ); + rpc.length = 8; + rpc.section_length = 8; + rpc.payload_start = 1; + + rpc.msg[0] = CONTROL_MSG_SLOT_NUMBER; + *((uint32_t*)&rpc.msg[4]) = htonl( (dev->face_present << 8) | dev->board_number ); + ctn91xx_rpc_send_kernel( dev, &rpc ); + kfree( rpc.msg ); +} + +void ctn91xx_event_dump_queue(ctn91xx_dev_t* dev) +{ + struct list_head* i = NULL; + ctn91xx_event_t* event = NULL; + spin_lock_w_flags(&dev->event_queue_lock); + + if(list_empty(&dev->event_queue)) { + goto end; + } + + list_for_each(i, &dev->event_queue) { + event = list_entry(i, ctn91xx_event_t, list); + INFO("\tevent %p, type %#02x", event, event->type); + } + +end: + spin_unlock_w_flags(&dev->event_queue_lock); +} + diff --git a/ctn91xx_event.h b/ctn91xx_event.h new file mode 100644 index 0000000..59e4292 --- /dev/null +++ b/ctn91xx_event.h @@ -0,0 +1,40 @@ +#ifndef CTN91XX_SIGNAL_H +#define CTN91XX_SIGNAL_H + +#include "ctn91xx.h" + +ctn91xx_event_t* ctn91xx_event_new(ctn91xx_dev_t* dev, cablecard_interrupt_t* data); + +ctn91xx_event_t* ctn91xx_event_new_full( + ctn91xx_dev_t* dev, + uint8_t type, + uint16_t length, + uint8_t origin, + uint8_t control_byte, + uint8_t seqno); + +ssize_t ctn91xx_event_read(struct file* filp, char __user * data, size_t count, loff_t* offset); +unsigned int ctn91xx_event_poll(struct file* filp, struct poll_table_struct* wait); +ctn91xx_event_t* ctn91xx_remove_first_event(ctn91xx_dev_t* dev); +void ctn91xx_event_cleanup(ctn91xx_dev_t* dev, ctn91xx_event_t* event); +void ctn91xx_event_cleanup_waiting(ctn91xx_dev_t* dev); + +void ctn91xx_queue_event(ctn91xx_dev_t* dev, ctn91xx_event_t* event); +void ctn91xx_queue_delayed_event(ctn91xx_dev_t* dev, ctn91xx_event_t* event); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +void ctn91xx_delayed_event_worker(void* work); +void ctn91xx_delayed_slot_number_worker( void* work ); +#else +void ctn91xx_delayed_event_worker(struct work_struct* work); +void ctn91xx_delayed_slot_number_worker( struct work_struct* work ); +#endif + + +void ctn91xx_event_reset(ctn91xx_dev_t* dev); + +void ctn91xx_event_dump_queue(ctn91xx_dev_t* dev); + +int ctn91xx_ioctl_event_read(ctn91xx_dev_t* dev, unsigned long arg, int compat); + +#endif diff --git a/ctn91xx_interrupt.c b/ctn91xx_interrupt.c new file mode 100644 index 0000000..89fd366 --- /dev/null +++ b/ctn91xx_interrupt.c @@ -0,0 +1,342 @@ +#include "ctn91xx_interrupt.h" +#include "ctn91xx_util.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_net.h" +#include "ctn91xx_event.h" +#include "ctn91xx_rtp.h" + + +static int ctn91xx_interrupt_is_mpeg_dma(ctn91xx_dev_t* dev) +{ + if(dev->int_enable) { + uint8_t status = ctn91xx_read8(dev->mpeg_dma_base, MPEG_DMA_BANK_INTERRUPT_MASKED); + return status == 1; + } else { + return 0; + } +} + +#if USE_LEON +static int ctn91xx_interrupt_is_mpeg_filter_dma(ctn91xx_dev_t* dev) +{ + if(dev->int_enable) { + uint8_t status = ctn91xx_read8(dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_BANK_INTERRUPT_MASKED); + return status == 1; + } else { + return 0; + } +} +#endif + + +#define CONTROL_MSG_RTP_SETUP 0x1 +#define CONTROL_MSG_RTP_DESTROY 0x2 +#define CONTROL_MSG_MASK_TABLE_SETUP 0x3 +#define CONTROL_MSG_SLOT_NUMBER 0x8 + +int ctn91xx_interrupt_rpc_recvd(ctn91xx_dev_t* dev, uint16_t msg_len, uint8_t origin) +{ + ctn91xx_event_t* event; + + uint32_t msg_buffer_buffer = MSG_BUFFER_BUFFER; + uint8_t* payload = ((uint8_t*)dev->msg_base) + msg_buffer_buffer + 8 + 4; + uint8_t payload_start = ctn91xx_read8( dev->msg_base, msg_buffer_buffer ); + uint8_t msg_type = ctn91xx_read8( dev->msg_base, msg_buffer_buffer + 8 ); + +#if USE_PCI + if( payload_start ) { + if( msg_type == CONTROL_MSG_MASK_TABLE_SETUP ) { +#if HAS_MPEG_DMA + ctn91xx_reinit_mpeg_registers( dev ); +#endif + schedule_work(&dev->delayed_slot_number_worker); + INFO("handled mask table setup"); + return 0; + } else if( msg_type == CONTROL_MSG_RTP_SETUP ) { + ctn91xx_rtp_setup(dev, payload, msg_len - 12); + return 0; + } else if( msg_type == CONTROL_MSG_RTP_DESTROY ) { + ctn91xx_rtp_destroy(dev, payload, msg_len - 12); + return 0; + } + } +#endif + + //event to user + event = ctn91xx_event_new_full(dev, + CTN91XX_EVENT_RPC_RECVD, + msg_len, + origin, + 0,//unused + 0//unused + ); + + if(!event) { + INFO("dropping interrupt type %02x, no listeners", CTN91XX_EVENT_RPC_RECVD); + return 0; + } + + INFO("queueing rpc recvd: payload_start: %d msg_type: %d", payload_start, msg_type); + + ctn91xx_queue_event(dev, event); + return 1; +} + +int ctn91xx_interrupt_rpc_ack_recvd( ctn91xx_dev_t* dev ) +{ + ctn91xx_event_t* event; + + //event to user + event = ctn91xx_event_new_full(dev, + CTN91XX_EVENT_RPC_ACK_RECVD, + 0, + CTN91XX_ORIGIN_MSG_BUFFER, + 0,//unused + 0//unused + ); + + if(!event) { + INFO("dropping interrupt type %02x, no listeners", CTN91XX_EVENT_RPC_ACK_RECVD); + return 0; + } + + ctn91xx_queue_event(dev, event); + return 1; +} + +irqreturn_t rpc_isr(int irq, void *ptr) +{ + ctn91xx_dev_t* dev = ptr; + irqreturn_t ret_val = IRQ_NONE; + uint16_t msg_len; + struct net_device* ndev = dev->net_dev; + ctn91xx_net_priv_t* priv = netdev_priv(ndev); + + spin_lock_w_flags( &priv->lock ); + + if( ctn91xx_read8( dev->msg_base, MSG_BUFFER_MSG_AVAIL ) && + ( ctn91xx_read32( dev->msg_base, MSG_BUFFER_TAG ) & 0xff ) == MSG_BUFFER_TAG_CONTROL ) { + + ret_val = IRQ_HANDLED; + + ctn91xx_write8( 0, dev->msg_base, MSG_BUFFER_INT_ACK_AVAIL ); + + msg_len = ctn91xx_read16( dev->msg_base, MSG_BUFFER_MSG_LEN ); + + if( !ctn91xx_interrupt_rpc_recvd( dev, msg_len, CTN91XX_ORIGIN_MSG_BUFFER ) ) { + ctn91xx_write8( 1, dev->msg_base, MSG_BUFFER_MSG_RECV ); + } + } + + if( ctn91xx_read8( dev->msg_base, MSG_BUFFER_MSG_RECV ) && + ( ctn91xx_read32( dev->msg_base, MSG_BUFFER_LOCAL_TAG ) & 0xff ) == MSG_BUFFER_TAG_CONTROL ) { + + ret_val = IRQ_HANDLED; + + ctn91xx_write8( 0, dev->msg_base, MSG_BUFFER_INT_ACK_RECV ); + + if( !priv->tx_outstanding ) { + ERROR("unexpected rpc tx interrupt"); + } else { + ctn91xx_interrupt_rpc_ack_recvd( dev ); + priv->tx_outstanding = 0; + } + + ctn91xx_net_wake_up( dev ); + wake_up( &dev->msg_buffer_wait_queue ); + } + + spin_unlock_w_flags( &priv->lock ); + + return ret_val; +} + +#if USE_LEON +#endif + +#if HAS_MPEG_DMA + +irqreturn_t ctn91xx_mpeg_isr(int irq, void *ptr) +{ + ctn91xx_dev_t *dev = ptr; + irqreturn_t ret_val = IRQ_NONE; + int i; + uint16_t notify_vector = 0; + uint16_t notify_mask = 0; + + notify_vector = ctn91xx_read16(dev->mpeg_dma_base, MPEG_DMA_NOTIFY_VECTOR); + + if(notify_vector) { + for(i=0; impeg_dma_base, MPEG_DMA_NOTIFY_BASE + (i*8)); + uint16_t bytes_per_page = ctn91xx_read16(dev->mpeg_dma_base, MPEG_DMA_NOTIFY_BYTES_PER_PAGE_BASE + (i*8)); + + uint8_t pages_cleared = ctn91xx_interrupt_mpeg_notify(dev, origin, notify_cnt, bytes_per_page); + + if( pages_cleared != notify_cnt ) { + dev->stats.mismatched_notify_clear[i]++; + } + + ctn91xx_write8(pages_cleared, dev->mpeg_dma_base, MPEG_DMA_NOTIFY_BASE + (i*8)); + ctn91xx_write8(0x1, dev->mpeg_dma_base, MPEG_DMA_NOTIFY_BASE + (i*8) + 1); + } + } + ret_val = IRQ_HANDLED; + ctn91xx_write32(dev->dma_timeout, dev->mpeg_dma_base, MPEG_DMA_TIMEOUT_VALUE); + ctn91xx_write8(0x01, dev->mpeg_dma_base, MPEG_DMA_SET_TIMEOUT); + } + +#if USE_LEON + notify_vector = ctn91xx_read16(dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_NOTIFY_VECTOR); + + if(notify_vector) { + for(i=0; impeg_filter_dma_base, MPEG_FILTER_DMA_NOTIFY_BASE + (i*8)); + bytes_per_page = ctn91xx_read16(dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_NOTIFY_BYTES_PER_PAGE_BASE + (i*8)); + + if( notify_cnt > num_pages_per_bank( dev, origin - CTN91XX_ORIGIN_TUNER0 ) ) { + if( printk_ratelimit() ) { + ERROR("notify_cnt %d > bank size", notify_cnt ); + } + } + + pages_cleared = ctn91xx_interrupt_mpeg_notify(dev, origin, notify_cnt, bytes_per_page); + + ctn91xx_write8(pages_cleared, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_NOTIFY_BASE + (i*8)); + ctn91xx_write8(0x1, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_NOTIFY_BASE + (i*8) + 1); + } + } + ret_val = IRQ_HANDLED; + ctn91xx_write32(dev->dma_timeout, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_TIMEOUT_VALUE); + ctn91xx_write8(0x01, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_SET_TIMEOUT); + } +#endif + + if(ctn91xx_interrupt_is_mpeg_dma(dev)) { + for(i=0; impeg_dma_base, MPEG_DMA_LOCK_BASE + (i*2)); + + if(bank_lock) { + + uint8_t which_bank = ctn91xx_read8(dev->mpeg_dma_base, MPEG_DMA_LOCK_BASE + (i*2) + 1); + ctn91xx_interrupt_mpeg_dma(dev, origin, which_bank); + + ctn91xx_write8(0, dev->mpeg_dma_base, MPEG_DMA_LOCK_BASE + (i*2)); + } + } + ret_val = IRQ_HANDLED; + } + +#if USE_LEON + if(ctn91xx_interrupt_is_mpeg_filter_dma(dev)) { + for(i=0; impeg_filter_dma_base, MPEG_FILTER_DMA_LOCK_BASE + (i*2)); + + if(bank_lock) { + uint8_t which_bank = ctn91xx_read8(dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_LOCK_BASE + (i*2) + 1); + ctn91xx_interrupt_mpeg_dma(dev, origin, which_bank); + ctn91xx_write8(0, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_LOCK_BASE + (i*2)); + } + } + ret_val = IRQ_HANDLED; + } +#endif + + return ret_val; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +irqreturn_t ctn91xx_isr(int irq, void *ptr, struct pt_regs* regs) +#else +irqreturn_t ctn91xx_isr(int irq, void *ptr) +#endif +{ + ctn91xx_dev_t *dev = ptr; + spin_lock_w_flags( &dev->isr_lock ); + + dev->stats.isr_count++; + + if( !dev->int_enable ) { + goto not_handled; + } + + if( rpc_isr( irq, ptr ) == IRQ_HANDLED ) { + dev->stats.isr_rpc_count++; + goto handled; + } + + if( ctn91xx_net_isr( irq, ptr ) == IRQ_HANDLED ) { + dev->stats.isr_net_count++; + goto handled; + } + + +#if HAS_MPEG_DMA + if( ctn91xx_mpeg_isr(irq,ptr) == IRQ_HANDLED ) { + dev->stats.isr_mpeg_count++; + goto handled; + } +#endif + + +#if USE_LEON +#endif + +not_handled: + dev->stats.isr_not_handled++; + spin_unlock_w_flags( &dev->isr_lock ); + return IRQ_NONE; +handled: + spin_unlock_w_flags( &dev->isr_lock ); + return IRQ_HANDLED; +} + + +irqreturn_t ctn91xx_timer_isr(int irq, void* ptr) +{ + irqreturn_t ret = IRQ_NONE; + ctn91xx_dev_t* dev = (ctn91xx_dev_t*)ptr; + int i=0; + + for(i=0; itimer_reg_base, TIMER_IRQ_REG(i)); + if(irqset) { + INFO("timer interrupt for timer %d, clearing", i); + ctn91xx_write8(0, dev->timer_reg_base, TIMER_IRQ_REG(i)); + ret = IRQ_HANDLED; + } + } + + return ret; +} + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +#define ktime_get ktime_get_real +#endif + + + + + diff --git a/ctn91xx_interrupt.h b/ctn91xx_interrupt.h new file mode 100644 index 0000000..e10e51a --- /dev/null +++ b/ctn91xx_interrupt.h @@ -0,0 +1,22 @@ +#ifndef CTN91XX_INTERRUPT_H +#define CTN91XX_INTERRUPT_H + +#include "ctn91xx.h" + +void cablecard_interrupt_handle(cablecard_interrupt_t* data, ctn91xx_dev_t* dev); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +irqreturn_t ctn91xx_isr(int irq, void *ptr, struct pt_regs* regs); +#else +irqreturn_t ctn91xx_isr(int irq, void *ptr); +#endif +irqreturn_t cablecard_reg_isr(int irq, void *ptr); +irqreturn_t ctn91xx_timer_isr(int irq, void *ptr); +irqreturn_t i2c_isr(int irq, void *ptr); +irqreturn_t spit_isr(int irq, void *ptr); +irqreturn_t ctn91xx_mpeg_isr(int irq, void *ptr); +irqreturn_t drm_reg_isr(int irq, void *ptr); +irqreturn_t gpio_isr(int irq, void* ptr); +irqreturn_t scte21_isr(int irq, void* ptr); + +#endif diff --git a/ctn91xx_ioctl.c b/ctn91xx_ioctl.c new file mode 100644 index 0000000..94b64ff --- /dev/null +++ b/ctn91xx_ioctl.c @@ -0,0 +1,56 @@ +#include "ctn91xx.h" +#include "ctn91xx_ioctl.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_reset.h" +#include "ctn91xx_rpc.h" +#include "ctn91xx_event.h" + +static int +ctn91xx_get_stats( ctn91xx_dev_t* dev, unsigned long arg ) +{ + if( copy_to_user( (void* __user)arg, &dev->stats, sizeof(ctn91xx_stats_t) ) ) { + return -EINVAL; + } + + return 0; +} + +int ctn91xx_ioctl_handle(uint32_t cmd, unsigned long arg, ctn91xx_dev_t* dev, int compat) +{ + int ret = 0; + switch(cmd) { + case CTN91XX_IOCTL_RESET_ALL: + ctn91xx_reset_all(dev); + break; +#if HAS_MPEG_DMA + case CTN91XX_IOCTL_VBUFFER_STATUS: + ret = ctn91xx_print_vbuffer_state(dev, arg); + break; + case CTN91XX_IOCTL_DMA_STATS: + ret = ctn91xx_mpeg_dma_stats( dev, arg ); + break; +#endif + case CTN91XX_IOCTL_EVENT_READ: + ret = ctn91xx_ioctl_event_read(dev, arg, compat); + break; + case CTN91XX_IOCTL_RPC_SEND: + ret = ctn91xx_rpc_send( dev, arg, compat ); + break; + case CTN91XX_IOCTL_GET_STATS: + ret = ctn91xx_get_stats( dev, arg ); + break; + case CTN91XX_IOCTL_IS_BOARD_BOOTED: + ret = dev->board_booting; + break; + case TCGETS: + //fopen calls this on us...it's the "is_a_tty" call + //just tell it to go away + ret = -EINVAL; + break; + default: + ret = -EINVAL; + WARNING("Unknown ioctl on ctn91xx device (%d).", cmd); + break; + } + return ret; +} diff --git a/ctn91xx_ioctl.h b/ctn91xx_ioctl.h new file mode 100644 index 0000000..8269325 --- /dev/null +++ b/ctn91xx_ioctl.h @@ -0,0 +1,154 @@ +#ifndef CTN91XX_IOCTL_H +#define CTN91XX_IOCTL_H + +#define SIOCTL(n) (100+n) + +/* user space ioctl's */ +#define CTN91XX_IOCTL_RESET_ALL SIOCTL(6) +#define CTN91XX_IOCTL_SET_ALWAYS_SCARD SIOCTL(7) +#define CTN91XX_IOCTL_DOWNLOAD_FW_TO_MEMORY SIOCTL(8) +#define CTN91XX_IOCTL_MPEG_READ_SETUP SIOCTL(9) +#define CTN91XX_IOCTL_MPEG_READ_START SIOCTL(10) +#define CTN91XX_IOCTL_MPEG_READ_COMPLETE SIOCTL(11) +#define CTN91XX_IOCTL_VBUFFER_STATUS SIOCTL(12) +#define CTN91XX_IOCTL_SET_SLOT_NUMBER SIOCTL(13) +#define CTN91XX_IOCTL_EVENT_READ SIOCTL(14) +#define CTN91XX_IOCTL_I2C_WRITE_READ SIOCTL(15) +#define CTN91XX_IOCTL_DMA_STATS SIOCTL(16) +#define CTN91XX_IOCTL_MPEG_SEND SIOCTL(17) +#define CTN91XX_IOCTL_RPC_SEND SIOCTL(18) +#define CTN91XX_IOCTL_SPIT_WRITE_READ SIOCTL(19) +// 20 unused +#define CTN91XX_IOCTL_GPIO_READ_WRITE SIOCTL(21) +#define CTN91XX_IOCTL_ENABLE_POWER_SETTING SIOCTL(22) +#define CTN91XX_IOCTL_GET_STATS SIOCTL(23) +#define CTN91XX_IOCTL_GPIO_RAW_SETUP SIOCTL(24) +#define CTN91XX_IOCTL_IS_BOARD_BOOTED SIOCTL(25) +#define CTN91XX_IOCTL_SET_FACE_ATTACHED SIOCTL(26) +#define CTN91XX_IOCTL_NOTIFY_MASK_TABLE SIOCTL(27) +#define CTN91XX_IOCTL_PRINT_DESC_LIST SIOCTL(28) +#define CTN91XX_IOCTL_FORCE_TX_IRQ SIOCTL(29) +#define CTN91XX_IOCTL_FORCE_RX_IRQ SIOCTL(30) +#define CTN91XX_IOCTL_PRINT_NET_STATS SIOCTL(31) +#define CTN91XX_IOCTL_GET_SLOT_NUMBER SIOCTL(32) +#define CTN91XX_IOCTL_GET_FACE_PRESENT SIOCTL(33) + +typedef struct { + uint32_t count; + uint32_t amount_last_read; +} mpeg_read_t; + +typedef struct { + uint32_t count; + int32_t out_fd; + uint8_t* header; + uint32_t header_length; +} mpeg_send_t; + +typedef struct { + uint32_t count; + int32_t out_fd; + uint32_t header; + uint32_t header_length; +} mpeg_send_compat_t; + +typedef struct { + uint32_t npages; + uint32_t read_index; + uint16_t bytes_per_page; +} mpeg_stream_setup_t; + +typedef struct { + uint8_t* data; + uint32_t count; +} event_read_t; + +typedef struct { + uint32_t data; + uint32_t count; +} event_read_compat_t; + +typedef struct { + uint8_t error;//must be first + uint16_t read_addr; + uint32_t read_len; + uint16_t write_addr; + uint32_t write_len; + uint8_t bus; +} i2c_write_read_t; + +typedef struct { + uint8_t error;//must be first + uint8_t bus; + uint8_t bucket; + uint32_t read_len; + uint32_t write_len; +} spit_write_read_t; + +typedef struct { + uint8_t tuner; + uint32_t packet_count; +} dma_stats_t; + +typedef struct { + uint8_t payload_start; + uint32_t section_length; + uint16_t length; + uint8_t* msg; +} rpc_send_t; + +typedef struct { + uint8_t payload_start; + uint32_t section_length; + uint16_t length; + uint32_t msg; +} rpc_send_compat_t; + +typedef struct { + uint32_t fw_len; + uint8_t* fw; +} fw_t; + +typedef struct { + uint8_t pin; + uint8_t value; + uint8_t is_read; +} gpio_read_write_t; + +typedef struct { + uint32_t is_input; + uint32_t mask; +} gpio_raw_setup_t; + +typedef struct { + uint8_t enable; + uint8_t type; +} enable_power_setting_t; + +typedef struct { + uint32_t packet_count[NUM_MPEG_DEVICES]; + uint32_t drop_count[NUM_MPEG_DEVICES]; + uint32_t discont_count[NUM_MPEG_DEVICES]; + uint32_t mismatched_notify_clear[NUM_MPEG_DEVICES]; + uint32_t isr_count; + uint32_t isr_mpeg_count; + uint32_t isr_timer_count; + uint32_t isr_cablecard_count; + uint32_t isr_spit_count; + uint32_t isr_drm_count; + uint32_t isr_gpio_count; + uint32_t isr_scte21_count; + uint32_t isr_rpc_count; + uint32_t isr_net_count; + uint32_t isr_eth_count; + uint32_t isr_not_handled; + uint32_t scte21_checksum_failed_count; + uint32_t scte21_slow_count; + uint32_t scte21_overflow_count; + uint32_t scte21_continue_count; + uint32_t isr_net_rx_count; + uint32_t isr_net_tx_count; + uint32_t isr_net_zero_count; +} ctn91xx_stats_t; + +#endif diff --git a/ctn91xx_kal.h b/ctn91xx_kal.h new file mode 100644 index 0000000..b6c8fb6 --- /dev/null +++ b/ctn91xx_kal.h @@ -0,0 +1,44 @@ +#ifndef _CTN91XX_KAL_H +#define _CTN91XX_KAL_H + +#if defined(__KERNEL__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//it's in an arch specific header...this is just easier +#define TCGETS 0x5401 + + +#define writell(_d,_p) (*(volatile uint64_t*)(_p)) = (uint64_t)_d +#define readll(_p) ((volatile uint64_t*)(_p))[0] + +#define spin_lock_w_flags(_l) unsigned long _flags; spin_lock_irqsave((_l), _flags) +#define spin_relock_w_flags(_l) spin_lock_irqsave((_l), _flags) +#define spin_unlock_w_flags(_l) spin_unlock_irqrestore((_l), _flags) + +#endif + +#endif + diff --git a/ctn91xx_mpeg.c b/ctn91xx_mpeg.c new file mode 100644 index 0000000..1979f65 --- /dev/null +++ b/ctn91xx_mpeg.c @@ -0,0 +1,1489 @@ +#include "ctn91xx.h" +#include "ctn91xx_util.h" +#include "ctn91xx_driver.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_reset.h" + +#include + +#define MPEG_VERIFY 0 + +#if CHECK_NOTIFY_CC +#define CHECK_NOTIFY_CC_ACCUM_AMOUNT 100 +#define CHECK_NOTIFY_SYNC_ACCUM_AMOUNT 1000 + +void notify_cc_check( ctn91xx_dev_t* dev, int tuner_index, uint8_t* buffer, uint32_t length ) +{ + int pkts = length/188; + int i = 0; + for (i = 0; i < pkts; i++) + { + uint8_t* pkt = buffer + (188 * i); + uint16_t pid = ((pkt[1] & 0x1f) << 8) | (pkt[2] & 0xff); + uint8_t continuity_counter; + uint16_t expected_cc; + uint8_t has_adaptation = (((pkt[3] >> 5) & 0x1)); + uint8_t adaptation_length = pkt[4]; + uint8_t adaptation_flags = pkt[5]; + + if( pid >= 0x1fff ) { + continue; + } + + if( pkt[0] != 0x47 ) { + dev->pid_cc_notify[tuner_index][pid].out_of_sync++; + if( dev->pid_cc_notify[tuner_index][pid].out_of_sync % CHECK_NOTIFY_SYNC_ACCUM_AMOUNT == 0 ) { + if( printk_ratelimit() ) { + WARNING("NOTIFY board %d tuner %d pid %#x out_of_sync %d", + dev->board_number, + tuner_index, + pid, + dev->pid_cc_notify[tuner_index][pid].out_of_sync); + } + } + continue; + } + + continuity_counter = (pkt[3] & 0x0f); + + expected_cc = (dev->pid_cc_notify[tuner_index][pid].last_cc + 1) & 0xf; + if (expected_cc != continuity_counter + && continuity_counter != dev->pid_cc_notify[tuner_index][pid].last_cc + && (!has_adaptation || adaptation_length == 0 || !(adaptation_flags & 0x80))) //discontinuity_indicator + { + dev->pid_cc_notify[tuner_index][pid].discontinuities++; + dev->stats.discont_count[tuner_index]++; + + /* + if(dev->pid_cc_notify[tuner_index][pid].discontinuities % CHECK_NOTIFY_CC_ACCUM_AMOUNT == 0 || dev->pid_cc_notify[tuner_index][pid].discontinuities == 2) { + if( printk_ratelimit() ) { + WARNING( "NOTIFY board %d tuner %d pid %#x discontinuities %d count %d expected_cc %#01x cc %#01x", + dev->board_number, + tuner_index, + pid, + dev->pid_cc_notify[tuner_index][pid].discontinuities, + dev->pid_cc_notify[tuner_index][pid].count, + expected_cc, + continuity_counter); + } + }*/ + } + dev->pid_cc_notify[tuner_index][pid].count++; + dev->pid_cc_notify[tuner_index][pid].last_cc = continuity_counter; + } +} +#endif + +void ctn91xx_reinit_mpeg_registers( ctn91xx_dev_t* dev ) +{ +#if HAS_MPEG_DMA + mpeg_reset( dev ); +#endif +} + +void ctn91xx_vbuffer_print_internal(ctn91xx_dev_t* dev, vbuffer_t* vbuffer); + + +void verify_buffer_state(ctn91xx_dev_t* dev, vbuffer_t* vbuffer, int who_called ) +{ +#if !MPEG_VERIFY + return; +#else + uint32_t j; + uint32_t num_bank_pages = default_num_pages_per_bank( vbuffer->tuner_index ); + static uint32_t cnt=0; + + uint32_t notify_abs = ( vbuffer->notify_idx < vbuffer->write_idx ? vbuffer->npages + vbuffer->notify_idx : vbuffer->notify_idx ); + + static uint32_t printed = 0; + if(printed) return; + cnt++; + + if( !( notify_abs >= vbuffer->write_idx + && ( notify_abs <= ( vbuffer->write_idx + num_bank_pages ) ) ) ) { + printed = 1; + WARNING("verify1 failed (%d, %d): notify_index=%d notify_abs=%d write_index=%d num_bank_pages=%d", who_called, cnt, vbuffer->notify_idx, notify_abs, vbuffer->write_idx, num_bank_pages); + } + + for (j = 0; j < vbuffer->npages; j++) { + + uint32_t j_abs = ( j < vbuffer->write_idx ? vbuffer->npages + j : j); + if(vbuffer->lock_cnt[j]) { + if(!(j_abs >= notify_abs && j_abs < vbuffer->write_idx + 2*num_bank_pages)) { + printed = 1; + WARNING("verify3 failed (%d, %d): j=%d j_abs=%d notify_index=%d notify_abs=%d write_index=%d", who_called, cnt, j, j_abs, vbuffer->notify_idx, notify_abs, vbuffer->write_idx); + } + } + + if(vbuffer->dropped[j]) { + if(!(j_abs >= vbuffer->write_idx && j_abs < vbuffer->write_idx + num_bank_pages)) { + printed = 1; + WARNING("verify4 failed (%d, %d): j=%d j_abs=%d notify_index=%d notify_abs=%d write_index=%d", who_called, cnt, j, j_abs, vbuffer->notify_idx, notify_abs, vbuffer->write_idx); + } + } + } + + if( printed ) { + ctn91xx_vbuffer_print_internal( dev, vbuffer ); + } +#endif +} + + +#if HAS_MPEG_DMA + +int is_filter_stream(int stream_index) { + return (stream_index + CTN91XX_ORIGIN_TUNER0 >= CTN91XX_ORIGIN_FILTER0); +} + +int pages_per_mpeg_device(int stream_index) { + switch(stream_index) { + case (0)...(5): + return MPEG_BUFFER_NPAGES; + case (6)...(11): + default: + return FILTER_BUFFER_NPAGES; + } +} + +int num_pages_per_bank(ctn91xx_dev_t* dev, int stream_index) { + + return default_num_pages_per_bank(stream_index); +} + +int num_pages_per_notify(ctn91xx_dev_t* dev, int stream_index) { + + if(is_filter_stream(stream_index)) { + return ctn91xx_read8(dev->mpeg_filter_dma_base, + MPEG_FILTER_DMA_PAGES_PER_NOTIFY_BASE + ((stream_index+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*8)); + } else { + return ctn91xx_read8(dev->mpeg_dma_base, MPEG_DMA_PAGES_PER_NOTIFY_BASE + (stream_index*8)); + } +} + +int mpeg_bytes_per_page_from_stream(ctn91xx_dev_t* dev, int stream_index) { + + if(is_filter_stream(stream_index)) { + return ctn91xx_read16(dev->mpeg_filter_dma_base, + MPEG_FILTER_DMA_BYTES_PER_PAGE_BASE + ((stream_index+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*2)); + } else { + return ctn91xx_read16(dev->mpeg_dma_base, MPEG_DMA_BYTES_PER_PAGE_BASE + (stream_index*2)); + } +} + +int default_num_pages_per_bank(int stream_index) { + switch(stream_index) { + case (0)...(5): +#if USE_MPEG_NOTIFY +#if USE_LEON + return 2; +#else + return 32; +#endif +#else + return 8; +#endif + case (6)...(11): +#if USE_MPEG_NOTIFY +#if USE_LEON + return 4; +#else + return 8; +#endif +#else + return 1; +#endif + default: + return 0; + } +} + +int default_num_pages_per_notify(int stream_index) { + switch(stream_index) { + case (0)...(5): +#if USE_LEON + return 2; +#else + return 8; +#endif + case (6)...(11): +#if USE_MPEG_NOTIFY && USE_LEON + return 1; +#else + return 1; +#endif + default: + return 0; + } +} + +int default_mpeg_bytes_per_page_from_stream(int stream_index) +{ + switch(stream_index) { + case (0)...(5): + return 3948; + case (6)...(11): + return 188; + default: + return 0; + } +} + +int get_addr_list_offset(ctn91xx_dev_t* dev, int stream_index, int bank) +{ + int ret = 0; + switch(stream_index) { + case (0)...(5): + ret = 128 + (stream_index * 128 * 4); + if(bank == 1) { + ret += 64*4; + } + break; + case (6)...(11): + ret = 512 + 64*(stream_index + CTN91XX_ORIGIN_TUNER0 - CTN91XX_ORIGIN_FILTER0); + if(bank == 1) { + ret += 8*4; + } + break; + } + return ret; +} + +void mpeg_vbuffer_from_tuner_index(uint8_t tuner_index, ctn91xx_dev_t* dev, + vbuffer_t** vbuffer) +{ + if( !vbuffer ) { + ERROR("invalid arg"); + return; + } + + if( tuner_index > NUM_MPEG_DEVICES ) { + *vbuffer = NULL; + return; + } + + *vbuffer = &dev->mpeg_buffer[tuner_index]; +} + + +void mpeg_vbuffer_from_tuner(uint8_t tuner, ctn91xx_dev_t* dev, + vbuffer_t** vbuffer) +{ + if( !vbuffer ) { + ERROR("invalid arg"); + return; + } + + if( tuner > CTN91XX_ORIGIN_TUNER0+CTN91XX_ORIGIN_FILTER5) { + *vbuffer = NULL; + return; + } + + *vbuffer = &dev->mpeg_buffer[tuner - CTN91XX_ORIGIN_TUNER0]; +} + +void mpeg_user_cnt(uint8_t minor_num, ctn91xx_dev_t* dev, int** user_cnt, uint8_t* origin) +{ + uint8_t device_number = CETON_MINOR_DEVICE_NUMBER( minor_num ); + int * local_user_cnt = 0; + uint8_t local_origin = 0; + + if (!dev || (!user_cnt && !origin)) { + ERROR("invalid arg"); + return; + } + + if( device_number < 1 || device_number > ( NUM_MPEG_DEVICES + 1 ) ) { + ERROR("invalid arg"); + return; + } + + device_number--; + local_origin = device_number + CTN91XX_ORIGIN_TUNER0; + local_user_cnt = &dev->mpeg_user_cnt[device_number]; + + if (origin) { + *origin = local_origin; + } + if (user_cnt) { + *user_cnt = local_user_cnt; + } +} + + +void mpeg_vbuffer_from_minor(uint8_t minor_num, ctn91xx_dev_t* dev, + vbuffer_t** vbuffer) +{ + uint8_t device_number = CETON_MINOR_DEVICE_NUMBER( minor_num ); + + if (!dev || !vbuffer) { + ERROR("invalid arg"); + return; + } + + if( device_number < 1 || device_number > ( NUM_MPEG_DEVICES + 1 ) ) { + ERROR("invalid arg"); + return; + } + + device_number--; + + *vbuffer = &dev->mpeg_buffer[device_number]; +} + +int* mpeg_user_cnt_from_tuner(ctn91xx_dev_t* dev, uint8_t origin) +{ + int* ret = NULL; + + if( origin > CTN91XX_ORIGIN_FILTER5 ) { + ERROR("invalid arg"); + return NULL; + } + + ret = &dev->mpeg_user_cnt[origin - CTN91XX_ORIGIN_TUNER0]; + + if( ret && ( *ret > 1 || *ret < 0 ) ) { + ERROR("mpeg user cnt invalid tuner: %d cnt %d", origin, *ret); + } + + return ret; +} + +int ctn91xx_mpeg_dma_stats( ctn91xx_dev_t* dev, unsigned long arg ) +{ + vbuffer_t* vbuffer = NULL; + dma_stats_t stats; + if( copy_from_user( &stats, (dma_stats_t*)arg, sizeof( dma_stats_t ) ) ) { + return -EIO; + } + + mpeg_vbuffer_from_tuner( stats.tuner, dev, &vbuffer ); + + if( !vbuffer ) { + return -EINVAL; + } + + stats.packet_count = vbuffer->packet_count; + + if( copy_to_user( (dma_stats_t*)arg, &stats, sizeof( dma_stats_t ) ) ) { + return -EIO; + } + return 0; +} + +static int ctn91xx_mpeg_open(struct inode* inode, struct file* filp) +{ + ctn91xx_dev_t* dev = NULL; + int ret = 0; + uint8_t tuner; + int* user_cnt = 0; + int minor; + vbuffer_t* vbuffer = NULL; + + dev = ctn91xx_lookup_dev_from_file(inode, filp); + minor = iminor(inode); + + mpeg_user_cnt(minor, dev, &user_cnt, &tuner); + + if(!user_cnt) { + ctn91xx_cleanup_dev_from_file(inode, filp); + return -EIO; + } + + mutex_lock(&dev->fd_mutex); + if(!(*user_cnt)) { + (*user_cnt)++; + } else { + ctn91xx_cleanup_dev_from_file(inode, filp); + ret = -EBUSY; + goto cleanup; + } + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + { + spin_lock_w_flags(&vbuffer->lock); + vbuffer->read_idx = vbuffer->notify_idx; + spin_unlock_w_flags(&vbuffer->lock); + } + +cleanup: + mutex_unlock(&dev->fd_mutex); + return ret; +} + +static int ctn91xx_mpeg_release(struct inode* inode, struct file* filp) +{ + ctn91xx_dev_t* dev = NULL; + uint8_t tuner; + int* user_cnt; + int minor; + + + dev = ctn91xx_lookup_dev_from_file(inode, filp); + minor = iminor(inode); + + mpeg_user_cnt(iminor(inode), dev, &user_cnt, &tuner); + + if(!user_cnt) { + ctn91xx_cleanup_dev_from_file(inode, filp); + return -EIO; + } + + mutex_lock(&dev->fd_mutex); + if((*user_cnt)) { + (*user_cnt)--; + } + mutex_unlock(&dev->fd_mutex); + ctn91xx_cleanup_dev_from_file(inode, filp); + return 0; +} + +static int ctn91xx_mpeg_mmap(struct file* filp, struct vm_area_struct* vma) +{ + int ret = 0; + ctn91xx_dev_t* dev = NULL; + int minor; + vbuffer_t* vbuffer = NULL; + long length = vma->vm_end - vma->vm_start; + unsigned long start = vma->vm_start; + int page_idx = 0; + + dev = ctn91xx_lookup_dev_from_file(NULL, filp); + minor = ctn91xx_lookup_minor_from_file(NULL, filp); + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + if( !vbuffer ) { + return -EINVAL; + } + + if ( ( vma->vm_end - vma->vm_start ) > ( vbuffer->npages * PAGE_SIZE ) ) { + ERROR( "requested region size is too big" ); + return -1; + } + + while( length > 0 ) { + struct page* p = vbuffer->pages[page_idx++]; + if( ( ret = vm_insert_page( vma, start, p ) ) < 0 ) { + return ret; + } + start += PAGE_SIZE; + length -= PAGE_SIZE; + } + + return ret; +} + + +//vbuffer->lock needs to be locked when calling this +int mpeg_data_ready(vbuffer_t* vbuffer) +{ + return (vbuffer->lock_cnt[vbuffer->read_idx] == 0) && (vbuffer->remaining[vbuffer->read_idx]); +} + +int mpeg_data_ready_at_index( vbuffer_t* vbuffer, int idx ) +{ + return ( vbuffer->lock_cnt[idx] == 0 ) && ( vbuffer->remaining[idx] ); +} + +#define ACCUM_AMOUNT 100 + +static unsigned int ctn91xx_mpeg_poll(struct file* filp, struct poll_table_struct* wait) +{ + ctn91xx_dev_t* dev = NULL; + int minor; + uint8_t tuner; + int tuner_index; + int* user_cnt; + uint32_t mask = 0; + vbuffer_t* vbuffer = 0; + + dev = ctn91xx_lookup_dev_from_file(NULL, filp); + minor = ctn91xx_lookup_minor_from_file(NULL, filp); + + mpeg_user_cnt((uint8_t)minor, dev, &user_cnt, &tuner); + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + tuner_index = tuner - CTN91XX_ORIGIN_TUNER0; + + if(vbuffer) { + spin_lock_w_flags(&vbuffer->lock); + + if(mpeg_data_ready(vbuffer) && *user_cnt) { + mask = POLLIN | POLLRDNORM; + } + + spin_unlock_w_flags(&vbuffer->lock); + + } else { + printk("vbuffer == NULL\n"); + } + + //Note: this debug stuff is not synced + if(((dev->stats.drop_count[tuner_index] - 1) % (ACCUM_AMOUNT/10)) == 0) { + if( printk_ratelimit() ) { + printk("(in poll): board %d drop_count %d tuner_index %d mask %d\n", + dev->board_number, dev->stats.drop_count[tuner_index], tuner_index, mask); + } + dev->stats.drop_count[tuner_index]++; + } + + poll_wait(filp, &dev->mpeg_wait_queues[tuner_index], wait); + + return mask; +} + +ssize_t ctn91xx_mpeg_read_common( ctn91xx_dev_t* dev, vbuffer_t* vbuffer, char __user * data, size_t count, int tuner_index, int nonblocking ); + +ssize_t ctn91xx_mpeg_read(struct file* filp, char __user * data, size_t count, loff_t* offset) +{ + int minor; + ctn91xx_dev_t* dev = NULL; + vbuffer_t* vbuffer = NULL; + int* user_cnt; + uint8_t tuner; + int tuner_index; + + dev = ctn91xx_lookup_dev_from_file(NULL, filp); + minor = ctn91xx_lookup_minor_from_file(NULL, filp); + + mpeg_user_cnt((uint8_t)minor, dev, &user_cnt, &tuner); + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + tuner_index = tuner - CTN91XX_ORIGIN_TUNER0; + + return ctn91xx_mpeg_read_common( dev, vbuffer, data, count, tuner_index, filp->f_flags & O_NONBLOCK ); +} + + +ssize_t ctn91xx_mpeg_read_common( ctn91xx_dev_t* dev, vbuffer_t* vbuffer, char __user * data, + size_t count, int tuner_index, int nonblocking) +{ + DECLARE_WAITQUEUE(wait, current); + ssize_t retval = 0; + int done = 0; + int error = 0; + size_t saved_cnt = count; + spinlock_t* lock = 0; + + if(!vbuffer) { + ERROR("bad vbuffer"); + return -EIO; + } + + //Note: this debug stuff is not synced + if(((dev->stats.drop_count[tuner_index] - 1) % (ACCUM_AMOUNT/10)) == 0) { + if( printk_ratelimit() ) { + printk("(in read): drop_count %d tuner_index %d\n", + dev->stats.drop_count[tuner_index], tuner_index); + } + dev->stats.drop_count[tuner_index]++; + } + + lock = &vbuffer->lock; + + add_wait_queue(&dev->mpeg_wait_queues[tuner_index], &wait); + + do { + int should_break = 0; +restart_wait: + should_break = 0; + + set_current_state(TASK_INTERRUPTIBLE); + + //Check for data + { + spin_lock_w_flags(lock); + + if(mpeg_data_ready(vbuffer)) { + should_break = 1; + } + + spin_unlock_w_flags(lock); + } + + if(should_break) { + break; + } + + if(nonblocking) { + retval = -EAGAIN; + goto out; + } + + if(!dev->mpeg_user_cnt[vbuffer->tuner_index]) { + retval = -EAGAIN; + goto out; + } + + if(signal_pending(current)) { + retval = -ERESTARTSYS; + goto out; + } + + schedule(); + } while(1); + + { + spin_lock_w_flags(lock); + + while(!done) { + + if(!mpeg_data_ready(vbuffer) || !dev->mpeg_user_cnt[vbuffer->tuner_index]) { + //go back and wait for the rest of my data + spin_unlock_w_flags(lock); + + if (nonblocking) { + //We already used our data, none is left so just let them know how much they got + goto out; + } else { + goto restart_wait; + } + } + + if(vbuffer->remaining[vbuffer->read_idx] >= count) { + size_t offset = vbuffer->sizes[vbuffer->read_idx] - vbuffer->remaining[vbuffer->read_idx]; + + spin_unlock_w_flags(lock); + + if(copy_to_user(&(data[saved_cnt - count]), + &(vbuffer->buffers[vbuffer->read_idx][offset]), count)) { + error = 1; + } + + spin_relock_w_flags(lock); + + vbuffer->remaining[vbuffer->read_idx] -= count; + retval += count; + done = 1; + + } else if(vbuffer->remaining[vbuffer->read_idx] > 0) { //remaining < count + size_t offset = vbuffer->sizes[vbuffer->read_idx] - vbuffer->remaining[vbuffer->read_idx]; + + spin_unlock_w_flags(lock); + + if(copy_to_user(&(data[saved_cnt - count]), + &(vbuffer->buffers[vbuffer->read_idx][offset]), vbuffer->remaining[vbuffer->read_idx])) { + error = 1; + done = 1; + } + + spin_relock_w_flags(lock); + + retval += vbuffer->remaining[vbuffer->read_idx]; + count -= vbuffer->remaining[vbuffer->read_idx]; + vbuffer->remaining[vbuffer->read_idx] = 0; + } else { + ERROR("mpeg read, bad state"); + error = 1; + spin_unlock_w_flags(lock); + + goto out; + } + + if(!vbuffer->remaining[vbuffer->read_idx]) { + vbuffer->read_idx = WRAPPED_PAGE_INDEX( vbuffer, vbuffer->read_idx + 1 ); + } + } + + spin_unlock_w_flags(lock); + } + +out: + set_current_state(TASK_RUNNING); + remove_wait_queue(&dev->mpeg_wait_queues[tuner_index], &wait); + + if(error) { + retval = -EIO; + } + + return retval; +} + +int ctn91xx_ioctl_mpeg_read_setup(ctn91xx_dev_t* dev, int minor, unsigned long arg) +{ + vbuffer_t* vbuffer = NULL; + mpeg_stream_setup_t setup; + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + if( !vbuffer ) { + ERROR("invalid vbuffer"); + return -EINVAL; + } + + { + spin_lock_w_flags( &vbuffer->lock ); + setup.read_index = vbuffer->read_idx; + spin_unlock_w_flags( &vbuffer->lock ); + } + + setup.npages = vbuffer->npages; + setup.bytes_per_page = default_mpeg_bytes_per_page_from_stream( vbuffer->tuner_index ); + + if( copy_to_user( (uint8_t*)arg, &setup, sizeof( mpeg_stream_setup_t ) ) ) { + ERROR("invalid user ptr"); + return -EIO; + } + + return 0; +} + +int ctn91xx_ioctl_mpeg_read_complete(ctn91xx_dev_t* dev, int minor, unsigned long arg); + +int ctn91xx_ioctl_mpeg_read_start(ctn91xx_dev_t* dev, int minor, unsigned long arg) +{ + ssize_t retval = 0; + int done = 0; + int error = 0; + vbuffer_t* vbuffer = NULL; + mpeg_read_t mpeg_data; + size_t count; + spinlock_t* lock = 0; + + if( copy_from_user( &mpeg_data, (mpeg_read_t*) arg, sizeof(mpeg_read_t) ) ) { + return -EINVAL; + } + + if( mpeg_data.amount_last_read ) { + ERROR("unless USE_AMOUNT_LAST_READ is set, you shouldn't get here"); + if( ctn91xx_ioctl_mpeg_read_complete( dev, minor, arg ) < 0 ) { + return -EINVAL; + } + } + + count = mpeg_data.count; + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + if( !vbuffer ) { + return -EINVAL; + } + + lock = &vbuffer->lock; + + { + int temp_read_index; + spin_lock_w_flags( lock ); + temp_read_index = vbuffer->read_idx; + + while( !done ) { + int cur_remaining = vbuffer->remaining[temp_read_index]; + + if( !mpeg_data_ready_at_index( vbuffer, temp_read_index ) ) { + spin_unlock_w_flags( lock ); + goto out; + } + + if( cur_remaining >= count ) { + cur_remaining -= count; + retval += count; + done = 1; + } else if( cur_remaining > 0 ) { //remaining < count + retval += cur_remaining; + count -= cur_remaining; + cur_remaining = 0; + } else { + ERROR("mpeg read, bad state retval %zd count %zd cur_remaining %d", + retval, count, cur_remaining); + error = 1; + spin_unlock_w_flags(lock); + goto out; + } + + if( !cur_remaining ) { + temp_read_index = WRAPPED_PAGE_INDEX(vbuffer, temp_read_index + 1 ); + } + } + + spin_unlock_w_flags( lock ); + } + +out: + if( error ) { + retval = -EIO; + } + + return retval; +} + + + +int ctn91xx_ioctl_mpeg_read_complete(ctn91xx_dev_t* dev, int minor, unsigned long arg) +{ + int done = 0; + int error = 0; + size_t count; + int new_read_index = 0; + vbuffer_t* vbuffer = NULL; + mpeg_read_t mpeg_data; + spinlock_t* lock = 0; + + if( copy_from_user( &mpeg_data, (mpeg_read_t*) arg, sizeof(mpeg_read_t) ) ) { + return -EINVAL; + } + + count = mpeg_data.amount_last_read; + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + if( !vbuffer ) { + return -EINVAL; + } + + new_read_index = vbuffer->read_idx; + lock = &vbuffer->lock; + + { + spin_lock_w_flags( lock ); + + while( !done ) { + + if( !mpeg_data_ready( vbuffer ) || !dev->mpeg_user_cnt[vbuffer->tuner_index]) { + spin_unlock_w_flags( lock ); + goto out; + } + + if( vbuffer->remaining[vbuffer->read_idx] >= count ) { + vbuffer->remaining[vbuffer->read_idx] -= count; + done = 1; + } else if( vbuffer->remaining[vbuffer->read_idx] > 0 ) { //remaining < count + count -= vbuffer->remaining[vbuffer->read_idx]; + vbuffer->remaining[vbuffer->read_idx] = 0; + } else { + ERROR("mpeg read, bad state"); + error = 1; + spin_unlock_w_flags( lock ); + goto out; + } + + if( !vbuffer->remaining[vbuffer->read_idx] ) { + vbuffer->read_idx = new_read_index = WRAPPED_PAGE_INDEX( vbuffer, vbuffer->read_idx + 1 ); + } + } + spin_unlock_w_flags( lock ); + } + +out: + if( error ) { + return -EIO; + } + + return new_read_index; +} + +int ctn91xx_mpeg_send(ctn91xx_dev_t* dev, int minor, unsigned long arg) +{ + int ret = 0; + int done = 0; + vbuffer_t* vbuffer = NULL; + mpeg_send_t send; + struct file* out_file = NULL; + spinlock_t* lock = 0; + ssize_t written = 0; + size_t count; + + mpeg_vbuffer_from_minor((uint8_t)minor, dev, &vbuffer); + + if( !vbuffer ) { + return -EINVAL; + } + + if( copy_from_user( &send, (mpeg_send_t*) arg, sizeof(mpeg_send_t) ) ) { + return -EINVAL; + } + + count = send.count; + + if( copy_from_user( vbuffer->header_buffer, send.header, send.header_length ) ) { + ret =-EINVAL; + goto out; + } + + out_file = fget( send.out_fd ); + if( !out_file ) { + goto out; + } + + if( !out_file->f_op || !out_file->f_op->sendpage ) { + goto fput_out; + } + + written = out_file->f_op->sendpage( + out_file, + vbuffer->header_buffer_page, + 0, send.header_length, + &out_file->f_pos, 1 /* more */ ); + + lock = &vbuffer->lock; + { + spin_lock_w_flags( lock ); + + while( !done ) { + size_t offset = vbuffer->sizes[vbuffer->read_idx] - vbuffer->remaining[vbuffer->read_idx]; + + if( !mpeg_data_ready( vbuffer ) || !dev->mpeg_user_cnt[vbuffer->tuner_index]) { + spin_unlock_w_flags( lock ); + out_file->f_op->sendpage( + out_file, + NULL, + 0, + 0, + &out_file->f_pos, 0 /* no-more */ ); + goto fput_out; + } + + if( vbuffer->remaining[vbuffer->read_idx] >= count ) { + + spin_unlock_w_flags( lock ); + written = out_file->f_op->sendpage( + out_file, + vbuffer->pages[vbuffer->read_idx], + offset, + count, + &out_file->f_pos, 0 /* no-more */ ); + + if( written < 0 ) { + ERROR("sendpage, written %zd", written); + goto fput_out; + } + + spin_relock_w_flags( lock ); + vbuffer->remaining[vbuffer->read_idx] -= count; + ret += count; + done = 1; + + } else if( vbuffer->remaining[vbuffer->read_idx] > 0 ) { //remaining < count + + spin_unlock_w_flags( lock ); + written = out_file->f_op->sendpage( + out_file, + vbuffer->pages[vbuffer->read_idx], + offset, + vbuffer->remaining[vbuffer->read_idx], + &out_file->f_pos, 1 /* more */ ); + + if( written < 0 ) { + ERROR("sendpage, written %zd", written); + goto fput_out; + } + + spin_relock_w_flags( lock ); + + ret += vbuffer->remaining[vbuffer->read_idx]; + count -= vbuffer->remaining[vbuffer->read_idx]; + vbuffer->remaining[vbuffer->read_idx] = 0; + } else { + ERROR("mpeg send, bad state"); + ret = -EINVAL; + spin_unlock_w_flags( lock ); + goto fput_out; + } + + if( !vbuffer->remaining[vbuffer->read_idx] ) { + vbuffer->read_idx = WRAPPED_PAGE_INDEX( vbuffer, vbuffer->read_idx + 1 ); + } + } + + spin_unlock_w_flags( lock ); + } + +fput_out: + fput( out_file ); +out: + return ret; +} + +ssize_t ctn91xx_mpeg_kernel_read( vbuffer_t* vbuffer, char* data, size_t count ) +{ + ctn91xx_dev_t* dev = vbuffer->dev; + ssize_t retval = 0; + int done = 0; + size_t saved_cnt = count; + + while(!done) { + size_t offset = vbuffer->sizes[vbuffer->read_idx] - vbuffer->remaining[vbuffer->read_idx]; + + if(!mpeg_data_ready(vbuffer) || !dev->mpeg_user_cnt[vbuffer->tuner_index]) { + goto out; + } + + if(vbuffer->remaining[vbuffer->read_idx] >= count) { + + memcpy( &data[saved_cnt - count], &vbuffer->buffers[vbuffer->read_idx][offset], count ); + + vbuffer->remaining[vbuffer->read_idx] -= count; + retval += count; + done = 1; + + } else if(vbuffer->remaining[vbuffer->read_idx] > 0) { //remaining < count + + memcpy( &data[saved_cnt - count], &vbuffer->buffers[vbuffer->read_idx][offset], vbuffer->remaining[vbuffer->read_idx] ); + + retval += vbuffer->remaining[vbuffer->read_idx]; + count -= vbuffer->remaining[vbuffer->read_idx]; + vbuffer->remaining[vbuffer->read_idx] = 0; + } else { + if( printk_ratelimit() ) { + ERROR("mpeg read, bad state"); + } + retval = 0; + goto out; + } + + if(!vbuffer->remaining[vbuffer->read_idx]) { + vbuffer->read_idx = WRAPPED_PAGE_INDEX( vbuffer, vbuffer->read_idx + 1 ); + } + } + +out: + return retval; +} + + + +static long ctn91xx_mpeg_ioctl(struct file *filp, uint cmd, ulong arg) +{ + int ret = 0; + ctn91xx_dev_t* dev = NULL; + + + dev = ctn91xx_lookup_dev_from_file( NULL, filp ); + + switch(cmd) + { + case CTN91XX_IOCTL_MPEG_READ_START: + { + int minor = ctn91xx_lookup_minor_from_file(NULL, filp); + ret = ctn91xx_ioctl_mpeg_read_start(dev, minor, arg); + break; + } + case CTN91XX_IOCTL_MPEG_READ_COMPLETE: + { + int minor = ctn91xx_lookup_minor_from_file(NULL, filp); + ret = ctn91xx_ioctl_mpeg_read_complete(dev, minor, arg); + break; + } + case CTN91XX_IOCTL_MPEG_READ_SETUP: + { + int minor = ctn91xx_lookup_minor_from_file(NULL, filp); + ret = ctn91xx_ioctl_mpeg_read_setup(dev, minor, arg); + break; + } + case CTN91XX_IOCTL_MPEG_SEND: + { + int minor = ctn91xx_lookup_minor_from_file(NULL, filp); + ret = ctn91xx_mpeg_send(dev, minor, arg); + break; + } + case TCGETS: + //fopen calls this on us...it's the "is_a_tty" call + //just tell it to go away + ret = -EINVAL; + break; + default: + ret = -EINVAL; + WARNING("Unknown ioctl on ctn91xx mpeg device (%d).", cmd); + break; + } + + return ret; +} + +static long ctn91xx_mpeg_compat_ioctl(struct file *filp, uint cmd, ulong arg) +{ + return ctn91xx_mpeg_ioctl( filp, cmd, arg ); +} + +static struct file_operations ctn91xx_mpeg_fops = { + .owner = THIS_MODULE, + .open = ctn91xx_mpeg_open, + .release = ctn91xx_mpeg_release, + .poll = ctn91xx_mpeg_poll, + .mmap = ctn91xx_mpeg_mmap, + .llseek = no_llseek, + .read = ctn91xx_mpeg_read, + .unlocked_ioctl = ctn91xx_mpeg_ioctl, + .compat_ioctl = ctn91xx_mpeg_compat_ioctl, +}; + +int ctn91xx_init_mpeg( ctn91xx_dev_t* dev ) +{ + int i; + cdev_init( &dev->mpeg_cdev, &ctn91xx_mpeg_fops ); + dev->mpeg_cdev.owner = THIS_MODULE; + cdev_add( &dev->mpeg_cdev, MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, MPEG_DEVICE_NUMBER ) ), NUM_MPEG_DEVICES ); + + for( i=0; iclass, NULL, MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, 1 + i ) ), "ctn91xx_mpeg%d_%d", dev->board_number, i ); + device_create( dev->class, NULL, MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, 7 + i ) ), "ctn91xx_filter%d_%d", dev->board_number, i ); +#else + device_create( dev->class, NULL, MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, 1 + i ) ), dev, "ctn91xx_mpeg%d_%d", dev->board_number, i ); + device_create( dev->class, NULL, MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, 7 + i ) ), dev, "ctn91xx_filter%d_%d", dev->board_number, i ); +#endif + } + return 0; +} + +void ctn91xx_deinit_mpeg( ctn91xx_dev_t* dev ) +{ + int i; + for( i=0; i<(NUM_MPEG_DEVICES); i++ ) { + device_destroy( dev->class, MKDEV( CETON_MAJOR, CETON_MINOR( dev->board_number, 1 + i ) ) ); + } + cdev_del( &dev->mpeg_cdev ); +} + + +int check_drop(vbuffer_t* vbuffer, ctn91xx_dev_t* dev, int new_map_page_index, int num_pages) +{ + int end_of_new_map_index = (new_map_page_index + num_pages) % vbuffer->npages; + + if(!dev->mpeg_user_cnt[vbuffer->tuner_index]) { + return 0; + } + + // Checks to see if there is a drop + // -If new_map_page_index (which is the new write page) is in the range + // that is currently being read from, then we will need to drop data. + // -The following code checks this and also takes into account the + // wrapping in the vbuffer + // + if(end_of_new_map_index < new_map_page_index) { + //it wrapped + return vbuffer->read_idx >= new_map_page_index || vbuffer->read_idx < end_of_new_map_index; + } else { + return vbuffer->read_idx >= new_map_page_index && vbuffer->read_idx < end_of_new_map_index; + } +} + +void ctn91xx_vbuffer_print_internal(ctn91xx_dev_t* dev, vbuffer_t* vbuffer) +{ + int last_was_skipped = 0; + int j = 0; + for (j = 0; j < vbuffer->npages; j++) { + + if ((j != 0) + && (j != vbuffer->npages - 1) + && vbuffer->lock_cnt[j - 1] == vbuffer->lock_cnt[j] + && vbuffer->lock_cnt[j] == vbuffer->lock_cnt[j + 1] + && vbuffer->remaining[j - 1] == vbuffer->remaining[j] + && vbuffer->remaining[j] == vbuffer->remaining[j + 1] + && j != vbuffer->read_idx + && j != vbuffer->write_idx + && j != vbuffer->notify_idx) + { + //Skip this entry + if (!last_was_skipped) { + INFO("..."); + } + last_was_skipped = 1; + } else { + last_was_skipped = 0; + + INFO( "vbuffer: %03d\t%d\t%d\t%c%c%c", j, vbuffer->lock_cnt[j], vbuffer->remaining[j], + j == vbuffer->read_idx ? 'r' : ' ', + j == vbuffer->write_idx ? 'w' : ' ', + j == vbuffer->notify_idx ? 'n' : ' '); + } + + + } +} + +void ctn91xx_mpeg_unmap_page(ctn91xx_dev_t* dev, vbuffer_t* vbuffer, + int page_idx, uint16_t bytes_per_page, uint8_t tuner_index) +{ + + if(vbuffer->lock_cnt[page_idx] == 0 && vbuffer->dropped[page_idx]) { + + vbuffer->dropped[page_idx] = 0; + return; + + } else if(vbuffer->lock_cnt[page_idx] == 0) { + static int first_time = 1; + if(first_time) { + ERROR("lock_cnt == 0 before unlock for tuner_index = %d", tuner_index); + ctn91xx_vbuffer_print_internal(dev, vbuffer); + first_time = 0; + } + vbuffer->read_idx = vbuffer->notify_idx; + return; + } + + vbuffer->lock_cnt[page_idx]--; + if(vbuffer->lock_cnt[page_idx] == 0) { + vbuffer->remaining[page_idx] = bytes_per_page; + vbuffer->sizes[page_idx] = bytes_per_page; + } +} + + +void ctn91xx_print_vbuffer(ctn91xx_dev_t* dev, vbuffer_t* vbuffer, int tuner_index) +{ + spin_lock_w_flags(&vbuffer->lock); + ctn91xx_vbuffer_print_internal(dev, vbuffer); + spin_unlock_w_flags(&vbuffer->lock); +} + +int ctn91xx_print_vbuffer_state(ctn91xx_dev_t* dev, unsigned long arg) +{ + vbuffer_t* vbuffer = NULL; + uint8_t origin = (uint8_t)arg; + int tuner_index = arg - CTN91XX_ORIGIN_TUNER0; + + mpeg_vbuffer_from_tuner(origin, dev, &vbuffer); + + if (!vbuffer) { + ERROR("Bad origin"); + return 0; + } + + INFO("Stream"); + + ctn91xx_print_vbuffer(dev, vbuffer, tuner_index); + + if (tuner_index > CTN91XX_ORIGIN_TUNER5) { + return 0; + } else { + int filter_stream_offset = (CTN91XX_ORIGIN_FILTER0 - CTN91XX_ORIGIN_TUNER0); + tuner_index = arg - CTN91XX_ORIGIN_TUNER0 + filter_stream_offset; + + origin += filter_stream_offset; + vbuffer = NULL; + + mpeg_vbuffer_from_tuner(origin, dev, &vbuffer); + } + + INFO("Accompanying Filter"); + + if (!vbuffer) { + ERROR("Bad origin"); + return 0; + } + + ctn91xx_print_vbuffer(dev, vbuffer, tuner_index); + + return 0; +} + +uint8_t ctn91xx_interrupt_mpeg_notify(ctn91xx_dev_t* dev, uint8_t origin, + uint8_t notify_cnt, uint16_t bytes_per_page) +{ + int i; + vbuffer_t* vbuffer = NULL; + int stream_index = origin - CTN91XX_ORIGIN_TUNER0; + + mpeg_vbuffer_from_tuner(origin, dev, &vbuffer); + + if(!vbuffer) { + ERROR("invalid mpeg origin %02x", origin); + return notify_cnt; + } + + vbuffer->packet_count += ( notify_cnt * bytes_per_page ) / 188; + + { + /** + * This is what happens here: + * -This function is called when the hardware has finished with + * "notify_cnt" worth of pages. + * -So we start at the index of the notify_idx and unlock/unmap + * "notify_cnt" worth of pages + * -This works in both the case when USE_MPEG_NOTIFY is on, and also + * when it is off (in which case this function will be unmapping a + * whole bank at a time) + * + * -The wake_up_interruptible lets any readers know we have data ready to read + */ + int cur_notify_idx = vbuffer->notify_idx; + spin_lock_w_flags(&vbuffer->lock); + + verify_buffer_state(dev, vbuffer, 1); + for(i=0; i < notify_cnt; i++) { + int was_a_drop = vbuffer->dropped[cur_notify_idx]; + + ctn91xx_mpeg_unmap_page(dev, vbuffer, cur_notify_idx, bytes_per_page, stream_index); + if( !was_a_drop ) { + dev->stats.packet_count[vbuffer->tuner_index] += (bytes_per_page / 188); +#if CHECK_NOTIFY_CC + notify_cc_check( dev, vbuffer->tuner_index, vbuffer->buffers[cur_notify_idx], bytes_per_page); +#endif + } + + verify_buffer_state(dev, vbuffer, 2); + + if(vbuffer->lock_cnt[cur_notify_idx] == 0) { + vbuffer->notify_idx = (cur_notify_idx + 1) % vbuffer->npages; + } + + verify_buffer_state(dev, vbuffer, 3); + cur_notify_idx = (cur_notify_idx + 1) % vbuffer->npages; + } + verify_buffer_state(dev, vbuffer, 4); + +#if USE_PCI + if( vbuffer->rtp_state.running ) { + schedule_work( &vbuffer->rtp_state.reader ); + } +#endif + + spin_unlock_w_flags(&vbuffer->lock); + } + + wake_up_interruptible(&dev->mpeg_wait_queues[stream_index]); + + return notify_cnt; +} + +void ctn91xx_interrupt_mpeg_dma(ctn91xx_dev_t* dev, uint8_t origin, int bank) +{ + vbuffer_t* vbuffer = NULL; + int next_write_page_index; + int new_map_page_index; + int num_pages; + int tuner_index; + int drop_detected = 0; + + mpeg_vbuffer_from_tuner(origin, dev, &vbuffer); + + if(!vbuffer) { + ERROR("invalid mpeg origin %02x", origin); + return; + } + + tuner_index = origin - CTN91XX_ORIGIN_TUNER0; + num_pages = num_pages_per_bank(dev, tuner_index); + + /** + * So this is what happens here: + * -On entering this function the write_idx is at the bank + * that we should unmap. + * -The bank after write_idx (next_write_page_index) + * is locked, and the hardware is currently dma'ing into it. + * -The bank after that one (new_map_page_index) is the one + * we need to map and lock now. + * + * After you are done locking the new_map_page_index bank, + * you need to move the write_idx back to point at next_write_page_index + * + * To detect when we are dropping data, we need to check whether the + * read_idx is contained within the range that "new_map_page_index" + * will cover. If it is, we want to make the hardware just reuse + * the current sg list pages, until the reader has moved on. + * + */ + + { +#if !USE_LEON && PRINT_DROP_IN_ISR + static int drop_ctr = 0; +#endif + spin_lock_w_flags(&vbuffer->lock); + + verify_buffer_state(dev, vbuffer, 5); + next_write_page_index = WRAPPED_PAGE_INDEX(vbuffer, vbuffer->write_idx + num_pages); + new_map_page_index = (next_write_page_index + num_pages) % vbuffer->npages; + + if(check_drop(vbuffer, dev, new_map_page_index, num_pages)) { + dev->stats.drop_count[tuner_index]++; + + drop_detected = 1; + + if( dev->stats.drop_count[tuner_index] == 100 ) { + if( printk_ratelimit() ) { + printk("(in isr): drop_count %d tuner_index %d\n", + dev->stats.drop_count[tuner_index], tuner_index); + } + } + } + + if(!drop_detected) { + verify_buffer_state(dev, vbuffer, 6); + //temporarily change write_idx to new_map_page_index + vbuffer->write_idx = new_map_page_index; + + vbuffer_map_bank(dev, vbuffer, num_pages, bank); + + vbuffer->write_idx = next_write_page_index; + verify_buffer_state(dev, vbuffer, 7); + } else { + //in the drop case: + // + // o leave write_idx where it is + // o set notify_idx = write_idx + // o copy the pages currently in the OTHER bank into the bank we + // are updating + // o flag the pages at write_idx "dropped" so unmap doesn't + // freak out when notify is called later with new data + // + + int j; + int addr_list_offset = get_addr_list_offset(dev, vbuffer->tuner_index, bank); + verify_buffer_state(dev, vbuffer, 8); + + vbuffer->notify_idx = vbuffer->write_idx; + + for(j=0; jwrite_idx + j); + int map_page_index = (cur_page_index + num_pages) % vbuffer->npages; + + if(is_filter_stream(vbuffer->tuner_index)) { + ctn91xx_write32(vbuffer->dma_addrs[map_page_index], + dev->mpeg_filter_dma_base, addr_list_offset + (j * 4)); + } else { + ctn91xx_write32(vbuffer->dma_addrs[map_page_index], + dev->mpeg_dma_base, addr_list_offset + (j * 4)); + } + vbuffer->dropped[cur_page_index] = 1; + } + + verify_buffer_state(dev, vbuffer, 9); +#if !USE_LEON && PRINT_DROP_IN_ISR + WARNING("DROP (%d,%d)", vbuffer->tuner_index, drop_ctr++ ); +#endif + } + + spin_unlock_w_flags(&vbuffer->lock); + } +} + +void vbuffer_map_bank(ctn91xx_dev_t* dev, vbuffer_t* vbuffer, int num_pages, int bank) +{ + int j; + int addr_list_offset = get_addr_list_offset(dev, vbuffer->tuner_index, bank); + + for(j=0; jwrite_idx + j); + + if(is_filter_stream(vbuffer->tuner_index)) { + ctn91xx_write32(vbuffer->dma_addrs[write_page_index], + dev->mpeg_filter_dma_base, addr_list_offset + (j * 4)); + } else { + ctn91xx_write32(vbuffer->dma_addrs[write_page_index], + dev->mpeg_dma_base, addr_list_offset + (j * 4)); + } + vbuffer->lock_cnt[write_page_index]++; + } +} + +void ctn91xx_reset_dma_adjustments(ctn91xx_dev_t* dev, uint32_t stream_index) +{ + if(is_filter_stream(stream_index)) { + ctn91xx_write16(default_mpeg_bytes_per_page_from_stream(stream_index), dev->mpeg_filter_dma_base, + MPEG_FILTER_DMA_BYTES_PER_PAGE_BASE + ((stream_index+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*2)); + ctn91xx_write8(default_num_pages_per_notify(stream_index), dev->mpeg_filter_dma_base, + MPEG_FILTER_DMA_PAGES_PER_NOTIFY_BASE + ((stream_index+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*8)); + } else { + ctn91xx_write16(default_mpeg_bytes_per_page_from_stream(stream_index), dev->mpeg_dma_base, MPEG_DMA_BYTES_PER_PAGE_BASE + (stream_index*2)); + ctn91xx_write8(default_num_pages_per_notify(stream_index), dev->mpeg_dma_base, MPEG_DMA_PAGES_PER_NOTIFY_BASE + (stream_index*8)); + } +} + + +#endif diff --git a/ctn91xx_mpeg.h b/ctn91xx_mpeg.h new file mode 100644 index 0000000..ce4626b --- /dev/null +++ b/ctn91xx_mpeg.h @@ -0,0 +1,57 @@ +#ifndef CTN91XX_MPEG_H +#define CTN91XX_MPEG_H + +#include "ctn91xx.h" + + +#if HAS_MPEG_DMA + +#define WRAPPED_PAGE_INDEX(vbuffer, idx) ((idx) % (vbuffer)->npages) +#define PAGE_INDEX_TO_PTR(vbuffer, idx) ((vbuffer)->buffers[(idx)]) + +int is_filter_stream(int stream_index); + +int ctn91xx_init_mpeg(ctn91xx_dev_t* dev); +void ctn91xx_deinit_mpeg(ctn91xx_dev_t* dev); +void ctn91xx_reinit_mpeg_registers( ctn91xx_dev_t* dev ); + +uint8_t ctn91xx_interrupt_mpeg_notify(ctn91xx_dev_t* dev, uint8_t origin, uint8_t notify_cnt, uint16_t bytes_per_page); +void ctn91xx_interrupt_mpeg_dma(ctn91xx_dev_t* dev, uint8_t origin, int bank); + +void ctn91xx_reset_dma_adjustments(ctn91xx_dev_t* dev, uint32_t stream_index); + +int ctn91xx_ioctl_mpeg_read(ctn91xx_dev_t* dev, int minor, unsigned long arg); + +void ctn91xx_mpeg_unmap_page(ctn91xx_dev_t* dev, vbuffer_t* vbuffer, int notify_index, uint16_t bytes_per_page, uint8_t origin); +void vbuffer_map_bank(ctn91xx_dev_t* dev, vbuffer_t* vbuffer, int num_pages, int bank); + +int pages_per_mpeg_device(int stream_index); + +int num_pages_per_bank(ctn91xx_dev_t* dev, int stream_index); +int num_pages_per_notify(ctn91xx_dev_t* dev, int stream_index); +int mpeg_bytes_per_page_from_stream(ctn91xx_dev_t* dev, int stream_index); + +int default_num_pages_per_bank(int stream_index); +int default_num_pages_per_notify(int stream_index); +int default_mpeg_bytes_per_page_from_stream(int stream_index); + +void mpeg_vbuffer_from_tuner(uint8_t tuner, ctn91xx_dev_t* dev, + vbuffer_t** vbuffer); + +void mpeg_vbuffer_from_tuner_index(uint8_t tuner_index, ctn91xx_dev_t* dev, + vbuffer_t** vbuffer); + +int mpeg_data_ready(vbuffer_t* vbuffer); + +ssize_t ctn91xx_mpeg_kernel_read( vbuffer_t* vbuffer, char* data, size_t count ); + + +//Debug + +int ctn91xx_print_vbuffer_state(ctn91xx_dev_t* dev, unsigned long arg); +int ctn91xx_mpeg_dma_stats( ctn91xx_dev_t* dev, unsigned long arg ); + + +#endif + +#endif diff --git a/ctn91xx_net.c b/ctn91xx_net.c new file mode 100644 index 0000000..7b4c4c4 --- /dev/null +++ b/ctn91xx_net.c @@ -0,0 +1,321 @@ +#include "ctn91xx_net.h" +#include "ctn91xx_kal.h" +#include "ctn91xx_util.h" + + +#define PRINT_TRAFFIC 0 + + +static int ctn91xx_net_open( struct net_device *ndev ); +static int ctn91xx_net_start_xmit( struct sk_buff *skb, struct net_device *ndev ); +static int ctn91xx_net_stop( struct net_device *ndev ); +static struct net_device_stats* ctn91xx_net_get_stats( struct net_device *ndev ); +static void ctn91xx_net_tx_timeout( struct net_device* ndev ); +static void ctn91xx_handle_tx( ctn91xx_dev_t* dev, ctn91xx_net_priv_t* priv ); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) +static int ctn91xx_net_set_mac_addr(struct net_device *netdev, void *p); + +static struct net_device_ops ctn91xx_net_device_ops = { + .ndo_open = ctn91xx_net_open, + .ndo_start_xmit = ctn91xx_net_start_xmit, + .ndo_stop = ctn91xx_net_stop, + .ndo_get_stats = ctn91xx_net_get_stats, + .ndo_set_mac_address = ctn91xx_net_set_mac_addr, + .ndo_tx_timeout = ctn91xx_net_tx_timeout, +}; +#endif + + +int ctn91xx_net_init( ctn91xx_dev_t* dev ) +{ + struct net_device* netdev = NULL; + ctn91xx_net_priv_t* priv = NULL; + int i; + + netdev = alloc_etherdev( sizeof( ctn91xx_net_priv_t ) ); + + if( netdev == NULL ) { + ERROR("unable to alloc new ethernet"); + return -ENOMEM; + } + +#if USE_PCI + SET_NETDEV_DEV( netdev, &dev->pdev->dev ); +#endif + + priv = netdev_priv(netdev); + priv->ctn91xx_dev = dev; + spin_lock_init( &priv->lock ); + + snprintf( netdev->name, 20, "ctn%d", dev->board_number ); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) + netdev->netdev_ops = &ctn91xx_net_device_ops; +#else + netdev->open = ctn91xx_net_open; + netdev->hard_start_xmit = ctn91xx_net_start_xmit; + netdev->stop = ctn91xx_net_stop; + netdev->get_stats = ctn91xx_net_get_stats; + netdev->tx_timeout = ctn91xx_net_tx_timeout; +#endif + netdev->watchdog_timeo = CTN91XX_NET_TX_TIMEOUT; + + i = register_netdev( netdev ); + + if( i ) { + ERROR("failed to register netdev"); + free_netdev( netdev ); + return -EINVAL; + } + +#if USE_PCI + netdev->dev_addr[0] = 0x00; + netdev->dev_addr[1] = 0x22; + netdev->dev_addr[2] = 0x2c; + netdev->dev_addr[3] = 0xff; + netdev->dev_addr[4] = 0xff; + netdev->dev_addr[5] = 0xff - dev->board_number; +#else + random_ether_addr(netdev->dev_addr); +#endif + + + netdev->irq = dev->irq; + netdev->base_addr = (unsigned long)dev->hw_reg_base; + + dev->net_dev = netdev; + +#if USE_PCI + ctn91xx_write8( 1, dev->msg_base, MSG_BUFFER_INT_ENABLE ); +#endif + + + return 0; +} + +int ctn91xx_net_deinit( ctn91xx_dev_t* dev ) +{ + ctn91xx_write32( 0, dev->msg_base, MSG_BUFFER_TAG ); + if(dev->net_dev) { + unregister_netdev( dev->net_dev ); + free_netdev( dev->net_dev ); + } + return 0; +} + +static int ctn91xx_net_open( struct net_device *ndev ) +{ + netif_start_queue( ndev ); + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) +static int ctn91xx_net_set_mac_addr(struct net_device *netdev, void *p) +{ + struct sockaddr *addr=p; + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + + sdump_buffer(netdev->dev_addr, 6, "MAC Address"); + return 0; +} +#endif + + +static int ctn91xx_net_start_xmit( struct sk_buff *skb, struct net_device *ndev ) +{ + ctn91xx_net_priv_t* priv = netdev_priv(ndev); + ctn91xx_dev_t* dev = priv->ctn91xx_dev; + int ret = 0; + + uint32_t msg_buffer_buffer = MSG_BUFFER_BUFFER; + + spin_lock_w_flags( &priv->lock ); + + if( !priv->tx_outstanding ) { + + //uint32_t tag = ctn91xx_read32( dev->msg_base, MSG_BUFFER_TAG ); + //uint8_t valid = ( tag >> 16 ) & 0x1; + // + //TODO check ready bit in tag to see if other side is up...if not up, + //stop the queue and set a timer to check it periodically, so we + //can netif_start_queue + // + //NOTE: windows driver does not set valid bit currently + + priv->tx_outstanding = 1; + priv->tx_skb = skb; + + spin_unlock_w_flags( &priv->lock ); + + ctn91xx_write32( MSG_BUFFER_TAG_ETHERNET, dev->msg_base, MSG_BUFFER_LOCAL_TAG ); + ctn91xx_write32( MSG_BUFFER_SLOT_NUMBER(dev) | MSG_BUFFER_TAG_ETHERNET, dev->msg_base, MSG_BUFFER_TAG ); + + ctn91xx_write16( skb->len, dev->msg_base, MSG_BUFFER_MSG_LEN ); + memcpy( dev->msg_base + msg_buffer_buffer, skb->data, skb->len ); + +#if PRINT_TRAFFIC + sdump_buffer( skb->data, skb->len, "tx"); +#endif + + ndev->trans_start = jiffies; + ctn91xx_write8( 1, dev->msg_base, MSG_BUFFER_MSG_AVAIL ); + + } else { + netif_stop_queue( ndev ); + ret = NETDEV_TX_BUSY; + spin_unlock_w_flags( &priv->lock ); + } + + return ret; +} + +static void ctn91xx_net_tx_timeout( struct net_device* net_dev ) +{ + ctn91xx_net_priv_t* priv = netdev_priv(net_dev); + ctn91xx_dev_t* dev = priv->ctn91xx_dev; + uint32_t tag = ctn91xx_read32( dev->msg_base, MSG_BUFFER_TAG ); + uint8_t valid = ( tag >> 16 ) & 0x1; + + spin_lock_w_flags( &priv->lock ); + + INFO("tx timeout (eth_pend=%d, tx_pen=%d recv=%d skb=%d valid=%d)", + (ctn91xx_read32( dev->msg_base, MSG_BUFFER_LOCAL_TAG ) & 0xff ) == MSG_BUFFER_TAG_ETHERNET, + priv->tx_outstanding, + ctn91xx_read8( dev->msg_base, MSG_BUFFER_MSG_RECV ), + priv->tx_skb != NULL, + valid); + ctn91xx_handle_tx( dev, priv ); + + spin_unlock_w_flags( &priv->lock ); +} + +static int ctn91xx_net_stop( struct net_device *ndev ) +{ + netif_stop_queue (ndev); + return 0; +} + +static struct net_device_stats* ctn91xx_net_get_stats( struct net_device *ndev ) +{ + ctn91xx_net_priv_t* priv = netdev_priv(ndev); + return &priv->stats; +} + +//lock must be held when calling +static void ctn91xx_handle_tx( ctn91xx_dev_t* dev, ctn91xx_net_priv_t* priv ) +{ + if( !priv->tx_outstanding ) { + ERROR("unexpected tx oustanding == 0"); + } + + if( (ctn91xx_read32( dev->msg_base, MSG_BUFFER_LOCAL_TAG ) & 0xff ) == MSG_BUFFER_TAG_ETHERNET) { + priv->tx_outstanding = 0; + ctn91xx_write8( 0, dev->msg_base, MSG_BUFFER_INT_ACK_RECV ); + } + + if( priv->tx_skb ) { + priv->stats.tx_bytes += priv->tx_skb->len; + priv->stats.tx_packets++; + dev_kfree_skb_irq( priv->tx_skb ); + priv->tx_skb = NULL; + } + + ctn91xx_net_wake_up( dev ); + wake_up( &dev->msg_buffer_wait_queue ); +} + +void ctn91xx_net_rx_skb( ctn91xx_dev_t* dev, struct sk_buff* skb, uint16_t rx_len ) +{ + struct net_device* netdev = dev->net_dev; + ctn91xx_net_priv_t* priv = netdev_priv(netdev); + + spin_lock_w_flags( &priv->lock ); + + skb->dev = netdev; + skb_put( skb, rx_len ); + skb->protocol = eth_type_trans( skb, netdev ); + netif_rx( skb ); + + netdev->last_rx = jiffies; + priv->stats.rx_bytes += rx_len; + priv->stats.rx_packets++; + + spin_unlock_w_flags( &priv->lock ); +} + +irqreturn_t ctn91xx_net_isr(int irq, void *ptr) +{ + ctn91xx_dev_t* dev = ptr; + struct net_device* netdev = dev->net_dev; + ctn91xx_net_priv_t* priv = netdev_priv(netdev); + irqreturn_t ret = IRQ_NONE; + struct sk_buff* skb; + uint16_t rx_len; + uint32_t msg_buffer_buffer = MSG_BUFFER_BUFFER; + + spin_lock_w_flags( &priv->lock ); + + if( ctn91xx_read8( dev->msg_base, MSG_BUFFER_MSG_AVAIL ) && + ( ctn91xx_read32( dev->msg_base, MSG_BUFFER_TAG ) & 0xff ) == MSG_BUFFER_TAG_ETHERNET ) { + + ret = IRQ_HANDLED; + dev->stats.isr_net_rx_count++; + + rx_len = ctn91xx_read16( dev->msg_base, MSG_BUFFER_MSG_LEN ); + + ctn91xx_write8( 0, dev->msg_base, MSG_BUFFER_INT_ACK_AVAIL ); + + if( rx_len > 0 ) { + skb = dev_alloc_skb( rx_len ); + + if( skb ) { + skb->dev = netdev; + + memcpy( skb->data, dev->msg_base + msg_buffer_buffer, rx_len ); + +#if PRINT_TRAFFIC + sdump_buffer( skb->data, rx_len, "rx"); +#endif + skb_put( skb, rx_len ); + + skb->protocol = eth_type_trans( skb, netdev ); + netif_rx( skb ); + + netdev->last_rx = jiffies; + priv->stats.rx_bytes += rx_len; + priv->stats.rx_packets++; + } else { + if( printk_ratelimit() ) { + INFO("Memory squeeze, dropping packet"); + } + priv->stats.rx_dropped++; + } + } else { + dev->stats.isr_net_zero_count++; + } + + ctn91xx_write8( 1, dev->msg_base, MSG_BUFFER_MSG_RECV ); + } + + if( ctn91xx_read8( dev->msg_base, MSG_BUFFER_MSG_RECV ) && + ctn91xx_read32( dev->msg_base, MSG_BUFFER_LOCAL_TAG ) == MSG_BUFFER_TAG_ETHERNET ) { + + dev->stats.isr_net_tx_count++; + ret = IRQ_HANDLED; + + ctn91xx_handle_tx( dev, priv ); + } + + spin_unlock_w_flags( &priv->lock ); + return ret; +} + +void ctn91xx_net_wake_up( ctn91xx_dev_t* ctn91xx_dev ) +{ + if( netif_queue_stopped( ctn91xx_dev->net_dev ) ) { + netif_wake_queue( ctn91xx_dev->net_dev ); + } +} + diff --git a/ctn91xx_net.h b/ctn91xx_net.h new file mode 100644 index 0000000..0c5e6f1 --- /dev/null +++ b/ctn91xx_net.h @@ -0,0 +1,16 @@ +#ifndef CTN91XX_NET_H +#define CTN91XX_NET_H + +#include "ctn91xx.h" +#define CTN91XX_NET_TX_TIMEOUT (6*HZ) + +int ctn91xx_net_init( ctn91xx_dev_t* dev ); +int ctn91xx_net_deinit( ctn91xx_dev_t* dev ); + +irqreturn_t ctn91xx_net_isr(int irq, void *ptr); +void ctn91xx_net_wake_up( ctn91xx_dev_t* dev ); + + +void ctn91xx_net_rx_skb( ctn91xx_dev_t* dev, struct sk_buff* skb, uint16_t rx_len ); + +#endif diff --git a/ctn91xx_pci.c b/ctn91xx_pci.c new file mode 100644 index 0000000..9d33693 --- /dev/null +++ b/ctn91xx_pci.c @@ -0,0 +1,282 @@ +#include "ctn91xx_pci.h" +#include "ctn91xx_util.h" +#include "ctn91xx_interrupt.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_net.h" +#include "ctn91xx_reset.h" +#include "ctn91xx_driver.h" + +#if USE_PCI + +#define MAX_BOARDS 20 +static int boards[MAX_BOARDS] = {}; + +static int face_present = 0; + +static int ctn91xx_register(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + uint32_t freq; + ctn91xx_dev_t* dev = kzalloc( sizeof( ctn91xx_dev_t ), GFP_KERNEL ); + + if(!dev) { + return -ENOMEM; + } + + dev->pdev = pdev; + + if(!ctn91xx_board_init(dev)) { + return -ENOMEM; + } + + if(pci_enable_device(pdev) != 0) { + ERROR("cannot enable pci device"); + return -ENODEV; + } + + pci_set_drvdata(pdev, (void*)dev); + + dev->irq = pdev->irq; + + if(pci_request_regions(pdev, "ctn91xx") != 0) { + pci_disable_device(pdev); + ERROR("pci device busy"); + return -EBUSY; + } + + + /* Try to init DMA */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,1,0) + if(pci_set_dma_mask(pdev, DMA_32BIT_MASK)) { +#else + if(pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { +#endif + ERROR("No suitable DMA mask available."); + } + + pci_set_master(pdev); + + dev->hw_reg_base = pci_resource_start(pdev, 0); + dev->base = ioremap_nocache(dev->hw_reg_base, CTN91XX_REG_REGION_SIZE); + + if(!dev->base) { + ERROR("reg ioremap failed"); + return -EIO; + } + + INFO("IO hw_reg_base address: %lx", dev->hw_reg_base); + INFO("reg_base: %p", dev->base); + + dev->translation_hw_reg_base = pci_resource_start(pdev, 1); + dev->translation_base = ioremap_nocache(dev->translation_hw_reg_base, CTN91XX_TRANSLATION_REG_REGION_SIZE); + + if(!dev->translation_base) { + WARNING("translation reg ioremap failed"); + } else { + INFO("IO translation_hw_reg_base address: %lx", dev->translation_hw_reg_base); + INFO("translation_reg_base: %p", dev->translation_base); + } + + dev->mpeg_filter_dma_base = dev->base + FILTER_DMA_REG_OFFSET; + dev->mpeg_dma_base = dev->base + DMA_REG_OFFSET; + dev->system_control_base = dev->base + SYSTEM_CONTROL_OFFSET; + dev->msg_base = dev->base + MSG_BUFFER_OFFSET; + dev->timer_reg_base = dev->base + TIMER_REG_OFFSET; + + + dev->release_version = ctn91xx_read16(dev->system_control_base, CTN91XX_RELEASE_VERSION_OFFSET); + dev->chip_version = ctn91xx_read16(dev->system_control_base, CTN91XX_CHIP_VERSION_OFFSET); + + if(dev->release_version == 0 || dev->chip_version == 0) { + + WARNING("read versions %d and %d, retrying", dev->release_version, dev->chip_version); + dev->release_version = ctn91xx_read16(dev->system_control_base, CTN91XX_RELEASE_VERSION_OFFSET); + dev->chip_version = ctn91xx_read16(dev->system_control_base, CTN91XX_CHIP_VERSION_OFFSET); + } + + if( dev->release_version == 0xffff ) { + ERROR("chip not configured %04x", dev->release_version); + goto reg_base_cleanup; + } + + freq = ctn91xx_read32(dev->system_control_base, SYSTEM_CONTROL_CLOCK_FREQ); + dev->ticks_per_us = (freq / 1000000); + INFO("system clock freq %d, ticks per us %d", freq, dev->ticks_per_us); + if( dev->ticks_per_us == 100 ) { + //FIXME: acting like noah based on clock frequency + dev->is_noah = 1; + } + + if( dev->is_noah ) { + //overwrite board_number with correct value from hw, if it supports it + //must be done before device files are registered + uint32_t tag = ctn91xx_read32( dev->msg_base, MSG_BUFFER_TAG ); + uint8_t retries_left = 2; + uint8_t valid = ( tag >> 16 ) & 0x1; + dev->board_number = ( tag >> 8 ) & 0xff; + + //0 is the reset value, and a valid board number (d'oh!) + //so try to ensure we got the right one + if( dev->board_number == 0 ) { + while( !valid && retries_left > 0 ) { + msleep(1000); + tag = ctn91xx_read32( dev->msg_base, MSG_BUFFER_TAG ); + valid = ( tag >> 16 ) & 0x1; + dev->board_number = ( tag >> 8 ) & 0xff; + retries_left--; + } + } + + dev->board_booting = valid; + + if( valid && boards[0] == 0 && dev->board_number == 0 ) { + INFO("face detected as present"); + face_present = 1; + } + + dev->face_present = face_present; + + if( !dev->face_present ) { + //will be set to next available by code below + dev->board_number = 0; + } + } + + if( dev->board_number > MAX_BOARDS ) { + ERROR("invalid board number %d", dev->board_number); + dev->board_number = MAX_BOARDS; + } + + if( !boards[dev->board_number] ) { + boards[dev->board_number] = 1; + } else { + int i; + for( i=0; iboard_number = i; + boards[i] = 1; + break; + } + } + } + + if(dev->release_version != 0 && dev->chip_version != 0) { + + dev->which_proc = ctn91xx_read32(dev->base, PROC_IDENT_OFFSET); + ctn91xx_reset_all(dev); + + INFO("Driver initialization successful for CTN-%d v%d", dev->chip_version, dev->release_version); + } else { + WARNING("release_version %d chip_version %d", dev->release_version, dev->chip_version); + } + + if( ctn91xx_register_ctrl_device( dev ) ) { + ERROR("Unable to register char device"); + goto reg_base_cleanup; + } + +#if HAS_MPEG_DMA + if( ctn91xx_init_mpeg(dev) < 0 ) { + goto reg_base_cleanup; + } +#endif + + if( ctn91xx_net_init( dev ) ) { + ERROR("failed to init net device"); + goto dev_cleanup; + } + + /* Now enable our interrupt */ + dev->int_enable = 1; + INFO("irq: %d", pdev->irq); + if(request_irq(pdev->irq, ctn91xx_isr, IRQF_SHARED, DEVICE_NAME, dev)) { + ERROR("Cannot allocate irq %d", pdev->irq); + pdev->irq = 0; + goto net_cleanup; + } + + return 0; + +net_cleanup: + ctn91xx_net_deinit( dev ); +dev_cleanup: + ctn91xx_unregister_ctrl_device( dev ); +#if HAS_MPEG_DMA + ctn91xx_deinit_mpeg(dev); +#endif + +reg_base_cleanup: + if(dev->translation_base) { + iounmap(dev->translation_base); + } + iounmap(dev->base); + + pci_release_regions(pdev); + pci_disable_device(pdev); + return -EIO; +} + +static void __devexit ctn91xx_unregister(struct pci_dev *pdev) +{ + ctn91xx_dev_t* dev = (ctn91xx_dev_t*)pci_get_drvdata(pdev); + + free_irq(pdev->irq, dev); + dev->int_enable = 0; + + + ctn91xx_write8(0x0, dev->mpeg_dma_base, MPEG_DMA_INTERRUPT_ENABLE); + ctn91xx_write8(0x1, dev->mpeg_dma_base, MPEG_DMA_RESET); + + msleep(1); + + if(ctn91xx_read8(dev->mpeg_dma_base, MPEG_DMA_BUSY)) { + ERROR("resetting while dma is busy, this is BAD"); + } + + ctn91xx_net_deinit( dev ); + + iounmap(dev->base); + + if(dev->translation_base) { + iounmap(dev->translation_base); + } + + ctn91xx_unregister_ctrl_device( dev ); + +#if HAS_MPEG_DMA + ctn91xx_deinit_mpeg(dev); +#endif + + pci_release_regions(pdev); + + pci_disable_device(pdev); + ctn91xx_board_uninit(dev); + + kfree( dev ); +} + +static struct pci_device_id ctn91xx_table[] __devinitdata = { + { CETON_VENDOR_ID, CTN91XX_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { ALTERA_VENDOR_ID, CTN91XX_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +static struct pci_driver ctn91xx_driver = { + .name = DEVICE_NAME, + .id_table = ctn91xx_table, + .probe = ctn91xx_register, + .remove = __devexit_p(ctn91xx_unregister), +}; + +int ctn91xx_register_pci_driver() +{ + return pci_register_driver(&ctn91xx_driver); +} + +void ctn91xx_unregister_pci_driver() +{ + pci_unregister_driver(&ctn91xx_driver); +} + +MODULE_DEVICE_TABLE(pci, ctn91xx_table); + +#endif diff --git a/ctn91xx_pci.h b/ctn91xx_pci.h new file mode 100644 index 0000000..c285783 --- /dev/null +++ b/ctn91xx_pci.h @@ -0,0 +1,8 @@ +#ifndef CTN91XX_PCI_H +#define CTN91XX_PCI_H + +int ctn91xx_register_pci_driver(void); +void ctn91xx_unregister_pci_driver(void); + +#endif + diff --git a/ctn91xx_registers.h b/ctn91xx_registers.h new file mode 100644 index 0000000..2749ba4 --- /dev/null +++ b/ctn91xx_registers.h @@ -0,0 +1,152 @@ +#ifndef CTN91XX_REGISTERS_H +#define CTN91XX_REGISTERS_H + +#ifndef USE_INTERNAL +#define USE_INTERNAL 1 +#endif + +/* Size of pci regions (data and registers)*/ +#define CTN91XX_REG_REGION_SIZE 2*64*1024 +#define CTN91XX_TRANSLATION_REG_REGION_SIZE 1*64*1024 + +#define ALTERA_VENDOR_ID 0x1172 +#define CETON_VENDOR_ID 0x1B7C +#define CTN91XX_DEVICE_ID 0x0004 + +/* REG SPACE OFFSETS */ +#define FILTER_DMA_REG_OFFSET 0x0C00 +#define DMA_REG_OFFSET 0x1000 +#define SYSTEM_CONTROL_OFFSET 0x0800 +#define MSG_BUFFER_OFFSET 0x8000 +#define TIMER_REG_OFFSET 0xCC00 +#define PROC_IDENT_OFFSET 0xC000 + + +#define CTN91XX_FW_START_BUF_ADDR (0x40000000 + (24 * 1024 * 1024)) +#define CTN91XX_FW_BUF_SIZE (PAGE_SIZE) + +#define CTN91XX_UNIQUE_ID_LOW 996 +#define CTN91XX_UNIQUE_ID_HIGH 1000 + +#define CTN91XX_RELEASE_VERSION_OFFSET 1020 +#define CTN91XX_CHIP_VERSION_OFFSET 1022 + +#define CTN91XX_VERSION_OFFSET 1020 + +#define CTN91XX_VERSION_INFINITV4_PCI_NOAH 0x3 +#define CTN91XX_VERSION_INFINITV4_USB_LUNA 0x4 +#define CTN91XX_VERSION_INFINITV4_ETH_LUNA 0x5 +#define CTN91XX_VERSION_INFINITV4_USB_VENICE 0x6 +#define CTN91XX_VERSION_INFINITV6_USB_VENICE 0x7 +#define CTN91XX_VERSION_INFINITV6_ETH_VENICE 0x8 +#define CTN91XX_VERSION_INFINITV4_ETH_VENICE 0x9 +#define CTN91XX_VERSION_INFINITV6_PCIE_NOVA 0x10 +#define CTN91XX_VERSION_INFINITV4_PCIE_NOVA 0x11 + +/* Interrupt types */ + +#define CTN91XX_EVENT_RPC_RECVD 0x54 +#define CTN91XX_EVENT_RPC_ACK_RECVD 0x55 + +#define MSG_BUFFER_MSG_AVAIL 0 +#define MSG_BUFFER_INT_ACK_AVAIL 1 +#define MSG_BUFFER_MSG_LEN 2 +#define MSG_BUFFER_MSG_RECV 4 +#define MSG_BUFFER_INT_ACK_RECV 5 +#define MSG_BUFFER_RESET 6 +#define MSG_BUFFER_INT_ENABLE 7 +#define MSG_BUFFER_TAG 8 +#define MSG_BUFFER_TAG_READY 0x80 +#define MSG_BUFFER_TAG_ETHERNET ( 0x01 | MSG_BUFFER_TAG_READY ) +#define MSG_BUFFER_TAG_CONTROL ( 0x02 | MSG_BUFFER_TAG_READY ) +#define MSG_BUFFER_SLOT_NUMBER(dev) ((0x10000) | ((dev->slot_number << 8) & 0xff00)) +#define MSG_BUFFER_LOCAL_TAG 12 +#define MSG_BUFFER_BUFFER 0x1000 +#define MSG_BUFFER_BUFFER_LENGTH 4096 + +#define CTN91XX_ORIGIN_CARD0 0x00 +#define CTN91XX_ORIGIN_TUNER0 0x04 +#define CTN91XX_ORIGIN_TUNER1 0x05 +#define CTN91XX_ORIGIN_TUNER2 0x06 +#define CTN91XX_ORIGIN_TUNER3 0x07 +#define CTN91XX_ORIGIN_TUNER4 0x08 +#define CTN91XX_ORIGIN_TUNER5 0x09 +#define CTN91XX_ORIGIN_FILTER0 0x0A +#define CTN91XX_ORIGIN_FILTER1 0x0B +#define CTN91XX_ORIGIN_FILTER2 0x0C +#define CTN91XX_ORIGIN_FILTER3 0x0D +#define CTN91XX_ORIGIN_FILTER4 0x0E +#define CTN91XX_ORIGIN_FILTER5 0x0F +#define CTN91XX_ORIGIN_MSG_BUFFER 0x10 + +#define NUM_FACE_GPIO 32 + +/** + * Timer registers + */ +#define NUM_TIMERS 16 +#define TIMER_ENABLE(index) ( index*16 + 0 ) +#define TIMER_IRQ_REG(index) ( index*16 + 1 ) +#define TIMER_MASK(index) ( index*16 + 2 ) +#define TIMER_TIMEOUT(index) ( index*16 + 4 ) + +#define MAX_TIMER_ENABLE(index) ( (index+6)*16 + 0 ) +#define MAX_TIMER_IRQ_REG(index) ( (index+6)*16 + 1 ) +#define MAX_TIMER_MASK(index) ( (index+6)*16 + 2 ) +#define MAX_TIMER_TIMEOUT(index) ( (index+6)*16 + 4 ) + +/** + * MPEG DMA control registers + */ +#define MPEG_DMA_INTERRUPT_ENABLE 0 +#define MPEG_DMA_WRITE_ENABLE 1 +#define MPEG_DMA_BANK_INTERRUPT_MASK 2 +#define MPEG_DMA_BANK_INTERRUPT_MASKED 3 +#define MPEG_DMA_BANK_INTERRUPT_REAL 4 +#define MPEG_DMA_RESET 5 +#define MPEG_DMA_BUSY 6 +#define MPEG_DMA_PAGES_PER_BANK_BASE 8 +#define MPEG_DMA_BYTES_PER_PAGE_BASE 56 +#define MPEG_DMA_LOCK_BASE 80 + +#define MPEG_DMA_NOTIFY_VECTOR 3696 +#define MPEG_DMA_NOTIFY_ENABLE 3698 +#define MPEG_DMA_TIMER_ENABLE 3699 +#define MPEG_DMA_NOTIFY_BASE 3700 +#define MPEG_DMA_NOTIFY_BYTES_PER_PAGE_BASE 3704 +#define MPEG_DMA_PAGES_PER_NOTIFY_BASE 3706 +#define MPEG_DMA_TIMEOUT_VALUE 3796 +#define MPEG_DMA_SET_TIMEOUT 3800 +#define MPEG_DMA_NOTIFY_VECTOR_MASK 3804 +#define MPEG_DMA_NOTIFY_VECTOR_REAL 3808 + +#define MPEG_FILTER_DMA_INTERRUPT_ENABLE 0 +#define MPEG_FILTER_DMA_WRITE_ENABLE 1 +#define MPEG_FILTER_DMA_BANK_INTERRUPT_MASK 2 +#define MPEG_FILTER_DMA_BANK_INTERRUPT_MASKED 3 +#define MPEG_FILTER_DMA_BANK_INTERRUPT_REAL 4 +#define MPEG_FILTER_DMA_RESET 5 +#define MPEG_FILTER_DMA_BUSY 6 +#define MPEG_FILTER_DMA_PAGES_PER_BANK_BASE 8 +#define MPEG_FILTER_DMA_BYTES_PER_PAGE_BASE 56 +#define MPEG_FILTER_DMA_LOCK_BASE 80 + +#define MPEG_FILTER_DMA_NOTIFY_VECTOR 128 +#define MPEG_FILTER_DMA_NOTIFY_ENABLE 130 +#define MPEG_FILTER_DMA_TIMER_ENABLE 131 +#define MPEG_FILTER_DMA_NOTIFY_BASE 132 +#define MPEG_FILTER_DMA_NOTIFY_BYTES_PER_PAGE_BASE 136 +#define MPEG_FILTER_DMA_PAGES_PER_NOTIFY_BASE 138 +#define MPEG_FILTER_DMA_NOTIFY_VECTOR_MASK 188 +#define MPEG_FILTER_DMA_NOTIFY_VECTOR_REAL 190 +#define MPEG_FILTER_DMA_TIMEOUT_VALUE 192 +#define MPEG_FILTER_DMA_SET_TIMEOUT 196 + + + +#define SYSTEM_CONTROL_CLOCK_FREQ 1004 +#define SYSTEM_CONTROL_UP_TIME_OFFSET 76 +#define SYSTEM_CONTROL_LAST_UP_TIME_OFFSET 84 + + +#endif diff --git a/ctn91xx_reset.c b/ctn91xx_reset.c new file mode 100644 index 0000000..68fa86e --- /dev/null +++ b/ctn91xx_reset.c @@ -0,0 +1,113 @@ +#include "ctn91xx_reset.h" +#include "ctn91xx_util.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_event.h" +#include "ctn91xx_rpc.h" + + + +#if HAS_MPEG_DMA +void mpeg_reset(ctn91xx_dev_t* dev) +{ + int i; + //stop dma engine + ctn91xx_write8(0x1, dev->mpeg_dma_base, MPEG_DMA_RESET); + ctn91xx_write8(0x0, dev->mpeg_dma_base, MPEG_DMA_RESET); + + ctn91xx_write8(0x1, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_RESET); + ctn91xx_write8(0x0, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_RESET); + + ctn91xx_busywait(dev, 1000); + +#if !USE_LEON + if(ctn91xx_read8(dev->mpeg_dma_base, MPEG_DMA_BUSY)) { + ERROR("resetting while dma is busy, this is BAD"); + } +#endif + + + //unmap the pages that were in use + //initialize the current scatter-gather addresses for each mpeg device + //lock those pages + for(i=0; impeg_buffer[i]; + int last_index_to_unmap; + int j; + + spin_lock_w_flags(&vbuffer->lock); + + last_index_to_unmap = WRAPPED_PAGE_INDEX(vbuffer, vbuffer->write_idx + (2*num_pages)); + + for(j = vbuffer->notify_idx; j != last_index_to_unmap; j = (j+1) % vbuffer->npages) { + if(vbuffer->lock_cnt[j] > 0) { + ctn91xx_mpeg_unmap_page(dev, vbuffer, j, 0,i); + vbuffer->lock_cnt[j] = 0; + } + } + + if(is_filter_stream(i)) { + ctn91xx_write8(num_pages, dev->mpeg_filter_dma_base, + MPEG_FILTER_DMA_PAGES_PER_BANK_BASE + ((i+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*4)); + } else { + ctn91xx_write8(num_pages, dev->mpeg_dma_base, MPEG_DMA_PAGES_PER_BANK_BASE + (i*4)); + } + ctn91xx_reset_dma_adjustments(dev, i); + + vbuffer->notify_idx = vbuffer->write_idx = vbuffer->read_idx = 0; + + vbuffer_map_bank(dev, vbuffer, num_pages, 0); + + vbuffer->write_idx = WRAPPED_PAGE_INDEX(vbuffer, vbuffer->write_idx + (num_pages)); + vbuffer_map_bank(dev, vbuffer, num_pages, 1); + + vbuffer->write_idx = vbuffer->read_idx; + + spin_unlock_w_flags(&vbuffer->lock); + + if(is_filter_stream(i)) { + ctn91xx_write8(0, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_LOCK_BASE + ((i+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*2)); + ctn91xx_write8(0, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_LOCK_BASE + ((i+CTN91XX_ORIGIN_TUNER0-CTN91XX_ORIGIN_FILTER0)*2)); + } else { + ctn91xx_write8(0, dev->mpeg_dma_base, MPEG_DMA_LOCK_BASE + (i*2)); + ctn91xx_write8(0, dev->mpeg_dma_base, MPEG_DMA_LOCK_BASE + (i*2)); + } + } + +#if USE_MPEG_NOTIFY + ctn91xx_write8(0x01, dev->mpeg_dma_base, MPEG_DMA_NOTIFY_ENABLE); + //timeouts requires notify support + ctn91xx_write32(dev->dma_timeout, dev->mpeg_dma_base, MPEG_DMA_TIMEOUT_VALUE); + ctn91xx_write8(0x01, dev->mpeg_dma_base, MPEG_DMA_SET_TIMEOUT); + ctn91xx_write8(0x01, dev->mpeg_dma_base, MPEG_DMA_TIMER_ENABLE); + + //timeouts requires notify support + ctn91xx_write8(0x01, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_NOTIFY_ENABLE); + ctn91xx_write32(dev->dma_timeout, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_TIMEOUT_VALUE); + ctn91xx_write8(0x01, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_SET_TIMEOUT); + ctn91xx_write8(0x01, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_TIMER_ENABLE); +#endif + + ctn91xx_write8(0x01, dev->mpeg_dma_base, MPEG_DMA_WRITE_ENABLE); + ctn91xx_write8(0x01, dev->mpeg_dma_base, MPEG_DMA_INTERRUPT_ENABLE); + + ctn91xx_write8(0x01, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_WRITE_ENABLE); + ctn91xx_write8(0x01, dev->mpeg_filter_dma_base, MPEG_FILTER_DMA_INTERRUPT_ENABLE); +} +#endif + +void ctn91xx_reset_all(ctn91xx_dev_t* dev) +{ + WRITE_LOCK(); + INFO("reset all"); + + + ctn91xx_event_reset(dev); + +#if HAS_MPEG_DMA + mpeg_reset(dev); +#endif + WRITE_UNLOCK(); +} + + diff --git a/ctn91xx_reset.h b/ctn91xx_reset.h new file mode 100644 index 0000000..6b83644 --- /dev/null +++ b/ctn91xx_reset.h @@ -0,0 +1,11 @@ +#ifndef CTN91XX_RESET_H +#define CTN91XX_RESET_H + +#include "ctn91xx.h" + +void ctn91xx_reset_all( ctn91xx_dev_t* dev ); + + +void mpeg_reset(ctn91xx_dev_t* dev); + +#endif diff --git a/ctn91xx_rpc.c b/ctn91xx_rpc.c new file mode 100644 index 0000000..ee021bd --- /dev/null +++ b/ctn91xx_rpc.c @@ -0,0 +1,119 @@ +#include "ctn91xx_rpc.h" +#include "ctn91xx_util.h" +#include "ctn91xx_net.h" + + +int ctn91xx_msg_buffer_wait_not_busy( ctn91xx_dev_t* dev ) +{ + int tries_left = 10; + struct net_device* ndev = dev->net_dev; + ctn91xx_net_priv_t* priv = netdev_priv(ndev); + + spin_lock_w_flags( &priv->lock ); + +recheck: + if( !priv->tx_outstanding ) { + priv->tx_outstanding = 1; + spin_unlock_w_flags( &priv->lock ); + return 0; + } + + spin_unlock_w_flags( &priv->lock ); + + //wait longer than the net timeout + if( !wait_event_timeout( + dev->msg_buffer_wait_queue, + priv->tx_outstanding == 0, + CTN91XX_NET_TX_TIMEOUT + HZ ) ) { + ERROR("rpc send timed out"); + return -EBUSY; + } else { + + if( tries_left <= 0 ) { + //we've met our condition 10 times, but still haven't been able to + //send. Just give up now. + return -EBUSY; + } + + spin_relock_w_flags( &priv->lock ); + tries_left--; + goto recheck; + } +} + + +void ctn91xx_rpc_send_common( ctn91xx_dev_t* dev, rpc_send_t* rpc ) +{ + uint32_t msg_buffer_buffer = MSG_BUFFER_BUFFER; + + ctn91xx_write32( MSG_BUFFER_TAG_CONTROL, dev->msg_base, MSG_BUFFER_LOCAL_TAG ); + ctn91xx_write32( MSG_BUFFER_SLOT_NUMBER(dev) | MSG_BUFFER_TAG_CONTROL, dev->msg_base, MSG_BUFFER_TAG ); + + ctn91xx_write8( rpc->payload_start, dev->msg_base, msg_buffer_buffer ); + + if( rpc->payload_start ) { + + ctn91xx_write32( htonl( rpc->section_length ), dev->msg_base, msg_buffer_buffer + 4 ); + ctn91xx_write16( rpc->length + 8, dev->msg_base, MSG_BUFFER_MSG_LEN ); + memcpy( dev->msg_base + msg_buffer_buffer + 8, dev->rpc_buffer, rpc->length ); + + } else { + ctn91xx_write16( rpc->length + 4, dev->msg_base, MSG_BUFFER_MSG_LEN ); + memcpy( dev->msg_base + msg_buffer_buffer + 4, dev->rpc_buffer, rpc->length ); + } + ctn91xx_write8( 1, dev->msg_base, MSG_BUFFER_MSG_AVAIL ); +} + + + +int ctn91xx_rpc_send_kernel( ctn91xx_dev_t* dev, rpc_send_t* rpc ) +{ + if( ctn91xx_msg_buffer_wait_not_busy( dev ) < 0 ) { + return -EBUSY; + } + + memcpy( dev->rpc_buffer, rpc->msg, rpc->length ); + ctn91xx_rpc_send_common( dev, rpc ); + return 0; +} + +int ctn91xx_rpc_send( ctn91xx_dev_t* dev, unsigned long arg, int compat ) +{ + if( ctn91xx_msg_buffer_wait_not_busy( dev ) < 0 ) { + return -EBUSY; + } + + if( compat ) { + rpc_send_compat_t rpc32 = {}; + rpc_send_t rpc = {}; + + if( copy_from_user( &rpc32, (__user rpc_send_compat_t*)arg, sizeof( rpc_send_compat_t ) ) ) { + return -EINVAL; + } + + if( copy_from_user( dev->rpc_buffer, (char __user*)(unsigned long)rpc32.msg, rpc32.length ) ) { + return -EINVAL; + } + + rpc.payload_start = rpc32.payload_start; + rpc.length = rpc32.length; + rpc.section_length = rpc32.section_length; + + ctn91xx_rpc_send_common( dev, &rpc ); + return 0; + } else { + rpc_send_t rpc = {}; + + if( copy_from_user( &rpc, (__user rpc_send_t*)arg, sizeof( rpc_send_t ) ) ) { + return -EINVAL; + } + + if( copy_from_user( dev->rpc_buffer, rpc.msg, rpc.length ) ) { + return -EINVAL; + } + + ctn91xx_rpc_send_common( dev, &rpc ); + return 0; + } +} + diff --git a/ctn91xx_rpc.h b/ctn91xx_rpc.h new file mode 100644 index 0000000..a868484 --- /dev/null +++ b/ctn91xx_rpc.h @@ -0,0 +1,9 @@ +#ifndef CTN91XX_RPC_H +#define CTN91XX_RPC_H + +#include "ctn91xx.h" + +int ctn91xx_rpc_send( ctn91xx_dev_t* dev, unsigned long arg, int compat ); +int ctn91xx_rpc_send_kernel( ctn91xx_dev_t* dev, rpc_send_t* rpc ); + +#endif diff --git a/ctn91xx_rtp.c b/ctn91xx_rtp.c new file mode 100644 index 0000000..109469d --- /dev/null +++ b/ctn91xx_rtp.c @@ -0,0 +1,289 @@ +#include "ctn91xx.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_rtp.h" +#include "ctn91xx_net.h" + +void ctn91xx_rtp_setup(ctn91xx_dev_t* dev, uint8_t* buf, uint32_t len) +{ + vbuffer_t* vbuffer = NULL; + rtp_state_t* rtp = NULL; + uint32_t tuner_index; + + if( len < SIZEOF_CONTROL_MSG_RTP_SETUP ) { + WARNING("len %d too small", len); + return; + } + + tuner_index = ntohl( *((uint32_t*)&buf[0] ) ); + + if( tuner_index >= NUM_MPEG_STREAMS ) { + WARNING("tuner index %08x was too large", tuner_index); + return; + } + +#if HAS_MPEG_DMA + mpeg_vbuffer_from_tuner_index(tuner_index, dev, &vbuffer); +#endif + + if( !vbuffer ) { + return; + } + + rtp = &vbuffer->rtp_state; + buf += 4; + + memcpy( rtp->remote_hw_addr, buf, 6 ); + buf += 6; + + memcpy( rtp->local_hw_addr, buf, 6 ); + buf += 6; + + rtp->remote_ip_addr = *((uint32_t*)&buf[0]); + buf += 4; + + rtp->local_ip_addr = *((uint32_t*)&buf[0]); + buf += 4; + + rtp->remote_port = *((uint16_t*)&buf[0]); + buf += 2; + + rtp->local_port = *((uint16_t*)&buf[0]); + buf += 2; + + if( !rtp->got_setup ) { + + rtp_header_t* hdr = (rtp_header_t*)rtp->buffer; + + rtp->buffer_used = RTP_HDR_SIZE; + + hdr->version = 2; + hdr->padding = 0; + hdr->extension = 0; + hdr->csrc_len = 0; + hdr->marker = 0; + hdr->payload = MP2T_PAYLOAD_TYPE; + + rtp->got_setup = 1; + rtp->running = 1; + + dev->mpeg_user_cnt[tuner_index]++; + + vbuffer->read_idx = vbuffer->notify_idx; + } +} + +void ctn91xx_rtp_destroy(ctn91xx_dev_t* dev, uint8_t* buf, uint32_t len) +{ + vbuffer_t* vbuffer = NULL; + rtp_state_t* rtp = NULL; + uint32_t tuner_index; + + if( len < SIZEOF_CONTROL_MSG_RTP_DESTROY ) { + WARNING("len %d too small", len); + return; + } + + tuner_index = ntohl( *((uint32_t*)&buf[0] ) ); + + if( tuner_index >= NUM_MPEG_STREAMS ) { + WARNING("tuner index %d was too large", tuner_index); + return; + } + +#if HAS_MPEG_DMA + mpeg_vbuffer_from_tuner_index(tuner_index, dev, &vbuffer); +#endif + + if( !vbuffer ) { + return; + } + + rtp = &vbuffer->rtp_state; + + memset( rtp->remote_hw_addr, 0, 6 ); + memset( rtp->local_hw_addr, 0, 6 ); + rtp->remote_ip_addr = 0; + rtp->local_ip_addr = 0; + rtp->remote_port = 0; + rtp->local_port = 0; + + rtp->got_setup = 0; + rtp->running = 0; + + if( dev->mpeg_user_cnt[tuner_index] > 0 ) { + dev->mpeg_user_cnt[tuner_index]--; + } +} + +uint16_t ip_hdr_chksum( uint8_t* ip, int len ) +{ + uint16_t* buf = (uint16_t*)ip; + int sum = 0; + + while( len > 1 ) { + sum += *buf; + buf++; + if( sum & 0x80000000 ) { + /* if high order bit set, fold */ + sum = (sum & 0xFFFF) + (sum >> 16); + } + len -= 2; + } + + if( len ) { + /* take care of left over byte */ + sum += (unsigned short) *(unsigned char *)buf; + } + + while( sum >> 16 ) { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + return (uint16_t)~sum; +} + + +#define ETH_PACKET_SIZE(rtp) (rtp->buffer_used + 14 /* eth */ + 20 /* ip */ + 8 /* udp */) +#define IP_PACKET_SIZE(rtp) (rtp->buffer_used + 20 /* ip */ + 8 /* udp */) +#define UDP_PACKET_SIZE(rtp) (rtp->buffer_used + 8 /* udp */) +#define RTP_PACKET_SIZE(rtp) (rtp->buffer_used) + + +static void rtp_send_packet( vbuffer_t* vbuffer ) +{ + ctn91xx_dev_t* dev = vbuffer->dev; + rtp_state_t* rtp = &vbuffer->rtp_state; + struct sk_buff* skb; + rtp_header_t* hdr = (rtp_header_t*)rtp->buffer; + uint8_t* buf = NULL; + uint16_t rx_len = ETH_PACKET_SIZE(rtp); + uint16_t ip_chk_sum = 0; + + hdr->seq_no = htons( rtp->seq_no++ ); + hdr->timestamp = MP2T_TIMESTAMP_CLOCK;//TODO get actual timstamp + hdr->ssrc = htonl( rtp->ssrc ); + + skb = dev_alloc_skb( rx_len ); + + if( !skb ) { + if( printk_ratelimit() ) { + WARNING("memory squeeze, dropping packet"); + } + return; + } + + buf = skb->data; + + memcpy( buf, rtp->remote_hw_addr, 6 ); + memcpy( buf + 6, rtp->local_hw_addr, 6 ); + //ether type = IP + buf[12] = 0x08; + buf[13] = 0x00; + buf += 14; + + //ipv4 header + buf[0] = 0x45; + buf[1] = 0x00; + + //ip length + *((uint16_t*)&buf[2]) = htons( IP_PACKET_SIZE( rtp ) ); + + //identification + *((uint16_t*)&buf[4]) = htons( rtp->ip_seq_no++ ); + + /* flags, fragment offset */ + buf[6] = 0; + buf[7] = 0; + /* ttl */ + buf[8] = 10; + /* protocol = UDP */ + buf[9] = 0x11; + + /* checksum */ + *((uint16_t*)&buf[10]) = 0x0000; + + /* addrs */ + *((uint32_t*)&buf[12]) = rtp->local_ip_addr; + *((uint32_t*)&buf[16]) = rtp->remote_ip_addr; + + /* go back and calc checksum */ + ip_chk_sum = ip_hdr_chksum( buf, 20 ); + *((uint16_t*)&buf[10]) = ip_chk_sum; + + buf += 20; + + //udp header + *((uint16_t*)&buf[0]) = rtp->local_port; + *((uint16_t*)&buf[2]) = rtp->remote_port; + *((uint16_t*)&buf[4]) = htons( UDP_PACKET_SIZE( rtp ) ); + //checksum + *((uint16_t*)&buf[6]) = 0x0000; + buf += 8; + + memcpy( buf, rtp->buffer, RTP_PACKET_SIZE( rtp ) ); + rtp->buffer_used = RTP_HDR_SIZE; + + ctn91xx_net_rx_skb( dev, skb, rx_len ); +} + +static void rtp_sink_read_data( vbuffer_t* vbuffer ) +{ + rtp_state_t* rtp = &vbuffer->rtp_state; + uint32_t bytes_left = RTP_SINK_BUFFER_SIZE - rtp->buffer_used; + uint32_t num_ts_pkts = bytes_left / MPEG_TS_PKT_SIZE; + uint8_t* unused_buffer_start = &rtp->buffer[rtp->buffer_used]; + + if( num_ts_pkts > 0 ) { +#if HAS_MPEG_DMA + uint32_t read_size = ctn91xx_mpeg_kernel_read( + vbuffer, unused_buffer_start, MPEG_TS_PKT_SIZE*num_ts_pkts ); + rtp->buffer_used += read_size; +#endif + } +} + +static void rtp_data_ready( vbuffer_t* vbuffer ) +{ + rtp_state_t* rtp = &vbuffer->rtp_state; + uint32_t bytes_left = RTP_SINK_BUFFER_SIZE - rtp->buffer_used; + + if( bytes_left >= MPEG_TS_PKT_SIZE ) { + rtp_sink_read_data( vbuffer ); + } + + bytes_left = RTP_SINK_BUFFER_SIZE - rtp->buffer_used; + + if( bytes_left < MPEG_TS_PKT_SIZE ) { + rtp_send_packet( vbuffer ); + } +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +void ctn91xx_rtp_reader(void* work) +#else +void ctn91xx_rtp_reader(struct work_struct* work) +#endif +{ + rtp_state_t* rtp = container_of(work, rtp_state_t, reader); + vbuffer_t* vbuffer = container_of(rtp, vbuffer_t, rtp_state); + + spin_lock_w_flags(&vbuffer->lock); + + if( !rtp->got_setup || !rtp->running ) { + goto end; + } + +#if HAS_MPEG_DMA + while( mpeg_data_ready( vbuffer ) ) { + rtp_data_ready( vbuffer ); + if( !rtp->running ) { + break; + } + } +#endif + +end: + spin_unlock_w_flags(&vbuffer->lock); +} + + diff --git a/ctn91xx_rtp.h b/ctn91xx_rtp.h new file mode 100644 index 0000000..59fd3db --- /dev/null +++ b/ctn91xx_rtp.h @@ -0,0 +1,36 @@ +#ifndef CTN91XX_RTP_H +#define CTN91XX_RTP_H + +#include "ctn91xx.h" + +#define MP2T_PAYLOAD_TYPE 33 +#define MP2T_TIMESTAMP_CLOCK 90000 + +#define SIZEOF_CONTROL_MSG_FIRST_HEADER 5 +#define SIZEOF_CONTROL_MSG_HEADER 1 +#define SIZEOF_CONTROL_MSG_RTP_SETUP 28 +#define SIZEOF_CONTROL_MSG_RTP_DESTROY 4 +#define SIZEOF_CONTROL_MSG_PBDA_TAG_TABLE (4+188) +#define NUM_MPEG_STREAMS 6 + +#define MPEG_TS_PKT_SIZE 188 +#define RTP_HDR_SIZE 12 + +#define RTP_NOTIFY_COUNT 8 +#define RTP_NOTIFY_PER_PKT 2 + +#define BUFFER_TS_COUNT 21 +#define BRIDGED_TS_COUNT 7 +#define NUM_TS_PKTS_PER_RTP_PKT BRIDGED_TS_COUNT +#define RTP_SINK_BUFFER_SIZE ((NUM_TS_PKTS_PER_RTP_PKT*MPEG_TS_PKT_SIZE)+RTP_HDR_SIZE) + +void ctn91xx_rtp_setup(ctn91xx_dev_t* dev, uint8_t* buf, uint32_t len); +void ctn91xx_rtp_destroy(ctn91xx_dev_t* dev, uint8_t* buf, uint32_t len); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +void ctn91xx_rtp_reader(void* work); +#else +void ctn91xx_rtp_reader(struct work_struct* work); +#endif + +#endif diff --git a/ctn91xx_structs.h b/ctn91xx_structs.h new file mode 100644 index 0000000..6c9381e --- /dev/null +++ b/ctn91xx_structs.h @@ -0,0 +1,295 @@ +#ifndef CTN91XX_STRUCTS_H +#define CTN91XX_STRUCTS_H + +#define CTN91XX_MAGIC 0x1B7C0004 + +/* Structure of the interrupt data on the card */ +typedef struct cablecard_interrupt { + uint8_t origin; /* offset 0 */ + uint8_t control_byte; /* offset 1 */ + uint16_t length; /* offset 2 */ + uint8_t type; /* offset 4 */ + uint8_t interrupt_set; /* offset 5 */ + uint16_t reserved; /* offset 6 */ + uint8_t seqno; /* offset 8 */ +} cablecard_interrupt_t; + + +struct ctn91xx_dev; + +typedef struct ctn91xx_dev ctn91xx_dev_t; + +typedef struct ctn91xx_dev_fd_state { + ctn91xx_dev_t* dev; + int minor; +} ctn91xx_dev_fd_state_t; + +#define SIZEOF_EVENT_HEADER 6 + +struct ctn91xx_event { + uint8_t type; + uint16_t length; + uint8_t origin; + uint8_t control_byte; + uint8_t seqno; + uint8_t should_queue; + + struct list_head list; +}; +typedef struct ctn91xx_event ctn91xx_event_t; + +typedef struct { + uint32_t last_cc; + uint32_t discontinuities; + uint32_t count; + uint32_t out_of_sync; +} pid_cc_t; + +typedef struct { + uint8_t remote_hw_addr[6]; + uint8_t local_hw_addr[6]; + uint32_t remote_ip_addr; + uint32_t local_ip_addr; + uint16_t remote_port; + uint16_t local_port; + + uint8_t got_setup; + uint8_t running; + + uint8_t* buffer; + uint32_t buffer_used; + + uint32_t start_rtp_time; + uint32_t start_seq; + uint32_t seq_no; + uint32_t ip_seq_no; + ktime_t prev_time; + uint32_t rtp_time_delta; + uint32_t ssrc; + + struct work_struct reader; + +} rtp_state_t; + +typedef struct { + uint8_t csrc_len:4; + uint8_t extension:1; + uint8_t padding:1; + uint8_t version:2; + + uint8_t payload:7; + uint8_t marker:1; + + uint16_t seq_no; + uint32_t timestamp; + uint32_t ssrc; +} rtp_header_t; + +struct vbuffer { + uint32_t npages; + uint8_t** buffers; + dma_addr_t* dma_addrs; + uint32_t read_idx; + uint32_t write_idx; + uint32_t notify_idx; + struct page** pages; + spinlock_t lock; + + /** + * array of size npages indicating how many + * users are using the page. greater than 0 + * means the page has been mapped + */ + uint8_t* lock_cnt; + uint8_t* dropped; + + /** + * array of size npages telling the current + * number of bytes in each page + */ + uint16_t* remaining; + uint16_t* sizes; + + /** + * Buffer for storing and copying any headers used in MPEG_SEND + * vmalloc'd + */ + uint8_t* header_buffer; + struct page* header_buffer_page; + + int tuner_index; + uint32_t packet_count; + + ctn91xx_dev_t* dev; + +#if USE_PCI + rtp_state_t rtp_state; +#endif +}; + +typedef struct vbuffer vbuffer_t; + +typedef struct { + /* i2c stuff */ + uint32_t read_index; + uint32_t read_len; + uint32_t write_index; + uint8_t state; + uint8_t error; + i2c_write_read_t msg; +} ctn91xx_i2c_state_t; + +typedef struct { + struct mutex mutex; + wait_queue_head_t wait_queue; + spit_write_read_t msg; + uint8_t* write_buffer; + uint8_t* read_buffer; + uint8_t error; + uint8_t done; +} ctn91xx_spit_state_t; + +typedef struct { + ctn91xx_dev_t* ctn91xx_dev; + struct net_device_stats stats; + spinlock_t lock; + int tx_outstanding; + struct sk_buff* tx_skb; +} ctn91xx_net_priv_t; + +typedef struct { + uint8_t value; + uint8_t is_input; + uint8_t hw_is_input; +} ctn91xx_gpio_t; + +typedef struct { + + uint8_t next_cgms_value; + uint8_t cgms_value; + uint8_t in_class_cgms; + uint8_t checksum; + uint8_t cont; + ktime_t last_event_time; + +} ctn91xx_scte21_state_t; + + +typedef struct { + //these three are used by hardware + uint32_t ctrl; + uint32_t dma_addr; //physical address + uint32_t nxt_addr; //physical address + + dma_addr_t paddr; //this descriptors physical address + void* priv; + uint8_t* virt; + struct list_head list; +} dma_desc_t; + +typedef struct { + ctn91xx_dev_t* dev; + + struct list_head desc_pool; + spinlock_t desc_pool_lock; + int num_tx_desc_active; + int num_rx_desc_active; + + uint8_t* buffers; + uint64_t* phys_addrs; + + struct net_device* netdev; + uint32_t num_interrupts; + +} ctn91xx_eth_t; + +struct ctn91xx_dev { + int magic; + void __iomem* base; + unsigned long hw_reg_base; + void __iomem* translation_base; + unsigned long translation_hw_reg_base; + + void __iomem* mpeg_filter_dma_base; + void __iomem* mpeg_dma_base; + void __iomem* system_control_base; + void __iomem* msg_base; + void __iomem* timer_reg_base; + + +#if USE_PCI + struct pci_dev* pdev; +#endif + + //pci side + uint8_t board_number; + int board_booting; + int face_present; + int ticks_per_us; + int irq; + int int_enable; + struct class* class; + + //leon side + int slot_number; + int is_noah; + int face_attached; + int face_supports_reset; + int face_reset_caps_detected; + + struct cdev ctrl_cdev; + struct cdev mpeg_cdev; + + uint8_t card_status; + uint16_t release_version; + uint16_t chip_version; + uint32_t board_version; //deprecated + + uint32_t which_proc; + + uint32_t always_scard; + uint32_t dma_timeout; + + /* event queue */ + spinlock_t event_queue_lock; + struct list_head event_queue; + wait_queue_head_t event_waitqueue; + int event_user_cnt; + + /* delayed event queue */ + spinlock_t delayed_event_queue_lock; + struct list_head delayed_event_queue; + struct work_struct delayed_event_worker; + struct work_struct delayed_slot_number_worker; + struct delayed_work gpio_poll; + + struct mutex fd_mutex; + + spinlock_t device_lock; + spinlock_t isr_lock; + + /* mpeg stuff */ + vbuffer_t mpeg_buffer[NUM_MPEG_DEVICES]; + uint32_t mpeg_user_cnt[NUM_MPEG_DEVICES]; + wait_queue_head_t mpeg_wait_queues[NUM_MPEG_DEVICES]; + + + /* net device and msg buffer stuff */ + struct net_device* net_dev; + wait_queue_head_t msg_buffer_wait_queue; + uint8_t* rpc_buffer; + + /* stats */ + ctn91xx_stats_t stats; + +#if CHECK_NOTIFY_CC + pid_cc_t pid_cc_notify[6][0x1fff]; +#endif + +}; + +#define WRITE_LOCK() spin_lock_w_flags(&dev->device_lock) +#define WRITE_RELOCK() spin_relock_w_flags(&dev->device_lock) +#define WRITE_UNLOCK() spin_unlock_w_flags(&dev->device_lock) + +#endif diff --git a/ctn91xx_util.c b/ctn91xx_util.c new file mode 100644 index 0000000..c343786 --- /dev/null +++ b/ctn91xx_util.c @@ -0,0 +1,579 @@ +#include "ctn91xx_util.h" +#include "ctn91xx_interrupt.h" +#include "ctn91xx_mpeg.h" +#include "ctn91xx_event.h" +#include "ctn91xx_rtp.h" + + +#if !USE_LEON +#include +#endif + +int ctn91xx_board_init(ctn91xx_dev_t* dev) +{ + static int board_ctr = 0; +#if HAS_MPEG_DMA + uint32_t i; +#endif + + dev->magic = CTN91XX_MAGIC; + + dev->int_enable = 0; + dev->board_number = board_ctr++; + + INIT_LIST_HEAD(&dev->event_queue); + INIT_LIST_HEAD(&dev->delayed_event_queue); + + mutex_init(&dev->fd_mutex); + spin_lock_init(&dev->event_queue_lock); + spin_lock_init(&dev->delayed_event_queue_lock); + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + INIT_WORK( &dev->delayed_event_worker, ctn91xx_delayed_event_worker, &dev->delayed_event_worker ); + INIT_WORK( &dev->delayed_slot_number_worker, ctn91xx_delayed_slot_number_worker, &dev->delayed_slot_number_worker ); +#else + INIT_WORK( &dev->delayed_event_worker, ctn91xx_delayed_event_worker ); + INIT_WORK( &dev->delayed_slot_number_worker, ctn91xx_delayed_slot_number_worker ); +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +#else +#endif + + spin_lock_init(&dev->isr_lock); + spin_lock_init(&dev->device_lock); + + init_waitqueue_head( &dev->msg_buffer_wait_queue ); + + dev->rpc_buffer = kmalloc( RPC_BUFFER_SIZE, GFP_KERNEL ); + + if( !dev->rpc_buffer ) { + goto out_of_memory; + } + +#if USE_LEON +#endif + +#if HAS_MPEG_DMA + + dev->dma_timeout = 540000; //20ms in 27mhz cycles + + for(i=0; impeg_buffer[i]; + vbuffer->dev = dev; + spin_lock_init(&vbuffer->lock); + vbuffer->npages = pages_per_mpeg_device(i); + vbuffer->tuner_index = i; + + vbuffer->read_idx = 0; + vbuffer->write_idx = 0; + vbuffer->notify_idx = 0; + + vbuffer->buffers = kzalloc(sizeof(void*) * vbuffer->npages, GFP_KERNEL); + vbuffer->dma_addrs = kmalloc(sizeof(dma_addr_t) * vbuffer->npages, GFP_KERNEL); + vbuffer->lock_cnt = kmalloc( vbuffer->npages, GFP_KERNEL ); + vbuffer->dropped = kmalloc( vbuffer->npages, GFP_KERNEL ); + vbuffer->remaining = kmalloc( vbuffer->npages * sizeof(uint16_t), GFP_KERNEL ); + vbuffer->sizes = kmalloc(vbuffer->npages * sizeof(uint16_t), GFP_KERNEL); + vbuffer->pages = kmalloc(vbuffer->npages * sizeof(struct page*), GFP_KERNEL ); + + if(!vbuffer->lock_cnt + || !vbuffer->dropped + || !vbuffer->remaining + || !vbuffer->sizes + || !vbuffer->buffers + || !vbuffer->dma_addrs + || !vbuffer->pages) { + goto out_of_memory; + } + + for( j=0; jnpages; j++ ) { +#if USE_PCI + vbuffer->buffers[j] = pci_alloc_consistent( dev->pdev, PAGE_SIZE, &vbuffer->dma_addrs[j] ); +#else + vbuffer->buffers[j] = kmalloc(PAGE_SIZE, GFP_KERNEL); + if( vbuffer->buffers[j] ) { + vbuffer->dma_addrs[j] = virt_to_phys(vbuffer->buffers[j]); + } +#endif + if( !vbuffer->buffers[j] ) { + ERROR("could not alloc mpeg stream %d (%d)", i, j); + goto out_of_memory; + } + + vbuffer->pages[j] = virt_to_page(vbuffer->buffers[j]); + memset(vbuffer->buffers[j], 0xaa, PAGE_SIZE); + + vbuffer->lock_cnt[j] = 0; + vbuffer->dropped[j] = 0; + vbuffer->sizes[j] = 0; + vbuffer->remaining[j] = 0; + } + + vbuffer->header_buffer = vmalloc( PAGE_SIZE ); + + if( !vbuffer->header_buffer ) { + goto out_of_virtual_memory; + } + + vbuffer->header_buffer_page = vmalloc_to_page( vbuffer->header_buffer ); + +#if USE_PCI + vbuffer->rtp_state.buffer = vmalloc( RTP_SINK_BUFFER_SIZE ); + + if( !vbuffer->rtp_state.buffer ) { + vfree( vbuffer->header_buffer ); + goto out_of_virtual_memory; + } + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + INIT_WORK( &vbuffer->rtp_state.reader, ctn91xx_rtp_reader, &vbuffer->rtp_state.reader ); +#else + INIT_WORK( &vbuffer->rtp_state.reader, ctn91xx_rtp_reader ); +#endif +#endif + init_waitqueue_head(&dev->mpeg_wait_queues[i]); + } +#endif + + init_waitqueue_head(&dev->event_waitqueue); + return 1; + +out_of_memory: + ERROR("out of memory on board init\n"); + return 0; + +#if HAS_MPEG_DMA +out_of_virtual_memory: + ERROR("out of virtual memory on board init\n"); + return 0; +#endif +} + +void ctn91xx_board_uninit_dma_pool(ctn91xx_dev_t* dev) +{ + int i; + + dev->event_user_cnt = 0; + for(i=0; impeg_user_cnt[i] = 0; + } + + //wait for the dma's to be done + //before trying to unmap the pages + msleep(100); + +#if HAS_MPEG_DMA + for(i=0; impeg_buffer[i]; + int last_index_to_unmap = WRAPPED_PAGE_INDEX(vbuffer, vbuffer->write_idx + (2*num_pages)); + int j; + + spin_lock_w_flags(&vbuffer->lock); + + for(j = vbuffer->notify_idx; j != last_index_to_unmap; j = (j+1) % vbuffer->npages) { + + if(vbuffer->lock_cnt[j] > 0) { + ctn91xx_mpeg_unmap_page(dev, vbuffer, j, 0, i); + } + } + + spin_unlock_w_flags(&vbuffer->lock); + + for( j=0; jnpages; j++ ) { +#if USE_PCI + pci_free_consistent( dev->pdev, PAGE_SIZE, vbuffer->buffers[j], vbuffer->dma_addrs[j] ); +#else + kfree( vbuffer->buffers[j] ); +#endif + } + + vfree( vbuffer->header_buffer ); + + kfree( vbuffer->buffers ); + kfree( vbuffer->lock_cnt ); + kfree( vbuffer->dropped ); + kfree( vbuffer->remaining ); + kfree( vbuffer->sizes ); + kfree( vbuffer->pages ); + kfree( vbuffer->dma_addrs ); + } +#endif +} + +void ctn91xx_board_uninit(ctn91xx_dev_t* dev) +{ + ctn91xx_event_cleanup_waiting(dev); + +#if USE_LEON +#endif + + if( dev->rpc_buffer ) { + kfree( dev->rpc_buffer ); + dev->rpc_buffer = NULL; + } + + ctn91xx_board_uninit_dma_pool(dev); +} + +#if PRINT_RW +ctn91xx_dev_t* gdev = NULL; +ctn91xx_set_dev(ctn91xx_dev_t* dev) { + gdev = dev; +} +#endif + +void ctn91xx_orin8(uint8_t data, void* __iomem base, unsigned long offset) +{ + BUG_ON(!base); + ctn91xx_write8( (ctn91xx_read8(base, offset) | data), base, offset ); +} + +void ctn91xx_andin8(uint8_t data, void* __iomem base, unsigned long offset) +{ + BUG_ON(!base); + ctn91xx_write8( (ctn91xx_read8(base, offset) & data), base, offset ); +} + +void ctn91xx_orin32(uint32_t data, void* __iomem base, unsigned long offset) +{ + BUG_ON(!base); + ctn91xx_write32( (ctn91xx_read32(base, offset) | data), base, offset ); +} + +void ctn91xx_andin32(uint32_t data, void* __iomem base, unsigned long offset) +{ + BUG_ON(!base); + ctn91xx_write32( (ctn91xx_read32(base, offset) & data), base, offset ); +} + + +void ctn91xx_write8(uint8_t byte, void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("w8 0x%08x 0x%02x", base+offset, byte); + ctn91xx_busywait(gdev, 1000); +#endif +#if USE_PCI + writeb(byte, ((uint8_t*)base)+offset); +#if READ_FOR_EVERY_WRITE + readb(((uint8_t*)base)+offset); +#endif +#elif USE_LEON + __raw_writeb( byte, ((uint8_t*)base)+offset ); +#endif + } +} + +void ctn91xx_write16(uint16_t data, void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("w16 0x%08x 0x%02x", base+offset, data); + ctn91xx_busywait(gdev, 1000); +#endif +#if USE_PCI + writew(data, ((uint8_t*)base)+offset); +#if READ_FOR_EVERY_WRITE + readw(((uint8_t*)base)+offset); +#endif +#elif USE_LEON + __raw_writew( data, ((uint8_t*)base)+offset ); +#endif + } +} + +void ctn91xx_write32(uint32_t data, void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("w32 0x%08x 0x%02x", base+offset, data); + ctn91xx_busywait(gdev, 1000); +#endif +#if USE_PCI + writel(data, ((uint8_t*)base)+offset); +#if READ_FOR_EVERY_WRITE + readl(((uint8_t*)base)+offset); +#endif +#elif USE_LEON + __raw_writel( data, ((uint8_t*)base)+offset ); +#endif + } +} + +#if USE_LEON +void ctn91xx_write64(uint64_t data, void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("w64 0x%08x 0x%02x", base+offset, data); + ctn91xx_busywait(gdev, 1000); +#endif + writell(data, ((uint8_t*)base)+offset); +#if READ_FOR_EVERY_WRITE + data = readll(((uint8_t*)base)+offset); +#endif + } +} +#endif + +uint8_t ctn91xx_read8(void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("r8 0x%08x", base+offset); + ctn91xx_busywait(gdev, 1000); +#endif +#if USE_PCI + return readb(((uint8_t*)base)+offset); +#elif USE_LEON + return __raw_readb(((uint8_t*)base)+offset); +#endif + } else { + ctn91xx_dev_t* dev = NULL; + ERROR("read from unmapped memory"); + return 0xff; + } +} + +uint16_t ctn91xx_read16(void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("r16 0x%08x", base+offset); + ctn91xx_busywait(gdev, 1000); +#endif +#if USE_PCI + return readw(((uint8_t*)base)+offset); +#elif USE_LEON + return __raw_readw(((uint8_t*)base)+offset); +#endif + } else { + ctn91xx_dev_t* dev = NULL; + ERROR("read from unmapped memory"); + return 0xffff; + } +} + +uint32_t ctn91xx_read32(void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { +#if PRINT_RW + INFO("r32 0x%08x", base+offset); + ctn91xx_busywait(gdev, 1000); +#endif +#if USE_PCI + return readl(((uint8_t*)base)+offset); +#elif USE_LEON + return __raw_readl(((uint8_t*)base)+offset); +#endif + } else { + ctn91xx_dev_t* dev = NULL; + ERROR("read from unmapped memory"); + return 0xffffffff; + } +} + +uint64_t ctn91xx_read64(void __iomem* base, unsigned long offset) +{ + BUG_ON(!base); + if (base != 0) { + return readll(((uint8_t*)base)+offset); + } else { + ctn91xx_dev_t* dev = NULL; + ERROR("read from unmapped memory"); + return 0xffffffffffffffffULL; + } +} + +uint16_t ctn91xx_release_version(ctn91xx_dev_t* dev) { + + return dev->release_version; +} + +uint16_t ctn91xx_chip_version(ctn91xx_dev_t* dev) { + + return dev->chip_version; +} + +//deprecated +uint32_t ctn91xx_board_version(ctn91xx_dev_t* dev) { + + return dev->board_version; +} + +uint32_t ctn91xx_which_proc(ctn91xx_dev_t* dev) +{ + return DMA_PROC | DRM_PROC | CC_PROC | EXT_PROC; + return dev->which_proc; +} + +uint64_t ctn91xx_gettick(ctn91xx_dev_t* dev) +{ + return ctn91xx_read64(dev->system_control_base, SYSTEM_CONTROL_UP_TIME_OFFSET); +} + +uint64_t ctn91xx_up_time(ctn91xx_dev_t* dev) +{ + uint64_t cur_tick = ctn91xx_gettick(dev); + uint64_t ns; +#if USE_LEON + ns = (cur_tick / (uint64_t)dev->ticks_per_us)*1000ULL; +#else + do_div(cur_tick, (uint64_t)dev->ticks_per_us); + ns = cur_tick*1000ULL; +#endif + return ns; +} + +void ctn91xx_busywait(ctn91xx_dev_t* dev, uint32_t us) { + uint64_t ns = us*1000ULL; + uint64_t s = ctn91xx_up_time(dev); + uint64_t e; + do + { + e = ctn91xx_up_time(dev); + } while(e - s < ns); +} + +void ctn91xx_mod_sdump_buffer( const char * module_name, const char * function, int line, const uint8_t* buf, uint32_t length, const char* name) +{ + int i, j, i_valid; + u32 remainder = (length % 16); + u32 top_len = length + (remainder ? 16 - remainder : 0); + u32 char_index; + u8 c; + + if (name) { + printk("%s:%i DUMP: \"%s\" (%p) length %d\n", function, line, name, buf, length); + } else { + printk("%s:%i DUMP: (%p) length %d\n", function, line, buf, length); + } + + printk("%08d ", 0); + + for(i=0; i= '0' && c <= 'z') ? c : '.'); + } else { + printk("%c", ' '); + } + } + if (i == top_len - 1) { + printk("|"); + } else { + printk("|\n%08d ", i+1); + } + + } else if (i % 8 == 7) { + printk(" "); + } + + + } + printk( "\n"); +} + +void ctn91xx_mod_pci_cfg_sdump_buffer( const char * module_name, const char * function, int line, struct pci_dev* pdev, int offset, uint32_t length, const char* name) +{ + int i, j, i_valid; + u32 remainder = (length % 16); + u32 top_len = length + (remainder ? 16 - remainder : 0); + u32 char_index; + u8 c; + u8 b; + + if (name) { + printk("%s:%i DUMP: \"%s\" (%d) length %d\n", function, line, name, offset, length); + } else { + printk("%s:%i DUMP: (%d) length %d\n", function, line, offset, length); + } + + printk("%08d ", 0); + + for(i=0; i= '0' && c <= 'z') ? c : '.'); + } else { + printk("%c", ' '); + } + } + if (i == top_len - 1) { + printk("|"); + } else { + printk("|\n%08d ", i+1); + } + + } else if (i % 8 == 7) { + printk(" "); + } + + + } + printk( "\n"); +} + +uint8_t solstice_bus_id(uint8_t i2c_addr) { + + switch(i2c_addr >> 1) { + case 0x61: + return 0; //First Xceive + case 0x64: + return 1; //Second Xceive + case 0x40: + case 0x47: + case 0x26: + return 2; //Auvitek's and CPLD + case 0x29: + return 3; //DRXj + } + + return 0; //doesn't really matter what we pick cause nothing will respond anyway +} + +#if USE_LEON +#endif diff --git a/ctn91xx_util.h b/ctn91xx_util.h new file mode 100644 index 0000000..06a2d78 --- /dev/null +++ b/ctn91xx_util.h @@ -0,0 +1,60 @@ +#ifndef CTN91XX_UTIL_H +#define CTN91XX_UTIL_H + +#include "ctn91xx.h" + + +int ctn91xx_board_init(ctn91xx_dev_t* dev); +void ctn91xx_board_uninit(ctn91xx_dev_t* dev); + +uint16_t ctn91xx_release_version(ctn91xx_dev_t* dev); +uint16_t ctn91xx_chip_version(ctn91xx_dev_t* dev); +uint32_t ctn91xx_board_version(ctn91xx_dev_t* dev); //deprecated + +void ctn91xx_write8(uint8_t byte, void __iomem* base, unsigned long offset); +void ctn91xx_write16(uint16_t data, void __iomem* base, unsigned long offset); +void ctn91xx_write32(uint32_t data, void __iomem* base, unsigned long offset); +void ctn91xx_write64(uint64_t data, void __iomem* base, unsigned long offset); + +uint8_t ctn91xx_read8(void __iomem* base, unsigned long offset); +uint16_t ctn91xx_read16(void __iomem* base, unsigned long offset); +uint32_t ctn91xx_read32(void __iomem* base, unsigned long offset); +uint64_t ctn91xx_read64(void __iomem* base, unsigned long offset); + +void ctn91xx_orin8(uint8_t data, void* __iomem base, unsigned long offset); +void ctn91xx_andin8(uint8_t data, void* __iomem base, unsigned long offset); +void ctn91xx_orin32(uint32_t data, void* __iomem base, unsigned long offset); +void ctn91xx_andin32(uint32_t data, void* __iomem base, unsigned long offset); + +void ctn91xx_face_gpio_read_write_cycle( ctn91xx_dev_t* dev ); +void ctn91xx_face_gpio_read_write_cycle_ex( ctn91xx_dev_t* dev, uint8_t print ); + +uint32_t ctn91xx_origin_is_tuner(uint8_t o); +uint32_t ctn91xx_interrupt_is_error(uint8_t type); + +uint32_t ctn91xx_which_proc(ctn91xx_dev_t* dev); + +uint8_t solstice_bus_id(uint8_t i2c_addr); + +uint64_t ctn91xx_gettick(ctn91xx_dev_t* dev); +uint64_t ctn91xx_up_time(ctn91xx_dev_t* dev); +void ctn91xx_busywait(ctn91xx_dev_t* dev, uint32_t us); + +void ctn91xx_bandwidth_test(ctn91xx_dev_t* dev); +void ctn91xx_network_bandwidth_test(ctn91xx_dev_t* dev, struct file* out_file); + + +#define sdump_buffer(buffer, length, name) ctn91xx_mod_sdump_buffer( "ctn91xx", __FUNCTION__, __LINE__, (buffer), (length), (name)) +void ctn91xx_mod_sdump_buffer( const char * module_name, const char * function, int line, const uint8_t* buf, uint32_t length, const char* name); + +#define pci_cfg_sdump_buffer(pdev, offset, length, name) ctn91xx_mod_pci_cfg_sdump_buffer( "ctn91xx", __FUNCTION__, __LINE__, (pdev), (offset), (length), (name)) +void ctn91xx_mod_pci_cfg_sdump_buffer( const char * module_name, const char * function, int line, struct pci_dev* pdev, int offset, uint32_t length, const char* name); + +int descriptor_pool_create(ctn91xx_eth_t* eth); +void descriptor_pool_destroy(ctn91xx_eth_t* eth); +dma_desc_t* descriptor_pool_get(ctn91xx_eth_t* eth, int for_tx); +void descriptor_pool_put(ctn91xx_eth_t* eth, dma_desc_t* desc, int for_tx); + +void print_desc_list(ctn91xx_dev_t* dev, struct list_head* list, const char* name); + +#endif