/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include /* * This function is a "Mobile Broadband Interface Model" (MBIM) link. * MBIM is intended to be used with high-speed network attachments. * * Note that MBIM requires the use of "alternate settings" for its data * interface. This means that the set_alt() method has real work to do, * and also means that a get_alt() method is required. */ #define MBIM_BULK_BUFFER_SIZE 4096 #define MAX_CTRL_PKT_SIZE 4096 enum mbim_peripheral_ep_type { MBIM_DATA_EP_TYPE_RESERVED = 0x0, MBIM_DATA_EP_TYPE_HSIC = 0x1, MBIM_DATA_EP_TYPE_HSUSB = 0x2, MBIM_DATA_EP_TYPE_PCIE = 0x3, MBIM_DATA_EP_TYPE_EMBEDDED = 0x4, MBIM_DATA_EP_TYPE_BAM_DMUX = 0x5, }; struct mbim_peripheral_ep_info { enum peripheral_ep_type ep_type; u32 peripheral_iface_id; }; struct mbim_ipa_ep_pair { u32 cons_pipe_num; u32 prod_pipe_num; }; struct mbim_ipa_ep_info { struct mbim_peripheral_ep_info ph_ep_info; struct mbim_ipa_ep_pair ipa_ep_pair; }; #define MBIM_IOCTL_MAGIC 'o' #define MBIM_GET_NTB_SIZE _IOR(MBIM_IOCTL_MAGIC, 2, u32) #define MBIM_GET_DATAGRAM_COUNT _IOR(MBIM_IOCTL_MAGIC, 3, u16) #define MBIM_EP_LOOKUP _IOR(MBIM_IOCTL_MAGIC, 4, struct mbim_ipa_ep_info) #define NR_MBIM_PORTS 1 #define MBIM_DEFAULT_PORT 0 /* ID for Microsoft OS String */ #define MBIM_OS_STRING_ID 0xEE struct ctrl_pkt { void *buf; int len; struct list_head list; }; struct mbim_ep_descs { struct usb_endpoint_descriptor *in; struct usb_endpoint_descriptor *out; struct usb_endpoint_descriptor *notify; }; struct mbim_notify_port { struct usb_ep *notify; struct usb_request *notify_req; u8 notify_state; atomic_t notify_count; }; enum mbim_notify_state { MBIM_NOTIFY_NONE, MBIM_NOTIFY_CONNECT, MBIM_NOTIFY_SPEED, MBIM_NOTIFY_RESPONSE_AVAILABLE, }; struct f_mbim { struct usb_function function; struct usb_composite_dev *cdev; atomic_t online; atomic_t open_excl; atomic_t ioctl_excl; atomic_t read_excl; atomic_t write_excl; wait_queue_head_t read_wq; enum transport_type xport; u8 port_num; struct data_port bam_port; struct mbim_notify_port not_port; struct mbim_ep_descs fs; struct mbim_ep_descs hs; u8 ctrl_id, data_id; bool data_interface_up; spinlock_t lock; struct list_head cpkt_req_q; struct list_head cpkt_resp_q; u32 ntb_input_size; u16 ntb_max_datagrams; atomic_t error; unsigned int cpkt_drop_cnt; bool remote_wakeup_enabled; }; struct mbim_ntb_input_size { u32 ntb_input_size; u16 ntb_max_datagrams; u16 reserved; }; /* temporary variable used between mbim_open() and mbim_gadget_bind() */ static struct f_mbim *_mbim_dev; static unsigned int nr_mbim_ports; static struct mbim_ports { struct f_mbim *port; unsigned port_num; } mbim_ports[NR_MBIM_PORTS]; static inline struct f_mbim *func_to_mbim(struct usb_function *f) { return container_of(f, struct f_mbim, function); } /*-------------------------------------------------------------------------*/ #define MBIM_NTB_DEFAULT_IN_SIZE (0x4000) #define MBIM_NTB_OUT_SIZE (0x1000) #define MBIM_NDP_IN_DIVISOR (0x4) #define NTB_DEFAULT_IN_SIZE_IPA (0x4000) #define MBIM_NTB_OUT_SIZE_IPA (0x4000) #define MBIM_FORMATS_SUPPORTED USB_CDC_NCM_NTB16_SUPPORTED static struct usb_cdc_ncm_ntb_parameters mbim_ntb_parameters = { .wLength = sizeof mbim_ntb_parameters, .bmNtbFormatsSupported = cpu_to_le16(MBIM_FORMATS_SUPPORTED), .dwNtbInMaxSize = cpu_to_le32(MBIM_NTB_DEFAULT_IN_SIZE), .wNdpInDivisor = cpu_to_le16(MBIM_NDP_IN_DIVISOR), .wNdpInPayloadRemainder = cpu_to_le16(0), .wNdpInAlignment = cpu_to_le16(4), .dwNtbOutMaxSize = cpu_to_le32(MBIM_NTB_OUT_SIZE), .wNdpOutDivisor = cpu_to_le16(4), .wNdpOutPayloadRemainder = cpu_to_le16(0), .wNdpOutAlignment = cpu_to_le16(4), .wNtbOutMaxDatagrams = 0, }; /* * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one * packet, to simplify cancellation; and a big transfer interval, to * waste less bandwidth. */ #define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ #define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ static struct usb_interface_assoc_descriptor mbim_iad_desc = { .bLength = sizeof mbim_iad_desc, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, /* .bFirstInterface = DYNAMIC, */ .bInterfaceCount = 2, /* control + data */ .bFunctionClass = 2, .bFunctionSubClass = 0x0e, .bFunctionProtocol = 0, /* .iFunction = DYNAMIC */ }; /* interface descriptor: */ static struct usb_interface_descriptor mbim_control_intf = { .bLength = sizeof mbim_control_intf, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bNumEndpoints = 1, .bInterfaceClass = 0x02, .bInterfaceSubClass = 0x0e, .bInterfaceProtocol = 0, /* .iInterface = DYNAMIC */ }; static struct usb_cdc_header_desc mbim_header_desc = { .bLength = sizeof mbim_header_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_HEADER_TYPE, .bcdCDC = cpu_to_le16(0x0110), }; static struct usb_cdc_union_desc mbim_union_desc = { .bLength = sizeof(mbim_union_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_UNION_TYPE, /* .bMasterInterface0 = DYNAMIC */ /* .bSlaveInterface0 = DYNAMIC */ }; static struct usb_cdc_mbim_desc mbim_desc = { .bLength = sizeof mbim_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_MBIM_TYPE, .bcdMBIMVersion = cpu_to_le16(0x0100), .wMaxControlMessage = cpu_to_le16(0x1000), .bNumberFilters = 0x20, .bMaxFilterSize = 0x80, .wMaxSegmentSize = cpu_to_le16(0x800), .bmNetworkCapabilities = 0x20, }; static struct usb_cdc_mbim_extended_desc ext_mbb_desc = { .bLength = sizeof ext_mbb_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_MBIM_EXTENDED_TYPE, .bcdMBIMExtendedVersion = cpu_to_le16(0x0100), .bMaxOutstandingCommandMessages = 64, .wMTU = cpu_to_le16(1500), }; /* the default data interface has no endpoints ... */ static struct usb_interface_descriptor mbim_data_nop_intf = { .bLength = sizeof mbim_data_nop_intf, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bAlternateSetting = 0, .bNumEndpoints = 0, .bInterfaceClass = 0x0a, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0x02, /* .iInterface = DYNAMIC */ }; /* ... but the "real" data interface has two bulk endpoints */ static struct usb_interface_descriptor mbim_data_intf = { .bLength = sizeof mbim_data_intf, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bAlternateSetting = 1, .bNumEndpoints = 2, .bInterfaceClass = 0x0a, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0x02, /* .iInterface = DYNAMIC */ }; /* full speed support: */ static struct usb_endpoint_descriptor fs_mbim_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = 4*cpu_to_le16(NCM_STATUS_BYTECOUNT), .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, }; static struct usb_endpoint_descriptor fs_mbim_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, }; static struct usb_endpoint_descriptor fs_mbim_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, }; static struct usb_descriptor_header *mbim_fs_function[] = { (struct usb_descriptor_header *) &mbim_iad_desc, /* MBIM control descriptors */ (struct usb_descriptor_header *) &mbim_control_intf, (struct usb_descriptor_header *) &mbim_header_desc, (struct usb_descriptor_header *) &mbim_union_desc, (struct usb_descriptor_header *) &mbim_desc, (struct usb_descriptor_header *) &ext_mbb_desc, (struct usb_descriptor_header *) &fs_mbim_notify_desc, /* data interface, altsettings 0 and 1 */ (struct usb_descriptor_header *) &mbim_data_nop_intf, (struct usb_descriptor_header *) &mbim_data_intf, (struct usb_descriptor_header *) &fs_mbim_in_desc, (struct usb_descriptor_header *) &fs_mbim_out_desc, NULL, }; /* high speed support: */ static struct usb_endpoint_descriptor hs_mbim_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = 4*cpu_to_le16(NCM_STATUS_BYTECOUNT), .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, }; static struct usb_endpoint_descriptor hs_mbim_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = cpu_to_le16(512), }; static struct usb_endpoint_descriptor hs_mbim_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = cpu_to_le16(512), }; static struct usb_descriptor_header *mbim_hs_function[] = { (struct usb_descriptor_header *) &mbim_iad_desc, /* MBIM control descriptors */ (struct usb_descriptor_header *) &mbim_control_intf, (struct usb_descriptor_header *) &mbim_header_desc, (struct usb_descriptor_header *) &mbim_union_desc, (struct usb_descriptor_header *) &mbim_desc, (struct usb_descriptor_header *) &ext_mbb_desc, (struct usb_descriptor_header *) &hs_mbim_notify_desc, /* data interface, altsettings 0 and 1 */ (struct usb_descriptor_header *) &mbim_data_nop_intf, (struct usb_descriptor_header *) &mbim_data_intf, (struct usb_descriptor_header *) &hs_mbim_in_desc, (struct usb_descriptor_header *) &hs_mbim_out_desc, NULL, }; /* Super Speed Support */ static struct usb_endpoint_descriptor ss_mbim_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = 4*cpu_to_le16(NCM_STATUS_BYTECOUNT), .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, }; static struct usb_ss_ep_comp_descriptor ss_mbim_notify_comp_desc = { .bLength = sizeof(ss_mbim_notify_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 3 values can be tweaked if necessary */ /* .bMaxBurst = 0, */ /* .bmAttributes = 0, */ .wBytesPerInterval = 4*cpu_to_le16(NCM_STATUS_BYTECOUNT), }; static struct usb_endpoint_descriptor ss_mbim_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16(1024), }; static struct usb_ss_ep_comp_descriptor ss_mbim_in_comp_desc = { .bLength = sizeof(ss_mbim_in_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 2 values can be tweaked if necessary */ /* .bMaxBurst = 0, */ /* .bmAttributes = 0, */ }; static struct usb_endpoint_descriptor ss_mbim_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16(1024), }; static struct usb_ss_ep_comp_descriptor ss_mbim_out_comp_desc = { .bLength = sizeof(ss_mbim_out_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 2 values can be tweaked if necessary */ /* .bMaxBurst = 0, */ /* .bmAttributes = 0, */ }; static struct usb_descriptor_header *mbim_ss_function[] = { (struct usb_descriptor_header *) &mbim_iad_desc, /* MBIM control descriptors */ (struct usb_descriptor_header *) &mbim_control_intf, (struct usb_descriptor_header *) &mbim_header_desc, (struct usb_descriptor_header *) &mbim_union_desc, (struct usb_descriptor_header *) &mbim_desc, (struct usb_descriptor_header *) &ext_mbb_desc, (struct usb_descriptor_header *) &ss_mbim_notify_desc, (struct usb_descriptor_header *) &ss_mbim_notify_comp_desc, /* data interface, altsettings 0 and 1 */ (struct usb_descriptor_header *) &mbim_data_nop_intf, (struct usb_descriptor_header *) &mbim_data_intf, (struct usb_descriptor_header *) &ss_mbim_in_desc, (struct usb_descriptor_header *) &ss_mbim_in_comp_desc, (struct usb_descriptor_header *) &ss_mbim_out_desc, (struct usb_descriptor_header *) &ss_mbim_out_comp_desc, NULL, }; /* string descriptors: */ #define STRING_CTRL_IDX 0 #define STRING_DATA_IDX 1 static struct usb_string mbim_string_defs[] = { [STRING_CTRL_IDX].s = "MBIM Control", [STRING_DATA_IDX].s = "MBIM Data", { } /* end of list */ }; static struct usb_gadget_strings mbim_string_table = { .language = 0x0409, /* en-us */ .strings = mbim_string_defs, }; static struct usb_gadget_strings *mbim_strings[] = { &mbim_string_table, NULL, }; /* Microsoft OS Descriptors */ /* * We specify our own bMS_VendorCode byte which Windows will use * as the bRequest value in subsequent device get requests. */ #define MBIM_VENDOR_CODE 0xA5 /* Microsoft OS String */ static u8 mbim_os_string[] = { 18, /* sizeof(mtp_os_string) */ USB_DT_STRING, /* Signature field: "MSFT100" */ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, /* vendor code */ MBIM_VENDOR_CODE, /* padding */ 0 }; /* Microsoft Extended Configuration Descriptor Header Section */ struct mbim_ext_config_desc_header { __le32 dwLength; __u16 bcdVersion; __le16 wIndex; __u8 bCount; __u8 reserved[7]; }; /* Microsoft Extended Configuration Descriptor Function Section */ struct mbim_ext_config_desc_function { __u8 bFirstInterfaceNumber; __u8 bInterfaceCount; __u8 compatibleID[8]; __u8 subCompatibleID[8]; __u8 reserved[6]; }; /* Microsoft Extended Configuration Descriptor */ static struct { struct mbim_ext_config_desc_header header; struct mbim_ext_config_desc_function function; } mbim_ext_config_desc = { .header = { .dwLength = __constant_cpu_to_le32(sizeof mbim_ext_config_desc), .bcdVersion = __constant_cpu_to_le16(0x0100), .wIndex = __constant_cpu_to_le16(4), .bCount = 1, }, .function = { .bFirstInterfaceNumber = 0, .bInterfaceCount = 1, .compatibleID = { 'A', 'L', 'T', 'R', 'C', 'F', 'G' }, /* .subCompatibleID = DYNAMIC */ }, }; static inline int mbim_lock(atomic_t *excl) { if (atomic_inc_return(excl) == 1) { return 0; } else { atomic_dec(excl); return -EBUSY; } } static inline void mbim_unlock(atomic_t *excl) { atomic_dec(excl); } static struct ctrl_pkt *mbim_alloc_ctrl_pkt(unsigned len, gfp_t flags) { struct ctrl_pkt *pkt; pkt = kzalloc(sizeof(struct ctrl_pkt), flags); if (!pkt) return ERR_PTR(-ENOMEM); pkt->buf = kmalloc(len, flags); if (!pkt->buf) { kfree(pkt); return ERR_PTR(-ENOMEM); } pkt->len = len; return pkt; } static void mbim_free_ctrl_pkt(struct ctrl_pkt *pkt) { if (pkt) { kfree(pkt->buf); kfree(pkt); } } static struct usb_request *mbim_alloc_req(struct usb_ep *ep, int buffer_size, size_t extra_buf) { struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); if (!req) return NULL; req->buf = kmalloc(buffer_size + extra_buf, GFP_KERNEL); if (!req->buf) { usb_ep_free_request(ep, req); return NULL; } req->length = buffer_size; return req; } void fmbim_free_req(struct usb_ep *ep, struct usb_request *req) { if (req) { kfree(req->buf); usb_ep_free_request(ep, req); } } /* ---------------------------- BAM INTERFACE ----------------------------- */ static int mbim_bam_setup(int no_ports) { int ret; pr_info("no_ports:%d\n", no_ports); ret = bam_data_setup(USB_FUNC_MBIM, no_ports); if (ret) { pr_err("bam_data_setup failed err: %d\n", ret); return ret; } pr_info("Initialized %d ports\n", no_ports); return 0; } /* -------------------------------------------------------------------------*/ static inline void mbim_reset_values(struct f_mbim *mbim) { mbim->ntb_input_size = MBIM_NTB_DEFAULT_IN_SIZE; atomic_set(&mbim->online, 0); } static void mbim_reset_function_queue(struct f_mbim *dev) { struct ctrl_pkt *cpkt = NULL; pr_debug("Queue empty packet for QBI\n"); spin_lock(&dev->lock); cpkt = mbim_alloc_ctrl_pkt(0, GFP_ATOMIC); if (!cpkt) { pr_err("%s: Unable to allocate reset function pkt\n", __func__); spin_unlock(&dev->lock); return; } list_add_tail(&cpkt->list, &dev->cpkt_req_q); spin_unlock(&dev->lock); pr_debug("%s: Wake up read queue\n", __func__); wake_up(&dev->read_wq); } static void fmbim_reset_cmd_complete(struct usb_ep *ep, struct usb_request *req) { struct f_mbim *dev = req->context; mbim_reset_function_queue(dev); } static void mbim_clear_queues(struct f_mbim *mbim) { struct ctrl_pkt *cpkt = NULL; struct list_head *act, *tmp; spin_lock(&mbim->lock); list_for_each_safe(act, tmp, &mbim->cpkt_req_q) { cpkt = list_entry(act, struct ctrl_pkt, list); list_del(&cpkt->list); mbim_free_ctrl_pkt(cpkt); } list_for_each_safe(act, tmp, &mbim->cpkt_resp_q) { cpkt = list_entry(act, struct ctrl_pkt, list); list_del(&cpkt->list); mbim_free_ctrl_pkt(cpkt); } spin_unlock(&mbim->lock); } /* * Context: mbim->lock held */ static void mbim_do_notify(struct f_mbim *mbim) { struct usb_request *req = mbim->not_port.notify_req; struct usb_cdc_notification *event; int status; pr_debug("notify_state: %d\n", mbim->not_port.notify_state); if (!req) return; event = req->buf; switch (mbim->not_port.notify_state) { case MBIM_NOTIFY_NONE: if (atomic_read(&mbim->not_port.notify_count) > 0) pr_err("Pending notifications in MBIM_NOTIFY_NONE\n"); else pr_debug("No pending notifications\n"); return; case MBIM_NOTIFY_RESPONSE_AVAILABLE: pr_debug("Notification %02x sent\n", event->bNotificationType); if (atomic_read(&mbim->not_port.notify_count) <= 0) { pr_debug("notify_response_avaliable: done\n"); return; } spin_unlock(&mbim->lock); status = usb_func_ep_queue(&mbim->function, mbim->not_port.notify, req, GFP_ATOMIC); spin_lock(&mbim->lock); if (status) { atomic_dec(&mbim->not_port.notify_count); pr_err("Queue notify request failed, err: %d\n", status); } return; } event->bmRequestType = 0xA1; event->wIndex = cpu_to_le16(mbim->ctrl_id); /* * In double buffering if there is a space in FIFO, * completion callback can be called right after the call, * so unlocking */ atomic_inc(&mbim->not_port.notify_count); pr_debug("queue request: notify_count = %d\n", atomic_read(&mbim->not_port.notify_count)); spin_unlock(&mbim->lock); status = usb_func_ep_queue(&mbim->function, mbim->not_port.notify, req, GFP_ATOMIC); spin_lock(&mbim->lock); if (status) { atomic_dec(&mbim->not_port.notify_count); pr_err("usb_func_ep_queue failed, err: %d\n", status); } } static void mbim_notify_complete(struct usb_ep *ep, struct usb_request *req) { struct f_mbim *mbim = req->context; struct usb_cdc_notification *event = req->buf; pr_debug("dev:%p\n", mbim); spin_lock(&mbim->lock); switch (req->status) { case 0: atomic_dec(&mbim->not_port.notify_count); pr_debug("notify_count = %d\n", atomic_read(&mbim->not_port.notify_count)); break; case -ECONNRESET: case -ESHUTDOWN: /* connection gone */ mbim->not_port.notify_state = MBIM_NOTIFY_NONE; atomic_set(&mbim->not_port.notify_count, 0); pr_info("ESHUTDOWN/ECONNRESET, connection gone\n"); spin_unlock(&mbim->lock); mbim_clear_queues(mbim); mbim_reset_function_queue(mbim); spin_lock(&mbim->lock); break; default: pr_err("Unknown event %02x --> %d\n", event->bNotificationType, req->status); break; } mbim_do_notify(mbim); spin_unlock(&mbim->lock); pr_debug("dev:%p Exit\n", mbim); } static void mbim_ep0out_complete(struct usb_ep *ep, struct usb_request *req) { /* now for SET_NTB_INPUT_SIZE only */ unsigned in_size = 0; struct usb_function *f = req->context; struct f_mbim *mbim = func_to_mbim(f); struct mbim_ntb_input_size *ntb = NULL; pr_debug("dev:%p\n", mbim); req->context = NULL; if (req->status || req->actual != req->length) { pr_err("Bad control-OUT transfer\n"); goto invalid; } if (req->length == 4) { in_size = get_unaligned_le32(req->buf); if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || in_size > le32_to_cpu(mbim_ntb_parameters.dwNtbInMaxSize)) { pr_err("Illegal INPUT SIZE (%d) from host\n", in_size); goto invalid; } } else if (req->length == 8) { ntb = (struct mbim_ntb_input_size *)req->buf; in_size = get_unaligned_le32(&(ntb->ntb_input_size)); if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || in_size > le32_to_cpu(mbim_ntb_parameters.dwNtbInMaxSize)) { pr_err("Illegal INPUT SIZE (%d) from host\n", in_size); goto invalid; } mbim->ntb_max_datagrams = get_unaligned_le16(&(ntb->ntb_max_datagrams)); } else { pr_err("Illegal NTB length %d\n", in_size); goto invalid; } pr_debug("Set NTB INPUT SIZE %d\n", in_size); mbim->ntb_input_size = in_size; return; invalid: usb_ep_set_halt(ep); pr_err("dev:%p Failed\n", mbim); return; } static void fmbim_cmd_complete(struct usb_ep *ep, struct usb_request *req) { struct f_mbim *dev = req->context; struct ctrl_pkt *cpkt = NULL; int len = req->actual; static bool first_command_sent; if (!dev) { pr_err("mbim dev is null\n"); return; } if (req->status < 0) { pr_err("mbim command error %d\n", req->status); return; } /* * Wait for user to process prev MBIM_OPEN cmd before handling new one. * However don't drop first command during bootup as file may not be * opened by now. Queue the command in this case. */ if (!atomic_read(&dev->open_excl) && first_command_sent) { pr_err("mbim not opened yet, dropping cmd pkt = %d\n", len); return; } if (!first_command_sent) first_command_sent = true; pr_debug("dev:%p port#%d\n", dev, dev->port_num); cpkt = mbim_alloc_ctrl_pkt(len, GFP_ATOMIC); if (!cpkt) { pr_err("Unable to allocate ctrl pkt\n"); return; } pr_debug("Add to cpkt_req_q packet with len = %d\n", len); memcpy(cpkt->buf, req->buf, len); spin_lock(&dev->lock); list_add_tail(&cpkt->list, &dev->cpkt_req_q); spin_unlock(&dev->lock); /* wakeup read thread */ pr_debug("Wake up read queue\n"); wake_up(&dev->read_wq); return; } static int mbim_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_mbim *mbim = func_to_mbim(f); struct usb_composite_dev *cdev = mbim->cdev; struct usb_request *req = cdev->req; struct ctrl_pkt *cpkt = NULL; int value = -EOPNOTSUPP; u16 w_index = le16_to_cpu(ctrl->wIndex); u16 w_value = le16_to_cpu(ctrl->wValue); u16 w_length = le16_to_cpu(ctrl->wLength); /* * composite driver infrastructure handles everything except * CDC class messages; interface activation uses set_alt(). */ if (!atomic_read(&mbim->online)) { pr_warning("usb cable is not connected\n"); return -ENOTCONN; } switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_RESET_FUNCTION: pr_debug("USB_CDC_RESET_FUNCTION\n"); value = 0; req->complete = fmbim_reset_cmd_complete; req->context = mbim; break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_SEND_ENCAPSULATED_COMMAND: pr_debug("USB_CDC_SEND_ENCAPSULATED_COMMAND\n"); if (w_length > req->length) { pr_debug("w_length > req->length: %d > %d\n", w_length, req->length); } value = w_length; req->complete = fmbim_cmd_complete; req->context = mbim; break; case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_GET_ENCAPSULATED_RESPONSE: pr_debug("USB_CDC_GET_ENCAPSULATED_RESPONSE\n"); if (w_value) { pr_err("w_length > 0: %d\n", w_length); break; } pr_debug("req%02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, w_value, w_index, w_length); spin_lock(&mbim->lock); if (list_empty(&mbim->cpkt_resp_q)) { pr_err("ctrl resp queue empty\n"); spin_unlock(&mbim->lock); break; } cpkt = list_first_entry(&mbim->cpkt_resp_q, struct ctrl_pkt, list); list_del(&cpkt->list); spin_unlock(&mbim->lock); value = min_t(unsigned, w_length, cpkt->len); memcpy(req->buf, cpkt->buf, value); mbim_free_ctrl_pkt(cpkt); pr_debug("copied encapsulated_response %d bytes\n", value); break; case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_GET_NTB_PARAMETERS: pr_debug("USB_CDC_GET_NTB_PARAMETERS\n"); if (w_length == 0 || w_value != 0 || w_index != mbim->ctrl_id) break; value = w_length > sizeof mbim_ntb_parameters ? sizeof mbim_ntb_parameters : w_length; memcpy(req->buf, &mbim_ntb_parameters, value); break; case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_GET_NTB_INPUT_SIZE: pr_debug("USB_CDC_GET_NTB_INPUT_SIZE\n"); if (w_length < 4 || w_value != 0 || w_index != mbim->ctrl_id) break; put_unaligned_le32(mbim->ntb_input_size, req->buf); value = 4; pr_debug("Reply to host INPUT SIZE %d\n", mbim->ntb_input_size); break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_SET_NTB_INPUT_SIZE: pr_debug("USB_CDC_SET_NTB_INPUT_SIZE\n"); if (w_length != 4 && w_length != 8) { pr_err("wrong NTB length %d\n", w_length); break; } if (w_value != 0 || w_index != mbim->ctrl_id) break; req->complete = mbim_ep0out_complete; req->length = w_length; req->context = f; value = req->length; break; /* optional in mbim descriptor: */ /* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */ /* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */ default: pr_err("invalid control req: %02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, w_value, w_index, w_length); } /* respond with data transfer or status phase? */ if (value >= 0) { pr_debug("control request: %02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, w_value, w_index, w_length); req->zero = (value < w_length); req->length = value; value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (value < 0) { pr_err("queueing req failed: %02x.%02x, err %d\n", ctrl->bRequestType, ctrl->bRequest, value); } } else { pr_err("ctrl req err %d: %02x.%02x v%04x i%04x l%d\n", value, ctrl->bRequestType, ctrl->bRequest, w_value, w_index, w_length); } /* device either stalls (value < 0) or reports success */ return value; } /* * This function handles the Microsoft-specific OS descriptor control * requests that are issued by Windows host drivers to determine the * configuration containing the MBIM function. * * Unlike mbim_setup() this function handles two specific device requests, * and only when a configuration has not yet been selected. */ static int mbim_ctrlrequest(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl) { int value = -EOPNOTSUPP; u16 w_index = le16_to_cpu(ctrl->wIndex); u16 w_value = le16_to_cpu(ctrl->wValue); u16 w_length = le16_to_cpu(ctrl->wLength); /* only respond to OS desciptors when no configuration selected */ if (cdev->config || !mbim_ext_config_desc.function.subCompatibleID[0]) return value; pr_debug("%02x.%02x v%04x i%04x l%u\n", ctrl->bRequestType, ctrl->bRequest, w_value, w_index, w_length); /* Handle MSFT OS string */ if (ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR && (w_value >> 8) == USB_DT_STRING && (w_value & 0xFF) == MBIM_OS_STRING_ID) { value = (w_length < sizeof(mbim_os_string) ? w_length : sizeof(mbim_os_string)); memcpy(cdev->req->buf, mbim_os_string, value); } else if (ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) && ctrl->bRequest == MBIM_VENDOR_CODE && w_index == 4) { /* Handle Extended OS descriptor */ value = (w_length < sizeof(mbim_ext_config_desc) ? w_length : sizeof(mbim_ext_config_desc)); memcpy(cdev->req->buf, &mbim_ext_config_desc, value); } /* respond with data transfer or status phase? */ if (value >= 0) { int rc; cdev->req->zero = value < w_length; cdev->req->length = value; rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); if (rc < 0) pr_err("response queue error: %d\n", rc); } return value; } static int mbim_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_mbim *mbim = func_to_mbim(f); struct usb_composite_dev *cdev = mbim->cdev; int ret = 0; pr_debug("intf=%u, alt=%u\n", intf, alt); /* Control interface has only altsetting 0 */ if (intf == mbim->ctrl_id) { pr_info("CONTROL_INTERFACE\n"); if (alt != 0) goto fail; if (mbim->not_port.notify->driver_data) { pr_info("reset mbim control %d\n", intf); usb_ep_disable(mbim->not_port.notify); } ret = config_ep_by_speed(cdev->gadget, f, mbim->not_port.notify); if (ret) { mbim->not_port.notify->desc = NULL; pr_err("Failed configuring notify ep %s: err %d\n", mbim->not_port.notify->name, ret); return ret; } ret = usb_ep_enable(mbim->not_port.notify); if (ret) { pr_err("usb ep#%s enable failed, err#%d\n", mbim->not_port.notify->name, ret); return ret; } mbim->not_port.notify->driver_data = mbim; /* Data interface has two altsettings, 0 and 1 */ } else if (intf == mbim->data_id) { pr_info("DATA_INTERFACE id %d, data interface status %d\n", mbim->data_id, mbim->data_interface_up); if (alt > 1) goto fail; if (mbim->data_interface_up == alt) return 0; if (mbim->bam_port.in->driver_data) { pr_info("reset mbim, alt-%d\n", alt); mbim_reset_values(mbim); } if (alt == 0) { /* * perform bam data disconnect handshake upon usb * disconnect */ switch (mbim->xport) { case USB_GADGET_XPORT_BAM_DMUX: gbam_mbim_disconnect(); break; case USB_GADGET_XPORT_BAM2BAM_IPA: bam_data_disconnect(&mbim->bam_port, USB_FUNC_MBIM, mbim->port_num); if (!gadget_is_dwc3(cdev->gadget)) break; if (msm_ep_unconfig(mbim->bam_port.in) || msm_ep_unconfig(mbim->bam_port.out)) { pr_err("ep_unconfig failed\n"); goto fail; } default: pr_err("unknown transport\n"); } goto notify_ready; } pr_info("Alt set 1, initialize ports\n"); /* * CDC Network only sends data in non-default altsettings. * Changing altsettings resets filters, statistics, etc. */ pr_info("Choose endpoints\n"); ret = config_ep_by_speed(cdev->gadget, f, mbim->bam_port.in); if (ret) { mbim->bam_port.in->desc = NULL; pr_err("IN ep %s failed: %d\n", mbim->bam_port.in->name, ret); return ret; } pr_info("Set mbim port in_desc = 0x%p\n", mbim->bam_port.in->desc); ret = config_ep_by_speed(cdev->gadget, f, mbim->bam_port.out); if (ret) { mbim->bam_port.out->desc = NULL; pr_err("OUT ep %s failed: %d\n", mbim->bam_port.out->name, ret); return ret; } pr_info("Set mbim port out_desc = 0x%p\n", mbim->bam_port.out->desc); pr_debug("Activate mbim\n"); switch (mbim->xport) { case USB_GADGET_XPORT_BAM_DMUX: gbam_mbim_connect(cdev->gadget, mbim->bam_port.in, mbim->bam_port.out); break; case USB_GADGET_XPORT_BAM2BAM_IPA: if (gadget_is_dwc3(cdev->gadget)) { if (msm_ep_config(mbim->bam_port.in) || msm_ep_config(mbim->bam_port.out)) { pr_err("%s: ep_config failed\n", __func__); goto fail; } } ret = bam_data_connect(&mbim->bam_port, mbim->xport, mbim->port_num, USB_FUNC_MBIM); if (ret) { pr_err("bam_data_setup failed:err:%d\n", ret); goto fail; } break; default: pr_err("unknown transport\n"); } notify_ready: mbim->data_interface_up = alt; spin_lock(&mbim->lock); mbim->not_port.notify_state = MBIM_NOTIFY_RESPONSE_AVAILABLE; spin_unlock(&mbim->lock); } else { goto fail; } atomic_set(&mbim->online, 1); pr_info("SET DEVICE ONLINE\n"); return 0; fail: pr_err("ERROR: Illegal Interface\n"); return -EINVAL; } /* * Because the data interface supports multiple altsettings, * this MBIM function *MUST* implement a get_alt() method. */ static int mbim_get_alt(struct usb_function *f, unsigned intf) { struct f_mbim *mbim = func_to_mbim(f); if (intf == mbim->ctrl_id) return 0; else if (intf == mbim->data_id) return mbim->data_interface_up; return -EINVAL; } static void mbim_disable(struct usb_function *f) { struct f_mbim *mbim = func_to_mbim(f); struct usb_composite_dev *cdev = mbim->cdev; pr_info("SET DEVICE OFFLINE\n"); atomic_set(&mbim->online, 0); mbim->remote_wakeup_enabled = 0; /* Disable Control Path */ if (mbim->not_port.notify->driver_data) { usb_ep_disable(mbim->not_port.notify); mbim->not_port.notify->driver_data = NULL; } atomic_set(&mbim->not_port.notify_count, 0); mbim->not_port.notify_state = MBIM_NOTIFY_NONE; mbim_clear_queues(mbim); mbim_reset_function_queue(mbim); /* Disable Data Path - only if it was initialized already (alt=1) */ if (!mbim->data_interface_up) { pr_debug("MBIM data interface is not opened. Returning\n"); return; } switch (mbim->xport) { case USB_GADGET_XPORT_BAM_DMUX: gbam_mbim_disconnect(); break; case USB_GADGET_XPORT_BAM2BAM_IPA: if (gadget_is_dwc3(cdev->gadget)) { msm_ep_unconfig(mbim->bam_port.out); msm_ep_unconfig(mbim->bam_port.in); } bam_data_disconnect(&mbim->bam_port, USB_FUNC_MBIM, mbim->port_num); break; default: pr_err("unknown transport\n"); } mbim->data_interface_up = false; pr_info("mbim deactivated\n"); } #define MBIM_ACTIVE_PORT 0 static void mbim_suspend(struct usb_function *f) { struct f_mbim *mbim = func_to_mbim(f); pr_info("mbim suspended\n"); pr_debug("%s(): remote_wakeup:%d\n:", __func__, mbim->cdev->gadget->remote_wakeup); if (mbim->xport == USB_GADGET_XPORT_BAM_DMUX) return; /* If the function is in Function Suspend state, avoid suspending the * MBIM function again. */ if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) && f->func_is_suspended) return; if (mbim->cdev->gadget->speed == USB_SPEED_SUPER) mbim->remote_wakeup_enabled = f->func_wakeup_allowed; else mbim->remote_wakeup_enabled = mbim->cdev->gadget->remote_wakeup; /* MBIM data interface is up only when alt setting is set to 1. */ if (!mbim->data_interface_up) { pr_debug("MBIM data interface is not opened. Returning\n"); return; } if (!mbim->remote_wakeup_enabled) atomic_set(&mbim->online, 0); bam_data_suspend(&mbim->bam_port, mbim->port_num, USB_FUNC_MBIM, mbim->remote_wakeup_enabled); } static void mbim_resume(struct usb_function *f) { struct f_mbim *mbim = func_to_mbim(f); pr_info("mbim resumed\n"); if (mbim->xport == USB_GADGET_XPORT_BAM_DMUX) return; /* * If the function is in USB3 Function Suspend state, resume is * canceled. In this case resume is done by a Function Resume request. */ if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) && f->func_is_suspended) return; /* resume control path by queuing notify req */ spin_lock(&mbim->lock); mbim_do_notify(mbim); spin_unlock(&mbim->lock); /* MBIM data interface is up only when alt setting is set to 1. */ if (!mbim->data_interface_up) { pr_debug("MBIM data interface is not opened. Returning\n"); return; } if (!mbim->remote_wakeup_enabled) atomic_set(&mbim->online, 1); bam_data_resume(&mbim->bam_port, mbim->port_num, USB_FUNC_MBIM, mbim->remote_wakeup_enabled); } static int mbim_func_suspend(struct usb_function *f, unsigned char options) { enum { MBIM_FUNC_SUSPEND_MASK = 0x1, MBIM_FUNC_WAKEUP_EN_MASK = 0x2 }; bool func_wakeup_allowed; struct f_mbim *mbim = func_to_mbim(f); if (f == NULL) return -EINVAL; pr_debug("Got Function Suspend(%u) command for %s function\n", options, f->name ? f->name : ""); /* Function Suspend is supported by Super Speed devices only */ if (mbim->cdev->gadget->speed != USB_SPEED_SUPER) return -ENOTSUPP; func_wakeup_allowed = ((options & MBIM_FUNC_WAKEUP_EN_MASK) != 0); if (options & MBIM_FUNC_SUSPEND_MASK) { f->func_wakeup_allowed = func_wakeup_allowed; if (!f->func_is_suspended) { mbim_suspend(f); f->func_is_suspended = true; } } else { if (f->func_is_suspended) { f->func_is_suspended = false; mbim_resume(f); } f->func_wakeup_allowed = func_wakeup_allowed; } return 0; } static int mbim_get_status(struct usb_function *f) { enum { MBIM_STS_FUNC_WAKEUP_CAP_SHIFT = 0, MBIM_STS_FUNC_WAKEUP_EN_SHIFT = 1 }; unsigned remote_wakeup_enabled_bit; const unsigned remote_wakeup_capable_bit = 1; remote_wakeup_enabled_bit = f->func_wakeup_allowed ? 1 : 0; return (remote_wakeup_enabled_bit << MBIM_STS_FUNC_WAKEUP_EN_SHIFT) | (remote_wakeup_capable_bit << MBIM_STS_FUNC_WAKEUP_CAP_SHIFT); } /*---------------------- function driver setup/binding ---------------------*/ static int mbim_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_mbim *mbim = func_to_mbim(f); int status; struct usb_ep *ep; struct usb_cdc_notification *event; pr_info("Enter\n"); mbim->cdev = cdev; /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) goto fail; mbim->ctrl_id = status; mbim_iad_desc.bFirstInterface = status; mbim_control_intf.bInterfaceNumber = status; mbim_union_desc.bMasterInterface0 = status; status = usb_interface_id(c, f); if (status < 0) goto fail; mbim->data_id = status; mbim->data_interface_up = false; mbim_data_nop_intf.bInterfaceNumber = status; mbim_data_intf.bInterfaceNumber = status; mbim_union_desc.bSlaveInterface0 = status; mbim->bam_port.cdev = cdev; mbim->bam_port.func = &mbim->function; status = -ENODEV; /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_in_desc); if (!ep) { pr_err("usb epin autoconfig failed\n"); goto fail; } pr_info("usb epin autoconfig succeeded\n"); ep->driver_data = cdev; /* claim */ mbim->bam_port.in = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_out_desc); if (!ep) { pr_err("usb epout autoconfig failed\n"); goto fail; } pr_info("usb epout autoconfig succeeded\n"); ep->driver_data = cdev; /* claim */ mbim->bam_port.out = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_notify_desc); if (!ep) { pr_err("usb notify ep autoconfig failed\n"); goto fail; } pr_info("usb notify ep autoconfig succeeded\n"); mbim->not_port.notify = ep; ep->driver_data = cdev; /* claim */ status = -ENOMEM; /* allocate notification request and buffer */ mbim->not_port.notify_req = mbim_alloc_req(ep, NCM_STATUS_BYTECOUNT, cdev->gadget->extra_buf_alloc); if (!mbim->not_port.notify_req) { pr_info("failed to allocate notify request\n"); goto fail; } pr_info("allocated notify ep request & request buffer\n"); mbim->not_port.notify_req->context = mbim; mbim->not_port.notify_req->complete = mbim_notify_complete; mbim->not_port.notify_req->length = sizeof(*event); event = mbim->not_port.notify_req->buf; event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; event->wValue = cpu_to_le16(0); event->wIndex = cpu_to_le16(mbim->ctrl_id); event->wLength = cpu_to_le16(0); /* copy descriptors, and track endpoint copies */ f->fs_descriptors = usb_copy_descriptors(mbim_fs_function); if (!f->fs_descriptors) goto fail; /* * support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at * both speeds */ if (gadget_is_dualspeed(c->cdev->gadget)) { hs_mbim_in_desc.bEndpointAddress = fs_mbim_in_desc.bEndpointAddress; hs_mbim_out_desc.bEndpointAddress = fs_mbim_out_desc.bEndpointAddress; hs_mbim_notify_desc.bEndpointAddress = fs_mbim_notify_desc.bEndpointAddress; /* copy descriptors, and track endpoint copies */ f->hs_descriptors = usb_copy_descriptors(mbim_hs_function); if (!f->hs_descriptors) goto fail; } if (gadget_is_superspeed(c->cdev->gadget)) { ss_mbim_in_desc.bEndpointAddress = fs_mbim_in_desc.bEndpointAddress; ss_mbim_out_desc.bEndpointAddress = fs_mbim_out_desc.bEndpointAddress; ss_mbim_notify_desc.bEndpointAddress = fs_mbim_notify_desc.bEndpointAddress; /* copy descriptors, and track endpoint copies */ f->ss_descriptors = usb_copy_descriptors(mbim_ss_function); if (!f->ss_descriptors) goto fail; } /* * If MBIM is bound in a config other than the first, tell Windows * about it by returning the num as a string in the OS descriptor's * subCompatibleID field. Windows only supports up to config #4. */ if (c->bConfigurationValue >= 2 && c->bConfigurationValue <= 4) { pr_debug("MBIM in configuration %d\n", c->bConfigurationValue); mbim_ext_config_desc.function.subCompatibleID[0] = c->bConfigurationValue + '0'; } pr_info("mbim(%d): %s speed IN/%s OUT/%s NOTIFY/%s\n", mbim->port_num, gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", mbim->bam_port.in->name, mbim->bam_port.out->name, mbim->not_port.notify->name); return 0; fail: pr_err("%s failed to bind, err %d\n", f->name, status); if (f->ss_descriptors) usb_free_descriptors(f->ss_descriptors); if (f->hs_descriptors) usb_free_descriptors(f->hs_descriptors); if (f->fs_descriptors) usb_free_descriptors(f->fs_descriptors); if (mbim->not_port.notify_req) { kfree(mbim->not_port.notify_req->buf); usb_ep_free_request(mbim->not_port.notify, mbim->not_port.notify_req); } /* we might as well release our claims on endpoints */ if (mbim->not_port.notify) mbim->not_port.notify->driver_data = NULL; if (mbim->bam_port.out) mbim->bam_port.out->driver_data = NULL; if (mbim->bam_port.in) mbim->bam_port.in->driver_data = NULL; return status; } static void mbim_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_mbim *mbim = func_to_mbim(f); pr_debug("unbinding mbim\n"); if (gadget_is_superspeed(c->cdev->gadget)) usb_free_descriptors(f->ss_descriptors); if (gadget_is_dualspeed(c->cdev->gadget)) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->fs_descriptors); kfree(mbim->not_port.notify_req->buf); usb_ep_free_request(mbim->not_port.notify, mbim->not_port.notify_req); mbim_ext_config_desc.function.subCompatibleID[0] = 0; } /** * mbim_bind_config - add MBIM link to a configuration * @c: the configuration to support the network link * Context: single threaded during gadget setup * Returns zero on success, else negative errno. */ int mbim_bind_config(struct usb_configuration *c, unsigned portno, char *xport_name) { struct f_mbim *mbim = NULL; int status = 0; pr_info("port number %u\n", portno); if (portno >= nr_mbim_ports) { pr_err("Can not add port %u. Max ports = %d\n", portno, nr_mbim_ports); return -ENODEV; } /* allocate and initialize one new instance */ mbim = mbim_ports[portno].port; if (!mbim) { pr_err("mbim struct not allocated\n"); return -ENOMEM; } mbim->xport = str_to_xport(xport_name); switch (mbim->xport) { case USB_GADGET_XPORT_BAM2BAM: /* Override BAM2BAM to BAM_DMUX for old ABI compatibility */ mbim->xport = USB_GADGET_XPORT_BAM_DMUX; /* fall-through */ case USB_GADGET_XPORT_BAM_DMUX: status = gbam_mbim_setup(); if (status) break; break; case USB_GADGET_XPORT_BAM2BAM_IPA: status = mbim_bam_setup(nr_mbim_ports); if (status) break; mbim_ntb_parameters.wNtbOutMaxDatagrams = 16; /* For IPA this is proven to give maximum throughput */ mbim_ntb_parameters.dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE_IPA); mbim_ntb_parameters.dwNtbOutMaxSize = cpu_to_le32(MBIM_NTB_OUT_SIZE_IPA); /* update rx buffer size to be used by usb rx request buffer */ mbim->bam_port.rx_buffer_size = MBIM_NTB_OUT_SIZE_IPA; mbim_ntb_parameters.wNdpInDivisor = 1; pr_debug("MBIM: dwNtbOutMaxSize:%d\n", MBIM_NTB_OUT_SIZE_IPA); break; default: status = -EINVAL; } if (status) { pr_err("%s transport setup failed\n", xport_name); return status; } /* maybe allocate device-global string IDs */ if (mbim_string_defs[0].id == 0) { /* control interface label */ status = usb_string_id(c->cdev); if (status < 0) return status; mbim_string_defs[STRING_CTRL_IDX].id = status; mbim_control_intf.iInterface = status; /* data interface label */ status = usb_string_id(c->cdev); if (status < 0) return status; mbim_string_defs[STRING_DATA_IDX].id = status; mbim_data_nop_intf.iInterface = status; mbim_data_intf.iInterface = status; } mbim->cdev = c->cdev; mbim_reset_values(mbim); mbim->function.name = "usb_mbim"; mbim->function.strings = mbim_strings; mbim->function.bind = mbim_bind; mbim->function.unbind = mbim_unbind; mbim->function.set_alt = mbim_set_alt; mbim->function.get_alt = mbim_get_alt; mbim->function.setup = mbim_setup; mbim->function.disable = mbim_disable; mbim->function.suspend = mbim_suspend; mbim->function.func_suspend = mbim_func_suspend; mbim->function.get_status = mbim_get_status; mbim->function.resume = mbim_resume; INIT_LIST_HEAD(&mbim->cpkt_req_q); INIT_LIST_HEAD(&mbim->cpkt_resp_q); status = usb_add_function(c, &mbim->function); pr_info("Exit status %d\n", status); return status; } /* ------------ MBIM DRIVER File Operations API for USER SPACE ------------ */ static ssize_t mbim_read(struct file *fp, char __user *buf, size_t count, loff_t *pos) { struct f_mbim *dev = fp->private_data; struct ctrl_pkt *cpkt = NULL; unsigned long flags; int ret = 0; pr_debug("Enter(%zu)\n", count); if (!dev) { pr_err("Received NULL mbim pointer\n"); return -ENODEV; } if (count > MBIM_BULK_BUFFER_SIZE) { pr_err("Buffer size is too big %zu, should be at most %d\n", count, MBIM_BULK_BUFFER_SIZE); return -EINVAL; } if (mbim_lock(&dev->read_excl)) { pr_err("Previous reading is not finished yet\n"); return -EBUSY; } if (atomic_read(&dev->error)) { mbim_unlock(&dev->read_excl); return -EIO; } spin_lock_irqsave(&dev->lock, flags); while (list_empty(&dev->cpkt_req_q)) { pr_debug("Requests list is empty. Wait.\n"); spin_unlock_irqrestore(&dev->lock, flags); ret = wait_event_interruptible(dev->read_wq, !list_empty(&dev->cpkt_req_q)); if (ret < 0) { pr_err("Waiting failed\n"); mbim_unlock(&dev->read_excl); return -ERESTARTSYS; } pr_debug("Received request packet\n"); spin_lock_irqsave(&dev->lock, flags); } cpkt = list_first_entry(&dev->cpkt_req_q, struct ctrl_pkt, list); if (cpkt->len > count) { spin_unlock_irqrestore(&dev->lock, flags); mbim_unlock(&dev->read_excl); pr_err("cpkt size too big:%d > buf size:%zu\n", cpkt->len, count); return -ENOMEM; } pr_debug("cpkt size:%d\n", cpkt->len); list_del(&cpkt->list); spin_unlock_irqrestore(&dev->lock, flags); mbim_unlock(&dev->read_excl); ret = copy_to_user(buf, cpkt->buf, cpkt->len); if (ret) { pr_err("copy_to_user failed: err %d\n", ret); ret = -ENOMEM; } else { pr_debug("copied %d bytes to user\n", cpkt->len); ret = cpkt->len; } mbim_free_ctrl_pkt(cpkt); return ret; } static ssize_t mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos) { struct f_mbim *dev = fp->private_data; struct ctrl_pkt *cpkt = NULL; struct usb_request *req = dev->not_port.notify_req; int ret = 0; unsigned long flags; pr_debug("Enter(%zu)\n", count); if (!dev || !req || !req->buf) { pr_err("%s: dev %p req %p req->buf %p\n", __func__, dev, req, req ? req->buf : req); return -ENODEV; } if (!count || count > MAX_CTRL_PKT_SIZE) { pr_err("error: ctrl pkt lenght %zu\n", count); return -EINVAL; } if (mbim_lock(&dev->write_excl)) { pr_err("Previous writing not finished yet\n"); return -EBUSY; } if (!atomic_read(&dev->online)) { pr_err("USB cable not connected\n"); mbim_unlock(&dev->write_excl); return -EPIPE; } if (dev->not_port.notify_state != MBIM_NOTIFY_RESPONSE_AVAILABLE) { pr_err("dev:%p state=%d error\n", dev, dev->not_port.notify_state); mbim_unlock(&dev->write_excl); return -EINVAL; } if (dev->function.func_is_suspended && !dev->function.func_wakeup_allowed) { dev->cpkt_drop_cnt++; pr_err("drop ctrl pkt of len %zu\n", count); return -ENOTSUPP; } cpkt = mbim_alloc_ctrl_pkt(count, GFP_KERNEL); if (!cpkt) { pr_err("failed to allocate ctrl pkt\n"); mbim_unlock(&dev->write_excl); return -ENOMEM; } ret = copy_from_user(cpkt->buf, buf, count); if (ret) { pr_err("copy_from_user failed err:%d\n", ret); mbim_free_ctrl_pkt(cpkt); mbim_unlock(&dev->write_excl); return ret; } spin_lock_irqsave(&dev->lock, flags); list_add_tail(&cpkt->list, &dev->cpkt_resp_q); if (atomic_inc_return(&dev->not_port.notify_count) != 1) { pr_debug("delay ep_queue: notifications queue is busy[%d]\n", atomic_read(&dev->not_port.notify_count)); spin_unlock_irqrestore(&dev->lock, flags); mbim_unlock(&dev->write_excl); return count; } spin_unlock_irqrestore(&dev->lock, flags); ret = usb_func_ep_queue(&dev->function, dev->not_port.notify, req, GFP_ATOMIC); if (ret == -ENOTSUPP || (ret < 0 && ret != -EAGAIN)) { spin_lock_irqsave(&dev->lock, flags); /* check if device disconnected while we dropped lock */ if (atomic_read(&dev->online)) { list_del(&cpkt->list); atomic_dec(&dev->not_port.notify_count); mbim_free_ctrl_pkt(cpkt); } dev->cpkt_drop_cnt++; spin_unlock_irqrestore(&dev->lock, flags); pr_err("drop ctrl pkt of len %d error %d\n", cpkt->len, ret); } else { ret = 0; } mbim_unlock(&dev->write_excl); pr_debug("Exit(%zu)\n", count); return ret ? ret : count; } static int mbim_open(struct inode *ip, struct file *fp) { pr_info("Open mbim driver\n"); while (!_mbim_dev) { pr_err("mbim_dev not created yet\n"); return -ENODEV; } if (mbim_lock(&_mbim_dev->open_excl)) { pr_err("Already opened\n"); return -EBUSY; } pr_info("Lock mbim_dev->open_excl for open\n"); if (!atomic_read(&_mbim_dev->online)) pr_err("USB cable not connected\n"); fp->private_data = _mbim_dev; atomic_set(&_mbim_dev->error, 0); pr_info("Exit, mbim file opened\n"); return 0; } static int mbim_release(struct inode *ip, struct file *fp) { pr_info("Close mbim file\n"); mbim_unlock(&_mbim_dev->open_excl); return 0; } #define BAM_DMUX_CHANNEL_ID 8 static long mbim_ioctl(struct file *fp, unsigned cmd, unsigned long arg) { struct f_mbim *mbim = fp->private_data; struct data_port *port; struct mbim_ipa_ep_info info; int ret = 0; pr_debug("Received command %d\n", cmd); if (!mbim) { pr_err("Bad parameter\n"); return -EINVAL; } if (mbim_lock(&mbim->ioctl_excl)) return -EBUSY; switch (cmd) { case MBIM_GET_NTB_SIZE: ret = copy_to_user((void __user *)arg, &mbim->ntb_input_size, sizeof(mbim->ntb_input_size)); if (ret) { pr_err("copying to user space failed\n"); ret = -EFAULT; } pr_info("Sent NTB size %d\n", mbim->ntb_input_size); break; case MBIM_GET_DATAGRAM_COUNT: ret = copy_to_user((void __user *)arg, &mbim->ntb_max_datagrams, sizeof(mbim->ntb_max_datagrams)); if (ret) { pr_err("copying to user space failed\n"); ret = -EFAULT; } pr_info("Sent NTB datagrams count %d\n", mbim->ntb_max_datagrams); break; case MBIM_EP_LOOKUP: if (!atomic_read(&mbim->online)) { pr_warn("usb cable is not connected\n"); return -ENOTCONN; } switch (mbim->xport) { case USB_GADGET_XPORT_BAM_DMUX: /* * Rmnet and MBIM share the same BAM-DMUX channel. * This channel number 8 should be in sync with * the one defined in u_bam.c. */ info.ph_ep_info.ep_type = MBIM_DATA_EP_TYPE_BAM_DMUX; info.ph_ep_info.peripheral_iface_id = BAM_DMUX_CHANNEL_ID; info.ipa_ep_pair.cons_pipe_num = 0; info.ipa_ep_pair.prod_pipe_num = 0; break; case USB_GADGET_XPORT_BAM2BAM_IPA: port = &mbim->bam_port; if ((port->ipa_producer_ep == -1) || (port->ipa_consumer_ep == -1)) { pr_err("EP_LOOKUP failed - IPA pipes not updated\n"); ret = -EAGAIN; break; } info.ph_ep_info.ep_type = MBIM_DATA_EP_TYPE_HSUSB; info.ph_ep_info.peripheral_iface_id = mbim->data_id; info.ipa_ep_pair.cons_pipe_num = port->ipa_consumer_ep; info.ipa_ep_pair.prod_pipe_num = port->ipa_producer_ep; break; default: ret = -ENODEV; pr_err("unknown transport\n"); break; } ret = copy_to_user((void __user *)arg, &info, sizeof(info)); if (ret) { pr_err("copying to user space failed\n"); ret = -EFAULT; } break; default: pr_err("wrong parameter\n"); ret = -EINVAL; } mbim_unlock(&mbim->ioctl_excl); return ret; } /* file operations for MBIM device /dev/android_mbim */ static const struct file_operations mbim_fops = { .owner = THIS_MODULE, .open = mbim_open, .release = mbim_release, .read = mbim_read, .write = mbim_write, .unlocked_ioctl = mbim_ioctl, }; static struct miscdevice mbim_device = { .minor = MISC_DYNAMIC_MINOR, .name = "android_mbim", .fops = &mbim_fops, }; static int mbim_init(int instances) { int i; struct f_mbim *dev = NULL; int ret; pr_info("initialize %d instances\n", instances); if (instances > NR_MBIM_PORTS) { pr_err("Max-%d instances supported\n", NR_MBIM_PORTS); return -EINVAL; } for (i = 0; i < instances; i++) { dev = kzalloc(sizeof(struct f_mbim), GFP_KERNEL); if (!dev) { pr_err("Failed to allocate mbim dev\n"); ret = -ENOMEM; goto fail_probe; } dev->port_num = i; dev->bam_port.ipa_consumer_ep = -1; dev->bam_port.ipa_producer_ep = -1; spin_lock_init(&dev->lock); INIT_LIST_HEAD(&dev->cpkt_req_q); INIT_LIST_HEAD(&dev->cpkt_resp_q); mbim_ports[i].port = dev; mbim_ports[i].port_num = i; init_waitqueue_head(&dev->read_wq); atomic_set(&dev->open_excl, 0); atomic_set(&dev->ioctl_excl, 0); atomic_set(&dev->read_excl, 0); atomic_set(&dev->write_excl, 0); nr_mbim_ports++; } _mbim_dev = dev; ret = misc_register(&mbim_device); if (ret) { pr_err("mbim driver failed to register\n"); goto fail_probe; } pr_info("Initialized %d ports\n", nr_mbim_ports); return ret; fail_probe: pr_err("Failed\n"); for (i = 0; i < nr_mbim_ports; i++) { kfree(mbim_ports[i].port); mbim_ports[i].port = NULL; } return ret; } static void fmbim_cleanup(void) { int i = 0; pr_info("Enter\n"); for (i = 0; i < nr_mbim_ports; i++) { kfree(mbim_ports[i].port); mbim_ports[i].port = NULL; } nr_mbim_ports = 0; misc_deregister(&mbim_device); _mbim_dev = NULL; }