diff options
author | Simon Shields <simon@lineageos.org> | 2019-07-04 15:57:00 +0200 |
---|---|---|
committer | Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> | 2020-01-20 02:58:43 +0100 |
commit | 5e139767b02b82184bcfdcabf61e67e8eaa6dbfa (patch) | |
tree | 437f0aea1d6811f447925ee51803061fb67866eb | |
parent | b4e625b1ddbd8cfa48c319fca86022513d824942 (diff) | |
download | kernel_replicant_linux-5e139767b02b82184bcfdcabf61e67e8eaa6dbfa.tar.gz kernel_replicant_linux-5e139767b02b82184bcfdcabf61e67e8eaa6dbfa.tar.bz2 kernel_replicant_linux-5e139767b02b82184bcfdcabf61e67e8eaa6dbfa.zip |
misc: xmm6262: Add Samsung IPC USB modem firmware download modulemodem/5.4-with-usb-disconnection-during-modem-init
TODO: Find Simon Shields commit message
Signed-off-by: Simon Shields <simon@lineageos.org>
GNUtoo@cyberdimension.org: rebase and fixes
Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
-rw-r--r-- | drivers/usb/misc/Kconfig | 5 | ||||
-rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/misc/xmm6262_boot.c | 432 |
3 files changed, 438 insertions, 0 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 9bce583aada3..28fb84477697 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -265,3 +265,8 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. + +config USB_XMM6262_BOOT + tristate "Samsung IPC USB modem firmware download module" + help + Say Y here if you want to use a Samsung IPC USB modem. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 0d416eb624bb..13da1ae99bc1 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o +obj-$(CONFIG_USB_XMM6262_BOOT) += xmm6262_boot.o diff --git a/drivers/usb/misc/xmm6262_boot.c b/drivers/usb/misc/xmm6262_boot.c new file mode 100644 index 000000000000..270ac8e688de --- /dev/null +++ b/drivers/usb/misc/xmm6262_boot.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for XMM6262 as found on Samsung GT-I9300, GT-N7100. + * + * Copyright (C) 2018 Simon Shields <simon@lineageos.org> + */ +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/usb.h> +#include <linux/usb/cdc.h> + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_XMM6262_MINOR_BASE 0 +#else +#define USB_XMM6262_MINOR_BASE 191 /* TODO */ +#endif + +struct xmm6262 { + struct usb_device *udev; + struct usb_interface *intf; + struct usb_interface *data_intf; + + struct urb *bulk_in_urb; + unsigned char *bulk_in_buf; + size_t bulk_in_size; + u8 bulk_in_addr; + u8 bulk_out_addr; + + struct sk_buff_head in_queue; + + /* + * should be held whenever + * bulk_in_buf and bulk_in_urb + * are in use + */ + struct mutex rx_mutex; + /* + * should be held while + * doing any file op + */ + struct mutex io_mutex; +}; + +static struct usb_driver xmm6262_boot_driver; +static int xmm6262_boot_start_rx(struct xmm6262 *dev); + +static void xmm6262_boot_rx_callback(struct urb *urb) +{ + struct xmm6262 *dev = urb->context; + struct sk_buff *skb; + int ret = 0; + + if (urb->status) { + if (urb->status != -ENOENT && + urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN) + dev_err(&dev->intf->dev, "nonzero bulk read status: %d\n", urb->status); + ret = urb->status; + goto error; + } else if (urb->actual_length) { + skb = alloc_skb(urb->actual_length, GFP_ATOMIC); + if (!skb) { + dev_err(&dev->intf->dev, "failed to allocate skb\n"); + ret = -ENOMEM; + goto error; + } + skb_put_data(skb, dev->bulk_in_buf, urb->actual_length); + skb_queue_tail(&dev->in_queue, skb); + } /* skip 0-length URBs */ + + mutex_unlock(&dev->rx_mutex); + xmm6262_boot_start_rx(dev); + return; +error: + mutex_unlock(&dev->rx_mutex); + // TODO: do something with ret + +} + +static int xmm6262_boot_start_rx(struct xmm6262 *dev) +{ + struct urb *urb = dev->bulk_in_urb; + + int ret = mutex_trylock(&dev->rx_mutex); + if (!ret) { + dev_warn(&dev->intf->dev, "already got pending RX job!\n"); + return -EBUSY; + } + + if (!dev->intf) + return -ENODEV; + + urb->transfer_flags = 0; + usb_fill_bulk_urb(urb, + dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->bulk_in_addr), + dev->bulk_in_buf, + dev->bulk_in_size, + xmm6262_boot_rx_callback, + dev); + + ret = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); + if (ret < 0) { + dev_err(&dev->intf->dev, "failed to submit rx urb: %d\n", ret); + ret = (ret == -ENOMEM) ? ret : -EIO; + mutex_unlock(&dev->rx_mutex); + /* TODO: we need a way to recover from this */ + } + + return ret; +} + +static int xmm6262_boot_open(struct inode *inode, struct file *file) +{ + struct xmm6262 *dev; + struct usb_interface *intf; + int subminor; + + subminor = iminor(inode); + + intf = usb_find_interface(&xmm6262_boot_driver, subminor); + if (!intf) { + pr_err("%s: can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(intf); + if (!dev) + return -ENODEV; + + /* begin receiving data */ + xmm6262_boot_start_rx(dev); + + file->private_data = dev; + return 0; +} + +static int xmm6262_boot_release(struct inode *inode, struct file *file) +{ + /* TODO stop RX */ + return 0; +} + +static ssize_t xmm6262_boot_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + struct xmm6262 *dev = file->private_data; + + struct sk_buff *skb = NULL; + int to_read = count; + int read = 0; + int ret; + + if (file->f_flags & O_NONBLOCK) { + ret = mutex_trylock(&dev->io_mutex); + if (!ret) { + dev_info(&dev->intf->dev, "already got another reader!\n"); + return -EAGAIN; + } + } else { + ret = mutex_lock_interruptible(&dev->io_mutex); + if (ret < 0) + return ret; + } + + ret = 0; + + while (to_read) { + skb = skb_dequeue(&dev->in_queue); + if (!skb) { + /* no data waiting: + * start rx again in case it failed for some reason */ + xmm6262_boot_start_rx(dev); + break; + } + + if (skb->len > to_read) { + if (copy_to_user(buf + read, skb->data, to_read)) { + dev_kfree_skb_any(skb); + ret = -EFAULT; + goto end; + } + + read += to_read; + to_read = 0; + skb_pull(skb, to_read); + skb_queue_head(&dev->in_queue, skb); + } else { + /* copy just skb->len */ + if (copy_to_user(buf + read, skb->data, skb->len)) { + dev_kfree_skb_any(skb); + ret = -EFAULT; + goto end; + } + read += skb->len; + to_read -= skb->len; + dev_kfree_skb_any(skb); + } + } + ret = read; +end: + mutex_unlock(&dev->io_mutex); + return ret; +} + +void xmm6262_boot_write_callback(struct urb *urb) +{ + struct xmm6262 *dev = urb->context; + + if (urb->status) { + if (urb->status != -ENOENT && + urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN) + dev_err(&dev->intf->dev, "nonzero write bulk status received: %d\n", urb->status); + } + + kfree(urb->transfer_buffer); + usb_free_urb(urb); +} + +static ssize_t xmm6262_boot_write(struct file *file, const char *user_buf, size_t count, + loff_t *ppos) +{ + struct xmm6262 *dev = file->private_data; + struct urb *urb = NULL; + char *buf = NULL; + int ret; + + if (unlikely(count == 0)) + return count; + + if (file->f_flags & O_NONBLOCK) { + ret = mutex_trylock(&dev->io_mutex); + if (!ret) { + dev_info(&dev->intf->dev, "already got another writer/reader!\n"); + return -EAGAIN; + } + } else { + ret = mutex_lock_interruptible(&dev->io_mutex); + if (ret < 0) + return ret; + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto error; + } + + buf = kzalloc(count, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_alloc; + } + + if (copy_from_user(buf, user_buf, count)) { + ret = -EFAULT; + goto err_copy; + } + + urb->transfer_flags = URB_ZERO_PACKET; + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, + dev->bulk_out_addr), + buf, count, + xmm6262_boot_write_callback, dev); + + ret = usb_submit_urb(urb, GFP_KERNEL); + mutex_unlock(&dev->io_mutex); + if (ret) { + dev_err(&dev->intf->dev, "failed submitting write urb: %d\n", ret); + return ret; + } + + return count; + +err_copy: + kfree(buf); +err_alloc: + usb_free_urb(urb); +error: + mutex_unlock(&dev->io_mutex); + return ret; +} + +static unsigned int xmm6262_boot_poll(struct file *file, struct poll_table_struct *p) +{ + struct xmm6262 *dev = file->private_data; + + if (!skb_queue_empty(&dev->in_queue)) { + return POLLIN; + } + + return 0; +} + +static const struct file_operations xmm6262_boot_fops = { + .owner = THIS_MODULE, + .read = xmm6262_boot_read, + .write = xmm6262_boot_write, + .poll = xmm6262_boot_poll, + .open = xmm6262_boot_open, + .release = xmm6262_boot_release, +}; + +static struct usb_class_driver xmm6262_boot_class = { + .name = "xmm6262_boot%d", + .fops = &xmm6262_boot_fops, + .minor_base = USB_XMM6262_MINOR_BASE, +}; + +static int xmm6262_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret, buflen; + struct xmm6262 *dev; + struct usb_endpoint_descriptor *bulk_in, *bulk_out; + struct usb_cdc_union_desc *union_header = NULL; + struct usb_cdc_parsed_header h; + struct usb_interface *data_intf; + struct usb_driver *usbdrv = to_usb_driver(intf->dev.driver); + unsigned char *buf; + + buflen = intf->altsetting->extralen; + buf = intf->altsetting->endpoint->extra; + + if (!buflen) { + if (intf->cur_altsetting->endpoint && + intf->cur_altsetting->endpoint->extralen && + intf->cur_altsetting->endpoint->extra) { + buflen = intf->cur_altsetting->endpoint->extralen; + buf = intf->cur_altsetting->endpoint->extra; + } else { + dev_err(&intf->dev, "No descriptor reference\n"); + return -EINVAL; + } + } + + cdc_parse_cdc_header(&h, intf, buf, buflen); + union_header = h.usb_cdc_union_desc; + + dev = devm_kzalloc(&intf->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->rx_mutex); + mutex_init(&dev->io_mutex); + + dev->udev = usb_get_dev(interface_to_usbdev(intf)); + dev->intf = intf; + data_intf = usb_ifnum_to_if(dev->udev, + union_header->bSlaveInterface0); + if (!data_intf || !data_intf->altsetting) { + dev_err(&intf->dev, "couldn't find data interface!\n"); + return -ENODEV; + } + skb_queue_head_init(&dev->in_queue); + + dev->data_intf = data_intf; + usb_driver_claim_interface(usbdrv, data_intf, dev); + usb_set_intfdata(intf, dev); + + ret = usb_find_common_endpoints(data_intf->altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (ret) { + dev_err(&intf->dev, "Couldn't find bulk-in and bulk-out EPs\n"); + return -EINVAL; + } + + ret = usb_register_dev(intf, &xmm6262_boot_class); + if (ret) { + return ret; + } + + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_addr = bulk_in->bEndpointAddress; + dev->bulk_in_buf = devm_kzalloc(&intf->dev, dev->bulk_in_size, GFP_KERNEL); + dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bulk_in_urb) + return -ENOMEM; + + dev->bulk_out_addr = bulk_out->bEndpointAddress; + + dev_info(&intf->dev, "Loaded XMM6262 boot serial device!\n"); + + return 0; +} + +static void xmm6262_disconnect(struct usb_interface *intf) +{ + struct xmm6262 *dev = usb_get_intfdata(intf); + struct usb_driver *usbdrv = to_usb_driver(intf->dev.driver); + + mutex_lock(&dev->rx_mutex); + /* time to stop! */ + dev->intf = NULL; + mutex_unlock(&dev->rx_mutex); + + usb_driver_release_interface(usbdrv, dev->data_intf); + usb_deregister_dev(intf, &xmm6262_boot_class); + +} + +static struct usb_device_id xmm6262_idtable[] = { + /* Infineon XMM626x */ + /* Initially appears as a CDC device that needs firmware uploaded before boot will continue */ + {USB_DEVICE_AND_INTERFACE_INFO(0x058b, 0x0041, + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_PROTO_NONE)}, + {} +}; + +static struct usb_driver xmm6262_boot_driver = { + .name = "xmm6262_boot", + .probe = xmm6262_probe, + .disconnect = xmm6262_disconnect, + //.suspend = xmm6262_suspend, + //.resume = xmm6262_resume, + //.reset_resume = xmm6262_reset_resume, + .id_table = xmm6262_idtable, + .supports_autosuspend = 0, +}; + +module_usb_driver(xmm6262_boot_driver); + +MODULE_DEVICE_TABLE(usb, xmm6262_idtable); + +MODULE_LICENSE("GPL"); |