From fed4e577ab9f9691a1d0e3fd2f5130cd073b8776 Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Fri, 8 Nov 2024 11:09:47 +0300 Subject: [PATCH] Initial commit --- Makefile | 34 ++ Makefile.dkms | 23 ++ dkms/dkms.conf | 8 + fliusb.c | 819 +++++++++++++++++++++++++++++++++++++++++++++++++ fliusb.h | 132 ++++++++ fliusb_ioctl.h | 89 ++++++ 6 files changed, 1105 insertions(+) create mode 100644 Makefile create mode 100644 Makefile.dkms create mode 100644 dkms/dkms.conf create mode 100644 fliusb.c create mode 100644 fliusb.h create mode 100644 fliusb_ioctl.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d16eb2a --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +modname := fliusb +obj-m := $(modname).o + +KVERSION := $(shell uname -r) +KDIR := /lib/modules/$(KVERSION)/build +PWD := "$$(pwd)" + +ifdef DEBUG +CFLAGS_$(obj-m) := -DDEBUG -Wall -Werror +endif + +# enable scatter-gather reads +#EXTRA_CFLAGS += -DSGREAD + +default: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) O=$(PWD) -C $(KDIR) M=$(PWD) clean + +load: + -rmmod $(modname) + insmod $(modname).ko + +install: + mkdir -p /lib/modules/$(KVERSION)/misc/$(modname) + install -m 0644 -o root -g root $(modname).ko /lib/modules/$(KVERSION)/misc/$(modname) + depmod -a + +uninstall: + rm /lib/modules/$(KVERSION)/misc/$(modname)/$(modname).ko + rmdir /lib/modules/$(KVERSION)/misc/$(modname) + rmdir /lib/modules/$(KVERSION)/misc + depmod -a diff --git a/Makefile.dkms b/Makefile.dkms new file mode 100644 index 0000000..570ebae --- /dev/null +++ b/Makefile.dkms @@ -0,0 +1,23 @@ +modname := fliusb +DKMS := dkms +modver := 1.3 + +# directory in which generated files are stored +DKMS_DEST := /usr/src/$(modname)-$(modver) + +all: install + +src_install: + mkdir -p '$(DKMS_DEST)' + cp Makefile fliusb* dkms/dkms.conf '$(DKMS_DEST)' + +build: src_install + $(DKMS) build -m fliusb -v $(modver) + +install: build + $(DKMS) install -m fliusb -v $(modver) + +uninstall: + $(DKMS) remove -m fliusb -v $(modver) --all + +.PHONY: all src_install build install uninstall \ No newline at end of file diff --git a/dkms/dkms.conf b/dkms/dkms.conf new file mode 100644 index 0000000..5c29096 --- /dev/null +++ b/dkms/dkms.conf @@ -0,0 +1,8 @@ +PACKAGE_VERSION="1.3" +PACKAGE_NAME="fliusb" +MAKE[0]="make KVER=$kernelver" +CLEAN="make clean" +BUILT_MODULE_NAME[0]="fliusb" +DEST_MODULE_LOCATION[0]="/kernel/drivers/usb/misc" +AUTOINSTALL="yes" + diff --git a/fliusb.c b/fliusb.c new file mode 100644 index 0000000..868f900 --- /dev/null +++ b/fliusb.c @@ -0,0 +1,819 @@ +/* + + Copyright (c) 2005 Finger Lakes Instrumentation (FLI), L.L.C. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + Neither the name of Finger Lakes Instrumentation (FLI), L.L.C. + nor the names of its contributors may be used to endorse or + promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + ====================================================================== + + Finger Lakes Instrumentation, L.L.C. (FLI) + web: http://www.fli-cam.com + email: support@fli-cam.com + +*/ +/* + * 2017 Edward V. Emelianov + * Fix bug with kernel panic on >=4.9.4 (numpg = get_user_pages(numpg, 1, 0, dev->usbsg.userpg, NULL); - wrong arguments order) + * Module adopted for kernels >=4.9 + * Tested on kernels 4.9.4 and 4.12.5 (@ gentoo) + * + * 2022 Timur A. Fatkhullin + * Add support for kernel >= 5.19 + * Add support for kernel 6.x + * 2023 tested on <= 6.2.14 kernels + * 2023 Oct. compiled with <= 6.5.7 + * 2023 Dec. compiled with <= 6.6.5 + * 2024 Feb. compiled with <= 6.7.6 + * 2024 Oct. compiled with <= 6.10.11 + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +// https://lkml.org/lkml/2010/4/12/132 +#define usb_buffer_free(...) usb_free_coherent(__VA_ARGS__) +#define usb_buffer_alloc(...) usb_alloc_coherent(__VA_ARGS__) +#include +#else +#include +#endif +#ifdef SGREAD +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) +// https://patchwork.kernel.org/patch/6309271/ +#include +#endif +#endif // SGREAD + +#include "fliusb.h" +#include "fliusb_ioctl.h" + +MODULE_AUTHOR("Finger Lakes Instrumentation, L.L.C. "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_VERSION("1.4"); + +/* initMUTEX was removed in 2.6.37, I hate Linux, minor revision changes + * should include a warning when this shit is going to happen with + * suggested alternatives! */ + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36) && !defined(init_MUTEX) +#define init_MUTEX(sem) sema_init(sem, 1) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) +#define ACCESS_OK(a,b,c) access_ok(b, c) +#else +#define ACCESS_OK(a,b,c) access_ok(a, b, c) +#endif + +struct mutex fliusb_mutex; + +/* Module parameters */ +typedef struct { + unsigned int buffersize; + unsigned int timeout; +} fliusb_param_t; + +static fliusb_param_t defaults = { + .buffersize = FLIUSB_BUFFERSIZE, + .timeout = FLIUSB_TIMEOUT, +}; + +#define FLIUSB_MOD_PARAMETERS \ + FLIUSB_MOD_PARAM(buffersize, uint, "USB bulk transfer buffer size") \ + FLIUSB_MOD_PARAM(timeout, uint, "USB bulk transfer timeout (msec)") + +#define FLIUSB_MOD_PARAM(var, type, desc) \ + module_param_named(var, defaults.var, type, S_IRUGO); \ + MODULE_PARM_DESC(var, desc); + +FLIUSB_MOD_PARAMETERS; + +#undef FLIUSB_MOD_PARAM + +/* Devices supported by this driver */ +static struct usb_device_id fliusb_table[] = { +#define FLIUSB_PROD(name, prodid) { USB_DEVICE(FLIUSB_VENDORID, prodid) }, + FLIUSB_PRODUCTS +#undef FLIUSB_PROD + {}, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, fliusb_table); + +/* Forward declarations of file operation functions */ +static int fliusb_open(struct inode *inode, struct file *file); +static int fliusb_release(struct inode *inode, struct file *file); +static ssize_t fliusb_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos); +static ssize_t fliusb_write(struct file *file, const char __user *user_buffer, + size_t count, loff_t *ppos); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36) +static long fliusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +#else +static int fliusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); +#endif + +static struct file_operations fliusb_fops = { + .owner = THIS_MODULE, + .read = fliusb_read, + .write = fliusb_write, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36) + .unlocked_ioctl = fliusb_ioctl, +#else + .ioctl = fliusb_ioctl, +#endif + .open = fliusb_open, + .release = fliusb_release, +}; + +static struct usb_class_driver fliusb_class = { + .name = "usb/" FLIUSB_NAME "%d", + .fops = &fliusb_fops, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14)) + .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, +#endif + .minor_base = FLIUSB_MINOR_BASE, +}; + +/* Forward declarations of USB driver functions */ +static int fliusb_probe(struct usb_interface *interface, const struct usb_device_id *id); +static void fliusb_disconnect(struct usb_interface *interface); + +static struct usb_driver fliusb_driver = { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14)) + .owner = THIS_MODULE, +#endif + .name = FLIUSB_NAME, + .probe = fliusb_probe, + .disconnect = fliusb_disconnect, + .id_table = fliusb_table, +}; + +static void fliusb_delete(struct kref *kref){ + fliusb_t *dev = container_of(kref, fliusb_t, kref); + usb_put_dev(dev->usbdev); + if (dev->buffer) + kfree(dev->buffer); + kfree(dev); + return; +} + +static int fliusb_allocbuffer(fliusb_t *dev, unsigned int size){ + void *tmp; + int err = 0; + if (size == 0) + return -EINVAL; + if (down_interruptible(&dev->buffsem)) + return -ERESTARTSYS; + if ((tmp = kmalloc(size, GFP_KERNEL)) == NULL) { + FLIUSB_WARN("kmalloc() failed: could not allocate %d byte buffer", size); + err = -ENOMEM; + goto done; + } + if (dev->buffer != NULL) + kfree(dev->buffer); + dev->buffer = tmp; + dev->buffersize = size; + +done: + up(&dev->buffsem); + return err; +} + +static int fliusb_open(struct inode *inode, struct file *file){ + fliusb_t *dev; + struct usb_interface *interface; + int minor = iminor(inode); + + if ((interface = usb_find_interface(&fliusb_driver, minor)) == NULL){ + FLIUSB_ERR("no interface found for minor number %d", minor); + return -ENODEV; + } + if ((dev = usb_get_intfdata(interface)) == NULL){ + FLIUSB_WARN("no device data for minor number %d", minor); + return -ENODEV; + } + /* increment usage count */ + kref_get(&dev->kref); + /* save a pointer to the device structure for later use */ + file->private_data = dev; + return 0; +} + +static int fliusb_release(struct inode *inode, struct file *file){ + fliusb_t *dev; + if ((dev = file->private_data) == NULL){ + FLIUSB_ERR("no device data for minor number %d", iminor(inode)); + return -ENODEV; + } + /* decrement usage count */ + kref_put(&dev->kref, fliusb_delete); + return 0; +} + +static int fliusb_simple_bulk_read(fliusb_t *dev, unsigned int pipe, char __user *userbuffer, + size_t count, unsigned int timeout){ + int err, cnt; + + if (count > dev->buffersize) + count = dev->buffersize; + if (!ACCESS_OK(VERIFY_WRITE, userbuffer, count)) + return -EFAULT; + if (down_interruptible(&dev->buffsem)) + return -ERESTARTSYS; + /* a simple blocking bulk read */ + if ((err = usb_bulk_msg(dev->usbdev, pipe, dev->buffer, count, &cnt, timeout))){ + cnt = err; + goto done; + } + + if (__copy_to_user(userbuffer, dev->buffer, cnt)) + cnt = -EFAULT; +done: + up(&dev->buffsem); + return cnt; +} + +#ifdef SGREAD + +static void fliusb_sg_bulk_read_timeout(unsigned long data){ + fliusb_t *dev = (fliusb_t *)data; + FLIUSB_ERR("bulk read timed out"); + usb_sg_cancel(&dev->usbsg.sgreq); + return; +} + +static int fliusb_sg_bulk_read(fliusb_t *dev, unsigned int pipe, char __user *userbuffer, + size_t count, unsigned int timeout){ + int err, i; + unsigned int numpg; + size_t pgoffset; + + /* userbuffer must be aligned to a multiple of the endpoint's + maximum packet size + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) + if ((size_t)userbuffer % usb_maxpacket(dev->usbdev, pipe, 0)){ + FLIUSB_ERR("user buffer is not properly aligned: 0x%p %% 0x%04x", userbuffer, + usb_maxpacket(dev->usbdev, pipe, 0)); +#else + if ((size_t)userbuffer % usb_maxpacket(dev->usbdev, pipe)){ + FLIUSB_ERR("user buffer is not properly aligned: 0x%p %% 0x%04x", userbuffer, + usb_maxpacket(dev->usbdev, pipe)); +#endif + return -EINVAL; + } + pgoffset = (size_t)userbuffer & (PAGE_SIZE - 1); + // COUNT: numpg=16, count=65536, pgoffset=0, PAGE_SIZE=4096 + numpg = ((count + pgoffset - 1) >> PAGE_SHIFT) + 1; + FLIUSB_DBG("COUNT: numpg=%u, count=%zd, pgoffset=%zd, PAGE_SIZE=%lu, dev->usbsg.maxpg=%u", numpg, count, pgoffset, PAGE_SIZE, dev->usbsg.maxpg); + if (numpg > dev->usbsg.maxpg) { + numpg = dev->usbsg.maxpg; + count = PAGE_SIZE - pgoffset + PAGE_SIZE * (dev->usbsg.maxpg - 1); + } + if (down_interruptible(&dev->usbsg.sem)) + return -ERESTARTSYS; + down_read(¤t->mm->mmap_sem); + FLIUSB_DBG("get_user_pages"); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +/*long get_user_pages(unsigned long start, unsigned long nr_pages, + unsigned int gup_flags, struct page **pages, + struct vm_area_struct **vmas);*/ + numpg = get_user_pages((size_t)userbuffer & PAGE_MASK, numpg, 1, dev->usbsg.userpg, NULL); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) + numpg = get_user_pages_remote(current, current->mm, (size_t)userbuffer & PAGE_MASK, numpg, + 1, 0, dev->usbsg.userpg, NULL); +#else + numpg = get_user_pages(current, current->mm, (size_t)userbuffer & PAGE_MASK, numpg, 1, 0, + dev->usbsg.userpg, NULL); +#endif + up_read(¤t->mm->mmap_sem); + FLIUSB_DBG("got %u pages", numpg); + if (numpg <= 0){ + FLIUSB_ERR("get_user_pages() failed: %d", numpg); + err = numpg; + goto done; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) + dev->usbsg.slist[0].page = dev->usbsg.userpg[0]; + dev->usbsg.slist[0].offset = pgoffset; + dev->usbsg.slist[0].length = min(count, (size_t)(PAGE_SIZE - pgoffset)); +#else + sg_set_page(&dev->usbsg.slist[0], dev->usbsg.userpg[0], min(count, (size_t)(PAGE_SIZE - pgoffset)), pgoffset); +#endif + + if (numpg > 1) { + for (i = 1; i < numpg - 1; i++){ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) + dev->usbsg.slist[i].page = dev->usbsg.userpg[i]; + dev->usbsg.slist[i].offset = 0; + dev->usbsg.slist[i].length = PAGE_SIZE; + } + dev->usbsg.slist[i].page = dev->usbsg.userpg[i]; + dev->usbsg.slist[i].offset = 0; + dev->usbsg.slist[i].length = ((size_t)userbuffer + count) & (PAGE_SIZE - 1); + if (dev->usbsg.slist[i].length == 0) + dev->usbsg.slist[i].length = PAGE_SIZE; +#else + FLIUSB_DBG("sg_set_page %d", i); + sg_set_page(&dev->usbsg.slist[i], dev->usbsg.userpg[i], PAGE_SIZE, 0); + } + FLIUSB_DBG("sg_set_page last"); + if ((((size_t)userbuffer + count) & (PAGE_SIZE - 1)) == 0) + sg_set_page(&dev->usbsg.slist[i], dev->usbsg.userpg[i], PAGE_SIZE, 0); + else + sg_set_page(&dev->usbsg.slist[i], dev->usbsg.userpg[i], + ((size_t)userbuffer + count) & (PAGE_SIZE - 1), 0); +#endif + } + + if ((err = usb_sg_init(&dev->usbsg.sgreq, dev->usbdev, pipe, 0, dev->usbsg.slist, numpg, 0, + GFP_KERNEL))){ + FLIUSB_ERR("usb_sg_init() failed: %d", err); + goto done; + } + + dev->usbsg.timer.expires = jiffies + (timeout * HZ + 500) / 1000; + dev->usbsg.timer.data = (unsigned long)dev; + dev->usbsg.timer.function = fliusb_sg_bulk_read_timeout; + add_timer(&dev->usbsg.timer); + + /* wait for the transfer to complete */ + usb_sg_wait(&dev->usbsg.sgreq); + del_timer_sync(&dev->usbsg.timer); + + if (dev->usbsg.sgreq.status){ + FLIUSB_ERR("bulk read error %d; transfered %d bytes", (int)dev->usbsg.sgreq.status, + (int)dev->usbsg.sgreq.bytes); + err = dev->usbsg.sgreq.status; + goto done; + } + err = dev->usbsg.sgreq.bytes; + +done: + up(&dev->usbsg.sem); + + for (i = 0; i < numpg; i++){ + if (!PageReserved(dev->usbsg.userpg[i])) + SetPageDirty(dev->usbsg.userpg[i]); +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 6, 0) + FLIUSB_DBG("put_page %d", i); + put_page(dev->usbsg.userpg[i]); +#else + FLIUSB_DBG("page_cache_release"); + page_cache_release(dev->usbsg.userpg[i]); +#endif + } + + return err; +} + +#endif /* SGREAD */ + +static int fliusb_bulk_read(fliusb_t *dev, unsigned int pipe, char __user *userbuffer, + size_t count, unsigned int timeout){ + FLIUSB_DBG("pipe: 0x%08x; userbuffer: %p; count: %zd; timeout: %u", pipe, userbuffer, + count, timeout); +#ifdef SGREAD + if (count > dev->buffersize) + return fliusb_sg_bulk_read(dev, pipe, userbuffer, count, timeout); + else +#endif /* SGREAD */ + return fliusb_simple_bulk_read(dev, pipe, userbuffer, count, timeout); +} + +#ifndef ASYNCWRITE +static int fliusb_bulk_write(fliusb_t *dev, unsigned int pipe, const char __user *userbuffer, size_t count, + unsigned int timeout){ + int err, cnt; + + FLIUSB_DBG("pipe: 0x%08x; userbuffer: %p; count: %zd; timeout: %u", pipe, userbuffer, count, timeout); + if (count > dev->buffersize) + count = dev->buffersize; + if (down_interruptible(&dev->buffsem)) + return -ERESTARTSYS; + if (copy_from_user(dev->buffer, userbuffer, count)){ + cnt = -EFAULT; + goto done; + } + + /* a simple blocking bulk write */ + if ((err = usb_bulk_msg(dev->usbdev, pipe, dev->buffer, count, &cnt, timeout))){ + cnt = err; + // reset USB in case of an error + err = usb_reset_configuration(dev->usbdev); + FLIUSB_DBG("configuration return: %d", err); + } + +done: + up(&dev->buffsem); + return cnt; +} + +#else // !ASYNCWRITE + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) +static void fliusb_bulk_write_callback(struct urb *urb, struct pt_regs *regs) +#else +static void fliusb_bulk_write_callback(struct urb *urb) +#endif +{ + switch (urb->status){ + case 0: + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + break; /* These aren't noteworthy */ + + default: + FLIUSB_WARN("bulk write error %d; transfered %d bytes", urb->status, urb->actual_length); + break; + } + + usb_buffer_free(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); + return; +} + +static int fliusb_bulk_write(fliusb_t *dev, unsigned int pipe, const char __user *userbuffer, size_t count, + unsigned int timeout){ + int err; + struct urb *urb = NULL; + char *buffer = NULL; + + FLIUSB_DBG("pipe: 0x%08x; userbuffer: %p; count: %zd; timeout: %u", pipe, userbuffer, count, timeout); + if (!ACCESS_OK(VERIFY_READ, userbuffer, count)) + return -EFAULT; + if ((urb = usb_alloc_urb(0, GFP_KERNEL)) == NULL) + return -ENOMEM; + if ((buffer = usb_buffer_alloc(dev->usbdev, count, GFP_KERNEL, &urb->transfer_dma)) == NULL){ + err = -ENOMEM; + goto error; + } + if (__copy_from_user(buffer, userbuffer, count)){ + err = -EFAULT; + goto error; + } + + usb_fill_bulk_urb(urb, dev->usbdev, pipe, buffer, count, fliusb_bulk_write_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if ((err = usb_submit_urb(urb, GFP_KERNEL))) + goto error; + usb_free_urb(urb); + return count; + +error: + if (buffer) + usb_buffer_free(dev->usbdev, count, buffer, urb->transfer_dma); + if (urb) + usb_free_urb(urb); + + return err; +} + +#endif /* ASYNCWRITE */ + +static ssize_t fliusb_read(struct file *file, char __user *userbuffer, size_t count, loff_t *ppos){ + fliusb_t *dev; + + if (count == 0) + return 0; + if (*ppos) + return -ESPIPE; + + dev = (fliusb_t *)file->private_data; + return fliusb_bulk_read(dev, dev->rdbulkpipe, userbuffer, count, dev->timeout); +} + +static ssize_t fliusb_write(struct file *file, const char __user *userbuffer, + size_t count, loff_t *ppos){ + fliusb_t *dev; + + if (count == 0) + return 0; + if (*ppos) + return -ESPIPE; + dev = (fliusb_t *)file->private_data; + return fliusb_bulk_write(dev, dev->wrbulkpipe, userbuffer, count, dev->timeout); +} + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36) +static long fliusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +#else +static int fliusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +#endif +{ + fliusb_t *dev; + union { + u_int8_t uint8; + unsigned int uint; + fliusb_bulktransfer_t bulkxfer; + fliusb_string_descriptor_t strdesc; + } tmp; + unsigned int tmppipe; + int err; + + FLIUSB_DBG("cmd: %u; arg: %lu", cmd, arg); + if (_IOC_TYPE(cmd) != FLIUSB_IOC_TYPE || _IOC_NR(cmd) > FLIUSB_IOC_MAX) + return -ENOTTY; + /* Check that arg can be read from */ + if ((_IOC_DIR(cmd) & _IOC_WRITE) && !ACCESS_OK(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd))) + return -EFAULT; + /* Check that arg can be written to */ + if ((_IOC_DIR(cmd) & _IOC_READ) && !ACCESS_OK(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))) + return -EFAULT; + + dev = (fliusb_t *)file->private_data; + switch (cmd){ +#define FLIUSB_IOC_GETCMD(cmd, val, type) \ + case cmd: \ + return __put_user(val, (type __user *)arg); \ + break; + + FLIUSB_IOC_GETCMD(FLIUSB_GETBUFFERSIZE, dev->buffersize, unsigned int); + FLIUSB_IOC_GETCMD(FLIUSB_GETTIMEOUT, dev->timeout, unsigned int); + FLIUSB_IOC_GETCMD(FLIUSB_GETRDEPADDR, (u_int8_t)(usb_pipeendpoint(dev->rdbulkpipe) | USB_DIR_IN), u_int8_t); + FLIUSB_IOC_GETCMD(FLIUSB_GETWREPADDR, (u_int8_t)(usb_pipeendpoint(dev->wrbulkpipe) | USB_DIR_OUT), u_int8_t); +#undef FLIUSB_IOC_GETCMD + + case FLIUSB_SETRDEPADDR: + if (__get_user(tmp.uint8, (u_int8_t __user *)arg)) + return -EFAULT; + tmppipe = usb_rcvbulkpipe(dev->usbdev, tmp.uint8); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) + if (usb_maxpacket(dev->usbdev, tmppipe, 0) == 0) +#else + if (usb_maxpacket(dev->usbdev, tmppipe) == 0) +#endif + { + FLIUSB_ERR("invalid read USB bulk transfer endpoint address: 0x%02x", tmp.uint8); + return -EINVAL; + } + dev->rdbulkpipe = tmppipe; + return 0; + break; + + case FLIUSB_SETWREPADDR: + if (__get_user(tmp.uint8, (u_int8_t __user *)arg)) + return -EFAULT; + tmppipe = usb_sndbulkpipe(dev->usbdev, tmp.uint8); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) + if (usb_maxpacket(dev->usbdev, tmppipe, 1) == 0) +#else + if (usb_maxpacket(dev->usbdev, tmppipe) == 0) +#endif + { + FLIUSB_ERR("invalid write USB bulk transfer endpoint address: 0x%02x", tmp.uint8); + return -EINVAL; + } + dev->wrbulkpipe = tmppipe; + return 0; + break; + + case FLIUSB_SETBUFFERSIZE: + if (__get_user(tmp.uint, (unsigned int __user *)arg)) + return -EFAULT; + return fliusb_allocbuffer(dev, tmp.uint); + break; + + case FLIUSB_SETTIMEOUT: + if (__get_user(tmp.uint, (unsigned int __user *)arg)) + return -EFAULT; + if (tmp.uint == 0) + { + FLIUSB_ERR("invalid timeout: %u", tmp.uint); + return -EINVAL; + } + dev->timeout = tmp.uint; + return 0; + break; + + case FLIUSB_BULKREAD: + if (__copy_from_user(&tmp.bulkxfer, (fliusb_bulktransfer_t __user *)arg, sizeof(fliusb_bulktransfer_t))) + return -EFAULT; + tmppipe = usb_rcvbulkpipe(dev->usbdev, tmp.bulkxfer.ep); + return fliusb_bulk_read(dev, tmppipe, tmp.bulkxfer.buf, tmp.bulkxfer.count, tmp.bulkxfer.timeout); + + case FLIUSB_BULKWRITE: + if (__copy_from_user(&tmp.bulkxfer, (fliusb_bulktransfer_t __user *)arg, sizeof(fliusb_bulktransfer_t))) + return -EFAULT; + tmppipe = usb_sndbulkpipe(dev->usbdev, tmp.bulkxfer.ep); + return fliusb_bulk_write(dev, tmppipe, tmp.bulkxfer.buf, tmp.bulkxfer.count, tmp.bulkxfer.timeout); + + case FLIUSB_GET_DEVICE_DESCRIPTOR: + if (__copy_to_user((void *)arg, &dev->usbdev->descriptor, sizeof(struct usb_device_descriptor))) + return -EFAULT; + return 0; + break; + + case FLIUSB_GET_STRING_DESCRIPTOR: + if (__copy_from_user(&tmp.strdesc, (fliusb_string_descriptor_t __user *)arg, + sizeof(fliusb_string_descriptor_t))) + return -EFAULT; + + memset(tmp.strdesc.buf, 0, sizeof(tmp.strdesc.buf)); + if ((err = usb_string(dev->usbdev, tmp.strdesc.index, tmp.strdesc.buf, sizeof(tmp.strdesc.buf))) < 0) + FLIUSB_WARN("usb_string() failed: %d", err); + if (__copy_to_user((void *)arg, &tmp.strdesc, sizeof(fliusb_string_descriptor_t))) + return -EFAULT; + return 0; + break; + + default: + FLIUSB_ERR("invalid ioctl request: %d", cmd); + return -ENOTTY; + } + return -ENOTTY; /* shouldn't get here */ +} + +static int fliusb_initdev(fliusb_t **dev, struct usb_interface *interface, + const struct usb_device_id *id){ + fliusb_t *tmpdev; + int err; + char prodstr[64] = "unknown"; + + /* These vendor/product ID checks shouldn't be necessary */ + if (id->idVendor != FLIUSB_VENDORID){ + FLIUSB_WARN("unexpectedly probing a non-FLI USB device"); + return -EINVAL; + } + + switch (id->idProduct){ +#define FLIUSB_PROD(name, prodid) case name##_PRODID: + FLIUSB_PRODUCTS +#undef FLIUSB_PROD + break; + default: + FLIUSB_WARN("unsupported FLI USB device"); + return -EINVAL; + } + + if ((tmpdev = kmalloc(sizeof(fliusb_t), GFP_KERNEL)) == NULL) + return -ENOMEM; + memset(tmpdev, 0, sizeof(*tmpdev)); + kref_init(&tmpdev->kref); + tmpdev->usbdev = usb_get_dev(interface_to_usbdev(interface)); + tmpdev->interface = interface; + tmpdev->timeout = defaults.timeout; + + switch (id->idProduct){ + case FLIUSB_MAXCAM_PRODID: + case FLIUSB_STEPPER_PRODID: + case FLIUSB_FOCUSER_PRODID: + case FLIUSB_FILTERWHEEL_PRODID: + tmpdev->rdbulkpipe = usb_rcvbulkpipe(tmpdev->usbdev, FLIUSB_RDEPADDR); + tmpdev->wrbulkpipe = usb_sndbulkpipe(tmpdev->usbdev, FLIUSB_WREPADDR); + break; + + case FLIUSB_PROLINECAM_PRODID: + tmpdev->rdbulkpipe = usb_rcvbulkpipe(tmpdev->usbdev, FLIUSB_PROLINE_RDEPADDR); + tmpdev->wrbulkpipe = usb_sndbulkpipe(tmpdev->usbdev, FLIUSB_PROLINE_WREPADDR); + break; + + default: + FLIUSB_WARN("unsupported FLI USB device"); + err = -EINVAL; + goto fail; + break; + } + + /* Check that the endpoints exist */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) + if (usb_maxpacket(tmpdev->usbdev, tmpdev->rdbulkpipe, 0) == 0){ +#else + if (usb_maxpacket(tmpdev->usbdev, tmpdev->rdbulkpipe) == 0){ +#endif + FLIUSB_ERR("invalid read USB bulk transfer endpoint address: 0x%02x", + usb_pipeendpoint(tmpdev->rdbulkpipe) | USB_DIR_IN); + err = -ENXIO; + goto fail; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) + if (usb_maxpacket(tmpdev->usbdev, tmpdev->wrbulkpipe, 1) == 0){ +#else + if (usb_maxpacket(tmpdev->usbdev, tmpdev->wrbulkpipe) == 0){ +#endif + FLIUSB_ERR("invalid write USB bulk transfer endpoint address: 0x%02x", + usb_pipeendpoint(tmpdev->wrbulkpipe) | USB_DIR_OUT); + err = -ENXIO; + goto fail; + } + + init_MUTEX(&tmpdev->buffsem); + if ((err = fliusb_allocbuffer(tmpdev, defaults.buffersize))) + goto fail; +#ifdef SGREAD + tmpdev->usbsg.maxpg = NUMSGPAGE; + init_timer(&tmpdev->usbsg.timer); + init_MUTEX(&tmpdev->usbsg.sem); +#endif /* SGREAD */ + if ((err = usb_string(tmpdev->usbdev, tmpdev->usbdev->descriptor.iProduct, prodstr, sizeof(prodstr))) < 0) + FLIUSB_WARN("usb_string() failed: %d", err); + FLIUSB_INFO("FLI USB device found: '%s'", prodstr); + *dev = tmpdev; + return 0; +fail: + kref_put(&tmpdev->kref, fliusb_delete); + return err; +} + +static int fliusb_probe(struct usb_interface *interface, const struct usb_device_id *id){ + fliusb_t *dev; + int err; + + if ((err = fliusb_initdev(&dev, interface, id))) + return err; + /* save a pointer to the device structure in this interface */ + usb_set_intfdata(interface, dev); + /* register the device */ + if ((err = usb_register_dev(interface, &fliusb_class))){ + FLIUSB_ERR("usb_register_dev() failed: %d", err); + usb_set_intfdata(interface, NULL); + kref_put(&dev->kref, fliusb_delete); + return err; + } + FLIUSB_INFO("FLI USB device attached; rdepaddr: 0x%02x; wrepaddr: 0x%02x; " + "buffersize: %d; timeout: %d", + usb_pipeendpoint(dev->rdbulkpipe) | USB_DIR_IN, usb_pipeendpoint(dev->wrbulkpipe) | USB_DIR_OUT, + dev->buffersize, dev->timeout); + + return 0; +} + +static void fliusb_disconnect(struct usb_interface *interface){ + fliusb_t *dev; + /* this is to block entry to fliusb_open() while the device is being + disconnected + */ + mutex_lock(&fliusb_mutex); + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + /* give back the minor number we were using */ + usb_deregister_dev(interface, &fliusb_class); + mutex_unlock(&fliusb_mutex); + /* decrement usage count */ + kref_put(&dev->kref, fliusb_delete); + FLIUSB_INFO("FLI USB device disconnected"); +} + +static int __init fliusb_init(void){ + int err; + + if ((err = usb_register(&fliusb_driver))) + FLIUSB_ERR("usb_register() failed: %d", err); + mutex_init(&fliusb_mutex); + FLIUSB_INFO(FLIUSB_NAME " module loaded"); + return err; +} + +static void __exit fliusb_exit(void){ + usb_deregister(&fliusb_driver); + + FLIUSB_INFO(FLIUSB_NAME " module unloaded"); + return; +} + +module_init(fliusb_init); +module_exit(fliusb_exit); diff --git a/fliusb.h b/fliusb.h new file mode 100644 index 0000000..6cdf60b --- /dev/null +++ b/fliusb.h @@ -0,0 +1,132 @@ +/* + + Copyright (c) 2005 Finger Lakes Instrumentation (FLI), L.L.C. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + Neither the name of Finger Lakes Instrumentation (FLI), L.L.C. + nor the names of its contributors may be used to endorse or + promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + ====================================================================== + + Finger Lakes Instrumentation, L.L.C. (FLI) + web: http://www.fli-cam.com + email: support@fli-cam.com + +*/ + +#ifndef _FLIUSB_H_ +#define _FLIUSB_H_ + +#define FLIUSB_NAME "fliusb" +#define FLIUSB_MINOR_BASE 240 /* This is arbitrary */ + +#define FLIUSB_VENDORID 0x0f18 + +/* Recognized FLI USB products */ + +/* FLIUSB_PROD(name, prodid) */ +#define FLIUSB_PRODUCTS \ + FLIUSB_PROD(FLIUSB_MAXCAM, 0x0002) \ + FLIUSB_PROD(FLIUSB_STEPPER, 0x0005) \ + FLIUSB_PROD(FLIUSB_FOCUSER, 0x0006) \ + FLIUSB_PROD(FLIUSB_FILTERWHEEL, 0x0007) \ + FLIUSB_PROD(FLIUSB_PROLINECAM, 0x000a) + +enum { +#define FLIUSB_PROD(name, prodid) name##_PRODID = prodid, + FLIUSB_PRODUCTS +#undef FLIUSB_PROD +}; + +/* Default values (module parameters override these) */ +#define FLIUSB_TIMEOUT 5000 /* milliseconds */ +#ifndef SGREAD +#define FLIUSB_BUFFERSIZE 65536 +#else +#define FLIUSB_BUFFERSIZE PAGE_SIZE +#endif /* SGREAD */ + +/* Model-specific parameters */ +#define FLIUSB_RDEPADDR 0x82 +#define FLIUSB_WREPADDR 0x02 +#define FLIUSB_PROLINE_RDEPADDR 0x81 +#define FLIUSB_PROLINE_WREPADDR 0x01 + +#ifdef SGREAD + +#define NUMSGPAGE 32 + +typedef struct{ + struct page *userpg[NUMSGPAGE]; + struct scatterlist slist[NUMSGPAGE]; + unsigned int maxpg; + struct usb_sg_request sgreq; + struct timer_list timer; + struct semaphore sem; +} fliusbsg_t; + +#endif /* SGREAD */ + +typedef struct { + /* Bulk transfer pipes used for read()/write() */ + unsigned int rdbulkpipe; + unsigned int wrbulkpipe; + + /* Kernel buffer used for bulk reads */ + void *buffer; + unsigned int buffersize; + struct semaphore buffsem; + +#ifdef SGREAD + fliusbsg_t usbsg; +#endif + + unsigned int timeout; /* timeout for bulk transfers in milliseconds */ + + struct usb_device *usbdev; + struct usb_interface *interface; + + struct kref kref; +} fliusb_t; + +#define FLIUSB_ERR(fmt, args...) printk(KERN_ERR "%s[%d]: " fmt "\n", __FUNCTION__, __LINE__, ##args) + +#define FLIUSB_WARN(fmt, args...) printk(KERN_WARNING "%s[%d]: " fmt "\n", __FUNCTION__, __LINE__, ##args) + +#define FLIUSB_INFO(fmt, args...) printk(KERN_NOTICE "%s[%d]: " fmt "\n", __FUNCTION__, __LINE__, ##args) + +#ifdef DEBUG +#define FLIUSB_DBG(fmt, args...) FLIUSB_INFO(fmt, ##args) +#else +#define FLIUSB_DBG(fmt, args...) do{}while(0) + +#endif + +#endif /* _FLIUSB_H_ */ diff --git a/fliusb_ioctl.h b/fliusb_ioctl.h new file mode 100644 index 0000000..39ec60d --- /dev/null +++ b/fliusb_ioctl.h @@ -0,0 +1,89 @@ +/* + + Copyright (c) 2005 Finger Lakes Instrumentation (FLI), L.L.C. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + Neither the name of Finger Lakes Instrumentation (FLI), L.L.C. + nor the names of its contributors may be used to endorse or + promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + ====================================================================== + + Finger Lakes Instrumentation, L.L.C. (FLI) + web: http://www.fli-cam.com + email: support@fli-cam.com + +*/ + +#ifndef _FLIUSB_IOCTL_H_ +#define _FLIUSB_IOCTL_H_ + +#include + +/* Structure to describe string descriptor transfers */ +typedef struct +{ + unsigned int index; + char buf[64]; +} fliusb_string_descriptor_t; + +/* Structure to describe bulk transfers */ +typedef struct +{ + u_int8_t ep; + void *buf; + size_t count; + unsigned int timeout; /* in msec */ +} fliusb_bulktransfer_t; + +/* 8-bit special value to identify ioctl 'type' */ +#define FLIUSB_IOC_TYPE 0xf1 + +/* Recognized ioctl commands */ +#define FLIUSB_GETRDEPADDR _IOR(FLIUSB_IOC_TYPE, 1, u_int8_t) +#define FLIUSB_SETRDEPADDR _IOW(FLIUSB_IOC_TYPE, 2, u_int8_t) + +#define FLIUSB_GETWREPADDR _IOR(FLIUSB_IOC_TYPE, 3, u_int8_t) +#define FLIUSB_SETWREPADDR _IOW(FLIUSB_IOC_TYPE, 4, u_int8_t) + +#define FLIUSB_GETBUFFERSIZE _IOR(FLIUSB_IOC_TYPE, 5, size_t) +#define FLIUSB_SETBUFFERSIZE _IOW(FLIUSB_IOC_TYPE, 6, size_t) + +#define FLIUSB_GETTIMEOUT _IOR(FLIUSB_IOC_TYPE, 7, unsigned int) +#define FLIUSB_SETTIMEOUT _IOW(FLIUSB_IOC_TYPE, 8, unsigned int) + +#define FLIUSB_BULKREAD _IOW(FLIUSB_IOC_TYPE, 9, fliusb_bulktransfer_t) +#define FLIUSB_BULKWRITE _IOW(FLIUSB_IOC_TYPE, 10, fliusb_bulktransfer_t) + +#define FLIUSB_GET_DEVICE_DESCRIPTOR _IOR(FLIUSB_IOC_TYPE, 11, struct usb_device_descriptor) +#define FLIUSB_GET_STRING_DESCRIPTOR _IOR(FLIUSB_IOC_TYPE, 12, struct usb_device_descriptor) + +#define FLIUSB_IOC_MAX 12 + +#endif /* _FLIUSB_IOCTL_H_ */