/* * hdm_usb.c - Hardware dependent module for USB * * Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG * * 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. * * This file is licensed under GPLv2. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mostcore.h" #include "networking.h" #define USB_MTU 512 #define NO_ISOCHRONOUS_URB 0 #define AV_PACKETS_PER_XACT 2 #define BUF_CHAIN_SIZE 0xFFFF #define MAX_NUM_ENDPOINTS 30 #define MAX_SUFFIX_LEN 10 #define MAX_STRING_LEN 80 #define MAX_BUF_SIZE 0xFFFF #define USB_VENDOR_ID_SMSC 0x0424 /* VID: SMSC */ #define USB_DEV_ID_BRDG 0xC001 /* PID: USB Bridge */ #define USB_DEV_ID_OS81118 0xCF18 /* PID: USB OS81118 */ #define USB_DEV_ID_OS81119 0xCF19 /* PID: USB OS81119 */ #define USB_DEV_ID_OS81210 0xCF30 /* PID: USB OS81210 */ /* DRCI Addresses */ #define DRCI_REG_NI_STATE 0x0100 #define DRCI_REG_PACKET_BW 0x0101 #define DRCI_REG_NODE_ADDR 0x0102 #define DRCI_REG_NODE_POS 0x0103 #define DRCI_REG_MEP_FILTER 0x0140 #define DRCI_REG_HASH_TBL0 0x0141 #define DRCI_REG_HASH_TBL1 0x0142 #define DRCI_REG_HASH_TBL2 0x0143 #define DRCI_REG_HASH_TBL3 0x0144 #define DRCI_REG_HW_ADDR_HI 0x0145 #define DRCI_REG_HW_ADDR_MI 0x0146 #define DRCI_REG_HW_ADDR_LO 0x0147 #define DRCI_REG_BASE 0x1100 #define DRCI_COMMAND 0x02 #define DRCI_READ_REQ 0xA0 #define DRCI_WRITE_REQ 0xA1 /** * struct most_dci_obj - Direct Communication Interface * @kobj:position in sysfs * @usb_device: pointer to the usb device * @reg_addr: register address for arbitrary DCI access */ struct most_dci_obj { struct kobject kobj; struct usb_device *usb_device; u16 reg_addr; }; #define to_dci_obj(p) container_of(p, struct most_dci_obj, kobj) struct most_dev; struct clear_hold_work { struct work_struct ws; struct most_dev *mdev; unsigned int channel; int pipe; }; #define to_clear_hold_work(w) container_of(w, struct clear_hold_work, ws) /** * struct most_dev - holds all usb interface specific stuff * @parent: parent object in sysfs * @usb_device: pointer to usb device * @iface: hardware interface * @cap: channel capabilities * @conf: channel configuration * @dci: direct communication interface of hardware * @ep_address: endpoint address table * @description: device description * @suffix: suffix for channel name * @channel_lock: synchronize channel access * @padding_active: indicates channel uses padding * @is_channel_healthy: health status table of each channel * @busy_urbs: list of anchored items * @io_mutex: synchronize I/O with disconnect * @link_stat_timer: timer for link status reports * @poll_work_obj: work for polling link status */ struct most_dev { struct kobject *parent; struct usb_device *usb_device; struct most_interface iface; struct most_channel_capability *cap; struct most_channel_config *conf; struct most_dci_obj *dci; u8 *ep_address; char description[MAX_STRING_LEN]; char suffix[MAX_NUM_ENDPOINTS][MAX_SUFFIX_LEN]; spinlock_t channel_lock[MAX_NUM_ENDPOINTS]; /* sync channel access */ bool padding_active[MAX_NUM_ENDPOINTS]; bool is_channel_healthy[MAX_NUM_ENDPOINTS]; struct clear_hold_work clear_work[MAX_NUM_ENDPOINTS]; struct usb_anchor *busy_urbs; struct mutex io_mutex; struct timer_list link_stat_timer; struct work_struct poll_work_obj; }; #define to_mdev(d) container_of(d, struct most_dev, iface) #define to_mdev_from_work(w) container_of(w, struct most_dev, poll_work_obj) static void wq_clear_halt(struct work_struct *wq_obj); static void wq_netinfo(struct work_struct *wq_obj); /** * drci_rd_reg - read a DCI register * @dev: usb device * @reg: register address * @buf: buffer to store data * * This is reads data from INIC's direct register communication interface */ static inline int drci_rd_reg(struct usb_device *dev, u16 reg, u16 *buf) { int retval; u16 *dma_buf = kzalloc(sizeof(u16), GFP_KERNEL); u8 req_type = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE; if (!dma_buf) return -ENOMEM; retval = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), DRCI_READ_REQ, req_type, 0x0000, reg, dma_buf, sizeof(u16), 5 * HZ); *buf = le16_to_cpu(*dma_buf); kfree(dma_buf); return retval; } /** * drci_wr_reg - write a DCI register * @dev: usb device * @reg: register address * @data: data to write * * This is writes data to INIC's direct register communicatio
{
    "name": "agl-homescreen",
    "version": "0.0.0",
    "description": "Homescreen project for AGL based on html5 technologies",
    "scripts": {
        "webpack": "webpack",
        "build": "webpack",
        "start": "webpack-dev-server"
    },
    "homepage": "https://bitbucket.org/smarsol/homescreen",
    "author": "humberto.alfonso@asvito.es",
    "repository": {
        "type": "git",
        "url": "git@bitbucket.org:smarsol/homescreen.git"
    },
    "keywords": [
        "agl",
        "automotive",
        "automotivegradelinux"
    ],
    "devDependencies": {
        "clean-webpack-plugin": "^1.0.1",
        "copy-webpack-plugin": "^4.6.0",
        "css-loader": "^2.1.0",
        "file-loader": "^4.0.0",
        "html-webpack-plugin": "^3.2.0",
        "image-webpack-loader": "^5.0.0",
        "mini-css-extract-plugin": "^0.5.0",
        "node-sass": "^4.12.0",
        "sass-loader": "^7.1.0",
        "style-loader": "^0.23.1",
        "uglifyjs-webpack-plugin": "^2.1.1",
        "webpack": "^4.29.5",
        "webpack-cli": "^3.2.3",
        "webpack-dev-server": "^3.7.2",
        "zip-webpack-plugin": "^3.0.0"
    },
    "dependencies": {
        "@iconfu/svg-inject": "^1.2.3",
        "bootstrap": "^4.3.1"
    }
}
out of range.\n"); return -EINVAL; } if (!conf->num_buffers || !conf->buffer_size) { dev_err(dev, "Misconfig: buffer size or #buffers zero.\n"); return -EINVAL; } if (conf->data_type != MOST_CH_SYNC && !(conf->data_type == MOST_CH_ISOC && conf->packets_per_xact != 0xFF)) { mdev->padding_active[channel] = false; goto exit; } mdev->padding_active[channel] = true; temp_size = conf->buffer_size; frame_size = get_stream_frame_size(conf); if (frame_size == 0 || frame_size > USB_MTU) { dev_warn(dev, "Misconfig: frame size wrong\n"); return -EINVAL; } if (conf->buffer_size % frame_size) { u16 tmp_val; tmp_val = conf->buffer_size / frame_size; conf->buffer_size = tmp_val * frame_size; dev_notice(dev, "Channel %d - rounding buffer size to %d bytes, channel config says %d bytes\n", channel, conf->buffer_size, temp_size); } num_frames = conf->buffer_size / frame_size; tail_space = num_frames * (USB_MTU - frame_size); temp_size += tail_space; /* calculate extra length to comply w/ HW padding */ conf->extra_len = (DIV_ROUND_UP(temp_size, USB_MTU) * USB_MTU) - conf->buffer_size; exit: mdev->conf[channel] = *conf; if (conf->data_type == MOST_CH_ASYNC) { u16 ep = mdev->ep_address[channel]; if (start_sync_ep(mdev->usb_device, ep) < 0) dev_warn(dev, "sync for ep%02x failed", ep); } return 0; } /** * hdm_request_netinfo - request network information * @iface: pointer to interface * @channel: channel ID * * This is used as trigger to set up the link status timer that * polls for the NI state of the INIC every 2 seconds. * */ static void hdm_request_netinfo(struct most_interface *iface, int channel) { struct most_dev *mdev; BUG_ON(!iface); mdev = to_mdev(iface); mdev->link_stat_timer.expires = jiffies + HZ; mod_timer(&mdev->link_stat_timer, mdev->link_stat_timer.expires); } /** * link_stat_timer_handler - schedule work obtaining mac address and link status * @data: pointer to USB device instance * * The handler runs in interrupt context. That's why we need to defer the * tasks to a work queue. */ static void link_stat_timer_handler(unsigned long data) { struct most_dev *mdev = (struct most_dev *)data; schedule_work(&mdev->poll_work_obj); mdev->link_stat_timer.expires = jiffies + (2 * HZ); add_timer(&mdev->link_stat_timer); } /** * wq_netinfo - work queue function to deliver latest networking information * @wq_obj: object that holds data for our deferred work to do * * This retrieves the network interface status of the USB INIC */ static void wq_netinfo(struct work_struct *wq_obj) { struct most_dev *mdev = to_mdev_from_work(wq_obj); struct usb_device *usb_device = mdev->usb_device; struct device *dev = &usb_device->dev; u16 hi, mi, lo, link; u8 hw_addr[6]; if (drci_rd_reg(usb_device, DRCI_REG_HW_ADDR_HI, &hi) < 0) { dev_err(dev, "Vendor request 'hw_addr_hi' failed\n"); return; } if (drci_rd_reg(usb_device, DRCI_REG_HW_ADDR_MI, &mi) < 0) { dev_err(dev, "Vendor request 'hw_addr_mid' failed\n"); return; } if (drci_rd_reg(usb_device, DRCI_REG_HW_ADDR_LO, &lo) < 0) { dev_err(dev, "Vendor request 'hw_addr_low' failed\n"); return; } if (drci_rd_reg(usb_device, DRCI_REG_NI_STATE, &link) < 0) { dev_err(dev, "Vendor request 'link status' failed\n"); return; } hw_addr[0] = hi >> 8; hw_addr[1] = hi; hw_addr[2] = mi >> 8; hw_addr[3] = mi; hw_addr[4] = lo >> 8; hw_addr[5] = lo; most_deliver_netinfo(&mdev->iface, link, hw_addr); } /** * wq_clear_halt - work queue function * @wq_obj: work_struct object to execute * * This sends a clear_halt to the given USB pipe. */ static void wq_clear_halt(struct work_struct *wq_obj) { struct clear_hold_work *clear_work = to_clear_hold_work(wq_obj); struct most_dev *mdev = clear_work->mdev; unsigned int channel = clear_work->channel; int pipe = clear_work->pipe; mutex_lock(&mdev->io_mutex); most_stop_enqueue(&mdev->iface, channel); usb_kill_anchored_urbs(&mdev->busy_urbs[channel]); if (usb_clear_halt(mdev->usb_device, pipe)) dev_warn(&mdev->usb_device->dev, "Failed to reset endpoint.\n"); mdev->is_channel_healthy[channel] = true; most_resume_enqueue(&mdev->iface, channel); mutex_unlock(&mdev->io_mutex); } /** * hdm_usb_fops - file operation table for USB driver */ static const struct file_operations hdm_usb_fops = { .owner = THIS_MODULE, }; /** * usb_device_id - ID table for HCD device probing */ static struct usb_device_id usbid[] = { { USB_DEVICE(USB_VENDOR_ID_SMSC, USB_DEV_ID_BRDG), }, { USB_DEVICE(USB_VENDOR_ID_SMSC, USB_DEV_ID_OS81118), }, { USB_DEVICE(USB_VENDOR_ID_SMSC, USB_DEV_ID_OS81119), }, { USB_DEVICE(USB_VENDOR_ID_SMSC, USB_DEV_ID_OS81210), }, { } /* Terminating entry */ }; #define MOST_DCI_RO_ATTR(_name) \ struct most_dci_attribute most_dci_attr_##_name = \ __ATTR(_name, S_IRUGO, show_value, NULL) #define MOST_DCI_ATTR(_name) \ struct most_dci_attribute most_dci_attr_##_name = \ __ATTR(_name, S_IRUGO | S_IWUSR, show_value, store_value) #define MOST_DCI_WO_ATTR(_name) \ struct most_dci_attribute most_dci_attr_##_name = \ __ATTR(_name, S_IWUSR, NULL, store_value) /** * struct most_dci_attribute - to access the attributes of a dci object * @attr: attributes of a dci object * @show: pointer to the show function * @store: pointer to the store function */ struct most_dci_attribute { struct attribute attr; ssize_t (*show)(struct most_dci_obj *d, struct most_dci_attribute *attr, char *buf); ssize_t (*store)(struct most_dci_obj *d, struct most_dci_attribute *attr, const char *buf, size_t count); }; #define to_dci_attr(a) container_of(a, struct most_dci_attribute, attr) /** * dci_attr_show - show function for dci object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer */ static ssize_t dci_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct most_dci_attribute *dci_attr = to_dci_attr(attr); struct most_dci_obj *dci_obj = to_dci_obj(kobj); if (!dci_attr->show) return -EIO; return dci_attr->show(dci_obj, dci_attr, buf); } /** * dci_attr_store - store function for dci object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer * @len: length of buffer */ static ssize_t dci_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct most_dci_attribute *dci_attr = to_dci_attr(attr); struct most_dci_obj *dci_obj = to_dci_obj(kobj); if (!dci_attr->store) return -EIO; return dci_attr->store(dci_obj, dci_attr, buf, len); } static const struct sysfs_ops most_dci_sysfs_ops = { .show = dci_attr_show, .store = dci_attr_store, }; /** * most_dci_release - release function for dci object * @kobj: pointer to kobject * * This frees the memory allocated for the dci object */ static void most_dci_release(struct kobject *kobj) { struct most_dci_obj *dci_obj = to_dci_obj(kobj); kfree(dci_obj); } struct regs { const char *name; u16 reg; }; static const struct regs ro_regs[] = { { "ni_state", DRCI_REG_NI_STATE }, { "packet_bandwidth", DRCI_REG_PACKET_BW }, { "node_address", DRCI_REG_NODE_ADDR }, { "node_position", DRCI_REG_NODE_POS }, }; static const struct regs rw_regs[] = { { "mep_filter", DRCI_REG_MEP_FILTER }, { "mep_hash0", DRCI_REG_HASH_TBL0 }, { "mep_hash1", DRCI_REG_HASH_TBL1 }, { "mep_hash2", DRCI_REG_HASH_TBL2 }, { "mep_hash3", DRCI_REG_HASH_TBL3 }, { "mep_eui48_hi", DRCI_REG_HW_ADDR_HI }, { "mep_eui48_mi", DRCI_REG_HW_ADDR_MI }, { "mep_eui48_lo", DRCI_REG_HW_ADDR_LO }, }; static int get_stat_reg_addr(const struct regs *regs, int size, const char *name, u16 *reg_addr) { int i; for (i = 0; i < size; i++) { if (!strcmp(name, regs[i].name)) { *reg_addr = regs[i].reg; return 0; } } return -EFAULT; } #define get_static_reg_addr(regs, name, reg_addr) \ get_stat_reg_addr(regs, ARRAY_SIZE(regs), name, reg_addr) static ssize_t show_value(struct most_dci_obj *dci_obj, struct most_dci_attribute *attr, char *buf) { const char *name = attr->attr.name; u16 val; u16 reg_addr; int err; if (!strcmp(name, "arb_address")) return snprintf(buf, PAGE_SIZE, "%04x\n", dci_obj->reg_addr); if (!strcmp(name, "arb_value")) reg_addr = dci_obj->reg_addr; else if (get_static_reg_addr(ro_regs, name, ®_addr) && get_static_reg_addr(rw_regs, name, ®_addr)) return -EFAULT; err = drci_rd_reg(dci_obj->usb_device, reg_addr, &val); if (err < 0) return err; return snprintf(buf, PAGE_SIZE, "%04x\n", val); } static ssize_t store_value(struct most_dci_obj *dci_obj, struct most_dci_attribute *attr, const char *buf, size_t count) { u16 val; u16 reg_addr; const char *name = attr->attr.name; struct usb_device *usb_dev = dci_obj->usb_device; int err = kstrtou16(buf, 16, &val); if (err) return err; if (!strcmp(name, "arb_address")) { dci_obj->reg_addr = val; return count; } if (!strcmp(name, "arb_value")) err = drci_wr_reg(usb_dev, dci_obj->reg_addr, val); else if (!strcmp(name, "sync_ep")) err = start_sync_ep(usb_dev, val); else if (!get_static_reg_addr(ro_regs, name, ®_addr)) err = drci_wr_reg(usb_dev, reg_addr, val); else return -EFAULT; if (err < 0) return err; return count; } static MOST_DCI_RO_ATTR(ni_state); static MOST_DCI_RO_ATTR(packet_bandwidth); static MOST_DCI_RO_ATTR(node_address); static MOST_DCI_RO_ATTR(node_position); static MOST_DCI_WO_ATTR(sync_ep); static MOST_DCI_ATTR(mep_filter); static MOST_DCI_ATTR(mep_hash0); static MOST_DCI_ATTR(mep_hash1); static MOST_DCI_ATTR(mep_hash2); static MOST_DCI_ATTR(mep_hash3); static MOST_DCI_ATTR(mep_eui48_hi); static MOST_DCI_ATTR(mep_eui48_mi); static MOST_DCI_ATTR(mep_eui48_lo); static MOST_DCI_ATTR(arb_address); static MOST_DCI_ATTR(arb_value); /** * most_dci_def_attrs - array of default attribute files of the dci object */ static struct attribute *most_dci_def_attrs[] = { &most_dci_attr_ni_state.attr, &most_dci_attr_packet_bandwidth.attr, &most_dci_attr_node_address.attr, &most_dci_attr_node_position.attr, &most_dci_attr_sync_ep.attr, &most_dci_attr_mep_filter.attr, &most_dci_attr_mep_hash0.attr, &most_dci_attr_mep_hash1.attr, &most_dci_attr_mep_hash2.attr, &most_dci_attr_mep_hash3.attr, &most_dci_attr_mep_eui48_hi.attr, &most_dci_attr_mep_eui48_mi.attr, &most_dci_attr_mep_eui48_lo.attr, &most_dci_attr_arb_address.attr, &most_dci_attr_arb_value.attr, NULL, }; /** * DCI ktype */ static struct kobj_type most_dci_ktype = { .sysfs_ops = &most_dci_sysfs_ops, .release = most_dci_release, .default_attrs = most_dci_def_attrs, }; /** * create_most_dci_obj - allocates a dci object * @parent: parent kobject * * This creates a dci object and registers it with sysfs. * Returns a pointer to the object or NULL when something went wrong. */ static struct most_dci_obj *create_most_dci_obj(struct kobject *parent) { struct most_dci_obj *most_dci = kzalloc(sizeof(*most_dci), GFP_KERNEL); int retval; if (!most_dci) return NULL; retval = kobject_init_and_add(&most_dci->kobj, &most_dci_ktype, parent, "dci"); if (retval) { kobject_put(&most_dci->kobj); return NULL; } return most_dci; } /** * destroy_most_dci_obj - DCI object release function * @p: pointer to dci object */ static void destroy_most_dci_obj(struct most_dci_obj *p) { kobject_put(&p->kobj); } /** * hdm_probe - probe function of USB device driver * @interface: Interface of the attached USB device * @id: Pointer to the USB ID table. * * This allocates and initializes the device instance, adds the new * entry to the internal list, scans the USB descriptors and registers * the interface with the core. * Additionally, the DCI objects are created and the hardware is sync'd. * * Return 0 on success. In case of an error a negative number is returned. */ static int hdm_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_host_interface *usb_iface_desc = interface->cur_altsetting; struct usb_device *usb_dev = interface_to_usbdev(interface); struct device *dev = &usb_dev->dev; struct most_dev *mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); unsigned int i; unsigned int num_endpoints; struct most_channel_capability *tmp_cap; struct usb_endpoint_descriptor *ep_desc; int ret = 0; if (!mdev) goto exit_ENOMEM; usb_set_intfdata(interface, mdev); num_endpoints = usb_iface_desc->desc.bNumEndpoints; mutex_init(&mdev->io_mutex); INIT_WORK(&mdev->poll_work_obj, wq_netinfo); setup_timer(&mdev->link_stat_timer, link_stat_timer_handler, (unsigned long)mdev); mdev->usb_device = usb_dev; mdev->link_stat_timer.expires = jiffies + (2 * HZ); mdev->iface.mod = hdm_usb_fops.owner; mdev->iface.interface = ITYPE_USB; mdev->iface.configure = hdm_configure_channel; mdev->iface.request_netinfo = hdm_request_netinfo; mdev->iface.enqueue = hdm_enqueue; mdev->iface.poison_channel = hdm_poison_channel; mdev->iface.description = mdev->description; mdev->iface.num_channels = num_endpoints; snprintf(mdev->description, sizeof(mdev->description), "usb_device %d-%s:%d.%d", usb_dev->bus->busnum, usb_dev->devpath, usb_dev->config->desc.bConfigurationValue, usb_iface_desc->desc.bInterfaceNumber); mdev->conf = kcalloc(num_endpoints, sizeof(*mdev->conf), GFP_KERNEL); if (!mdev->conf) goto exit_free; mdev->cap = kcalloc(num_endpoints, sizeof(*mdev->cap), GFP_KERNEL); if (!mdev->cap) goto exit_free1; mdev->iface.channel_vector = mdev->cap; mdev->iface.priv = NULL; mdev->ep_address = kcalloc(num_endpoints, sizeof(*mdev->ep_address), GFP_KERNEL); if (!mdev->ep_address) goto exit_free2; mdev->busy_urbs = kcalloc(num_endpoints, sizeof(*mdev->busy_urbs), GFP_KERNEL); if (!mdev->busy_urbs) goto exit_free3; tmp_cap = mdev->cap; for (i = 0; i < num_endpoints; i++) { ep_desc = &usb_iface_desc->endpoint[i].desc; mdev->ep_address[i] = ep_desc->bEndpointAddress; mdev->padding_active[i] = false; mdev->is_channel_healthy[i] = true; snprintf(&mdev->suffix[i][0], MAX_SUFFIX_LEN, "ep%02x", mdev->ep_address[i]); tmp_cap->name_suffix = &mdev->suffix[i][0]; tmp_cap->buffer_size_packet = MAX_BUF_SIZE; tmp_cap->buffer_size_streaming = MAX_BUF_SIZE; tmp_cap->num_buffers_packet = BUF_CHAIN_SIZE; tmp_cap->num_buffers_streaming = BUF_CHAIN_SIZE; tmp_cap->data_type = MOST_CH_CONTROL | MOST_CH_ASYNC | MOST_CH_ISOC | MOST_CH_SYNC; if (usb_endpoint_dir_in(ep_desc)) tmp_cap->direction = MOST_CH_RX; else tmp_cap->direction = MOST_CH_TX; tmp_cap++; init_usb_anchor(&mdev->busy_urbs[i]); spin_lock_init(&mdev->channel_lock[i]); } dev_notice(dev, "claimed gadget: Vendor=%4.4x ProdID=%4.4x Bus=%02x Device=%02x\n", le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct), usb_dev->bus->busnum, usb_dev->devnum); dev_notice(dev, "device path: /sys/bus/usb/devices/%d-%s:%d.%d\n", usb_dev->bus->busnum, usb_dev->devpath, usb_dev->config->desc.bConfigurationValue, usb_iface_desc->desc.bInterfaceNumber); mdev->parent = most_register_interface(&mdev->iface); if (IS_ERR(mdev->parent)) { ret = PTR_ERR(mdev->parent); goto exit_free4; } mutex_lock(&mdev->io_mutex); if (le16_to_cpu(usb_dev->descriptor.idProduct) == USB_DEV_ID_OS81118 || le16_to_cpu(usb_dev->descriptor.idProduct) == USB_DEV_ID_OS81119 || le16_to_cpu(usb_dev->descriptor.idProduct) == USB_DEV_ID_OS81210) { /* this increments the reference count of the instance * object of the core */ mdev->dci = create_most_dci_obj(mdev->parent); if (!mdev->dci) { mutex_unlock(&mdev->io_mutex); most_deregister_interface(&mdev->iface); ret = -ENOMEM; goto exit_free4; } kobject_uevent(&mdev->dci->kobj, KOBJ_ADD); mdev->dci->usb_device = mdev->usb_device; } mutex_unlock(&mdev->io_mutex); return 0; exit_free4: kfree(mdev->busy_urbs); exit_free3: kfree(mdev->ep_address); exit_free2: kfree(mdev->cap); exit_free1: kfree(mdev->conf); exit_free: kfree(mdev); exit_ENOMEM: if (ret == 0 || ret == -ENOMEM) { ret = -ENOMEM; dev_err(dev, "out of memory\n"); } return ret; } /** * hdm_disconnect - disconnect function of USB device driver * @interface: Interface of the attached USB device * * This deregisters the interface with the core, removes the kernel timer * and frees resources. * * Context: hub kernel thread */ static void hdm_disconnect(struct usb_interface *interface) { struct most_dev *mdev = usb_get_intfdata(interface); mutex_lock(&mdev->io_mutex); usb_set_intfdata(interface, NULL); mdev->usb_device = NULL; mutex_unlock(&mdev->io_mutex); del_timer_sync(&mdev->link_stat_timer); cancel_work_sync(&mdev->poll_work_obj); destroy_most_dci_obj(mdev->dci); most_deregister_interface(&mdev->iface); kfree(mdev->busy_urbs); kfree(mdev->cap); kfree(mdev->conf); kfree(mdev->ep_address); kfree(mdev); } static struct usb_driver hdm_usb = { .name = "hdm_usb", .id_table = usbid, .probe = hdm_probe, .disconnect = hdm_disconnect, }; static int __init hdm_usb_init(void) { pr_info("hdm_usb_init()\n"); if (usb_register(&hdm_usb)) { pr_err("could not register hdm_usb driver\n"); return -EIO; } return 0; } static void __exit hdm_usb_exit(void) { pr_info("hdm_usb_exit()\n"); usb_deregister(&hdm_usb); } module_init(hdm_usb_init); module_exit(hdm_usb_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Christian Gromm "); MODULE_DESCRIPTION("HDM_4_USB");