| /* Copyright (c) 2014-2016, 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. |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/kernel.h> |
| #include <linux/err.h> |
| #include <linux/sched.h> |
| #include <linux/ratelimit.h> |
| #include <linux/workqueue.h> |
| #include <linux/diagchar.h> |
| #include <linux/delay.h> |
| #include <linux/kmemleak.h> |
| #include <linux/list.h> |
| #ifdef CONFIG_DIAG_OVER_USB |
| #include <linux/usb/usbdiag.h> |
| #endif |
| #include "diag_usb.h" |
| #include "diag_mux.h" |
| #include "diagmem.h" |
| #include "diag_ipc_logging.h" |
| |
| #define DIAG_USB_STRING_SZ 10 |
| #define DIAG_USB_MAX_SIZE 16384 |
| |
| struct diag_usb_info diag_usb[NUM_DIAG_USB_DEV] = { |
| { |
| .id = DIAG_USB_LOCAL, |
| .name = DIAG_LEGACY, |
| .enabled = 0, |
| .mempool = POOL_TYPE_MUX_APPS, |
| .hdl = NULL, |
| .ops = NULL, |
| .read_buf = NULL, |
| .read_ptr = NULL, |
| .usb_wq = NULL, |
| .read_cnt = 0, |
| .write_cnt = 0, |
| .max_size = DIAG_USB_MAX_SIZE, |
| }, |
| #ifdef CONFIG_DIAGFWD_BRIDGE_CODE |
| { |
| .id = DIAG_USB_MDM, |
| .name = DIAG_MDM, |
| .enabled = 0, |
| .mempool = POOL_TYPE_MDM_MUX, |
| .hdl = NULL, |
| .ops = NULL, |
| .read_buf = NULL, |
| .read_ptr = NULL, |
| .usb_wq = NULL, |
| .read_cnt = 0, |
| .write_cnt = 0, |
| .max_size = DIAG_USB_MAX_SIZE, |
| }, |
| { |
| .id = DIAG_USB_MDM2, |
| .name = DIAG_MDM2, |
| .enabled = 0, |
| .mempool = POOL_TYPE_MDM2_MUX, |
| .hdl = NULL, |
| .ops = NULL, |
| .read_buf = NULL, |
| .read_ptr = NULL, |
| .usb_wq = NULL, |
| .read_cnt = 0, |
| .write_cnt = 0, |
| .max_size = DIAG_USB_MAX_SIZE, |
| }, |
| { |
| .id = DIAG_USB_QSC, |
| .name = DIAG_QSC, |
| .enabled = 0, |
| .mempool = POOL_TYPE_QSC_MUX, |
| .hdl = NULL, |
| .ops = NULL, |
| .read_buf = NULL, |
| .read_ptr = NULL, |
| .usb_wq = NULL, |
| .read_cnt = 0, |
| .write_cnt = 0, |
| .max_size = DIAG_USB_MAX_SIZE, |
| } |
| #endif |
| }; |
| |
| static int diag_usb_buf_tbl_add(struct diag_usb_info *usb_info, |
| unsigned char *buf, uint32_t len, int ctxt) |
| { |
| struct list_head *start, *temp; |
| struct diag_usb_buf_tbl_t *entry = NULL; |
| |
| list_for_each_safe(start, temp, &usb_info->buf_tbl) { |
| entry = list_entry(start, struct diag_usb_buf_tbl_t, track); |
| if (entry->buf == buf) { |
| atomic_inc(&entry->ref_count); |
| return 0; |
| } |
| } |
| |
| /* New buffer, not found in the list */ |
| entry = kzalloc(sizeof(struct diag_usb_buf_tbl_t), GFP_ATOMIC); |
| if (!entry) |
| return -ENOMEM; |
| |
| entry->buf = buf; |
| entry->ctxt = ctxt; |
| entry->len = len; |
| atomic_set(&entry->ref_count, 1); |
| INIT_LIST_HEAD(&entry->track); |
| list_add_tail(&entry->track, &usb_info->buf_tbl); |
| |
| return 0; |
| } |
| |
| static void diag_usb_buf_tbl_remove(struct diag_usb_info *usb_info, |
| unsigned char *buf) |
| { |
| struct list_head *start, *temp; |
| struct diag_usb_buf_tbl_t *entry = NULL; |
| |
| list_for_each_safe(start, temp, &usb_info->buf_tbl) { |
| entry = list_entry(start, struct diag_usb_buf_tbl_t, track); |
| if (entry->buf == buf) { |
| DIAG_LOG(DIAG_DEBUG_MUX, "ref_count-- for %pK\n", buf); |
| atomic_dec(&entry->ref_count); |
| /* |
| * Remove reference from the table if it is the |
| * only instance of the buffer |
| */ |
| if (atomic_read(&entry->ref_count) == 0) |
| list_del(&entry->track); |
| break; |
| } |
| } |
| } |
| |
| static struct diag_usb_buf_tbl_t *diag_usb_buf_tbl_get( |
| struct diag_usb_info *usb_info, |
| unsigned char *buf) |
| { |
| struct list_head *start, *temp; |
| struct diag_usb_buf_tbl_t *entry = NULL; |
| |
| list_for_each_safe(start, temp, &usb_info->buf_tbl) { |
| entry = list_entry(start, struct diag_usb_buf_tbl_t, track); |
| if (entry->buf == buf) { |
| DIAG_LOG(DIAG_DEBUG_MUX, "ref_count-- for %pK\n", buf); |
| atomic_dec(&entry->ref_count); |
| return entry; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * This function is called asynchronously when USB is connected and |
| * synchronously when Diag wants to connect to USB explicitly. |
| */ |
| static void usb_connect(struct diag_usb_info *ch) |
| { |
| int err = 0; |
| int num_write = 0; |
| int num_read = 1; /* Only one read buffer for any USB channel */ |
| |
| if (!ch || !atomic_read(&ch->connected)) |
| return; |
| |
| num_write = diag_mempools[ch->mempool].poolsize; |
| err = usb_diag_alloc_req(ch->hdl, num_write, num_read); |
| if (err) { |
| pr_err("diag: Unable to allocate usb requests for %s, write: %d read: %d, err: %d\n", |
| ch->name, num_write, num_read, err); |
| return; |
| } |
| |
| if (ch->ops && ch->ops->open) { |
| if (atomic_read(&ch->diag_state)) { |
| ch->ops->open(ch->ctxt, DIAG_USB_MODE); |
| } else { |
| /* |
| * This case indicates that the USB is connected |
| * but the logging is still happening in MEMORY |
| * DEVICE MODE. Continue the logging without |
| * resetting the buffers. |
| */ |
| } |
| } |
| /* As soon as we open the channel, queue a read */ |
| queue_work(ch->usb_wq, &(ch->read_work)); |
| } |
| |
| static void usb_connect_work_fn(struct work_struct *work) |
| { |
| struct diag_usb_info *ch = container_of(work, struct diag_usb_info, |
| connect_work); |
| usb_connect(ch); |
| } |
| |
| /* |
| * This function is called asynchronously when USB is disconnected |
| * and synchronously when Diag wants to disconnect from USB |
| * explicitly. |
| */ |
| static void usb_disconnect(struct diag_usb_info *ch) |
| { |
| if (!ch) |
| return; |
| |
| if (!atomic_read(&ch->connected) && |
| driver->usb_connected && diag_mask_param()) |
| diag_clear_masks(NULL); |
| |
| if (ch && ch->ops && ch->ops->close) |
| ch->ops->close(ch->ctxt, DIAG_USB_MODE); |
| } |
| |
| static void usb_disconnect_work_fn(struct work_struct *work) |
| { |
| struct diag_usb_info *ch = container_of(work, struct diag_usb_info, |
| disconnect_work); |
| usb_disconnect(ch); |
| } |
| |
| static void usb_read_work_fn(struct work_struct *work) |
| { |
| int err = 0; |
| unsigned long flags; |
| struct diag_request *req = NULL; |
| struct diag_usb_info *ch = container_of(work, struct diag_usb_info, |
| read_work); |
| if (!ch) |
| return; |
| |
| if (!atomic_read(&ch->connected) || !ch->enabled || |
| atomic_read(&ch->read_pending) || !atomic_read(&ch->diag_state)) { |
| pr_debug_ratelimited("diag: Discarding USB read, ch: %s e: %d, c: %d, p: %d, d: %d\n", |
| ch->name, ch->enabled, |
| atomic_read(&ch->connected), |
| atomic_read(&ch->read_pending), |
| atomic_read(&ch->diag_state)); |
| return; |
| } |
| |
| spin_lock_irqsave(&ch->lock, flags); |
| req = ch->read_ptr; |
| if (req) { |
| atomic_set(&ch->read_pending, 1); |
| req->buf = ch->read_buf; |
| req->length = USB_MAX_OUT_BUF; |
| err = usb_diag_read(ch->hdl, req); |
| if (err) { |
| pr_debug("diag: In %s, error in reading from USB %s, err: %d\n", |
| __func__, ch->name, err); |
| atomic_set(&ch->read_pending, 0); |
| queue_work(ch->usb_wq, &(ch->read_work)); |
| } |
| } else { |
| pr_err_ratelimited("diag: In %s invalid read req\n", __func__); |
| } |
| spin_unlock_irqrestore(&ch->lock, flags); |
| } |
| |
| static void usb_read_done_work_fn(struct work_struct *work) |
| { |
| struct diag_request *req = NULL; |
| struct diag_usb_info *ch = container_of(work, struct diag_usb_info, |
| read_done_work); |
| if (!ch) |
| return; |
| |
| /* |
| * USB is disconnected/Disabled before the previous read completed. |
| * Discard the packet and don't do any further processing. |
| */ |
| if (!atomic_read(&ch->connected) || !ch->enabled || |
| !atomic_read(&ch->diag_state)) |
| return; |
| |
| req = ch->read_ptr; |
| ch->read_cnt++; |
| |
| if (ch->ops && ch->ops->read_done && req->status >= 0) |
| ch->ops->read_done(req->buf, req->actual, ch->ctxt); |
| } |
| |
| static void diag_usb_write_done(struct diag_usb_info *ch, |
| struct diag_request *req) |
| { |
| int ctxt = 0; |
| int len = 0; |
| struct diag_usb_buf_tbl_t *entry = NULL; |
| unsigned char *buf = NULL; |
| unsigned long flags; |
| |
| if (!ch || !req) |
| return; |
| |
| ch->write_cnt++; |
| entry = diag_usb_buf_tbl_get(ch, req->context); |
| if (!entry) { |
| pr_err_ratelimited("diag: In %s, unable to find entry %pK in the table\n", |
| __func__, req->context); |
| return; |
| } |
| if (atomic_read(&entry->ref_count) != 0) { |
| DIAG_LOG(DIAG_DEBUG_MUX, "partial write_done ref %d\n", |
| atomic_read(&entry->ref_count)); |
| diag_ws_on_copy_complete(DIAG_WS_MUX); |
| diagmem_free(driver, req, ch->mempool); |
| return; |
| } |
| DIAG_LOG(DIAG_DEBUG_MUX, "full write_done, ctxt: %d\n", |
| ctxt); |
| spin_lock_irqsave(&ch->write_lock, flags); |
| list_del(&entry->track); |
| ctxt = entry->ctxt; |
| buf = entry->buf; |
| len = entry->len; |
| kfree(entry); |
| diag_ws_on_copy_complete(DIAG_WS_MUX); |
| |
| if (ch->ops && ch->ops->write_done) |
| ch->ops->write_done(buf, len, ctxt, DIAG_USB_MODE); |
| buf = NULL; |
| len = 0; |
| ctxt = 0; |
| spin_unlock_irqrestore(&ch->write_lock, flags); |
| diagmem_free(driver, req, ch->mempool); |
| } |
| |
| static void diag_usb_notifier(void *priv, unsigned int event, |
| struct diag_request *d_req) |
| { |
| int id = 0; |
| unsigned long flags; |
| struct diag_usb_info *usb_info = NULL; |
| |
| id = (int)(uintptr_t)priv; |
| if (id < 0 || id >= NUM_DIAG_USB_DEV) |
| return; |
| usb_info = &diag_usb[id]; |
| |
| switch (event) { |
| case USB_DIAG_CONNECT: |
| usb_info->max_size = usb_diag_request_size(usb_info->hdl); |
| atomic_set(&usb_info->connected, 1); |
| pr_info("diag: USB channel %s connected\n", usb_info->name); |
| queue_work(usb_info->usb_wq, |
| &usb_info->connect_work); |
| break; |
| case USB_DIAG_DISCONNECT: |
| atomic_set(&usb_info->connected, 0); |
| pr_info("diag: USB channel %s disconnected\n", usb_info->name); |
| queue_work(usb_info->usb_wq, |
| &usb_info->disconnect_work); |
| break; |
| case USB_DIAG_READ_DONE: |
| spin_lock_irqsave(&usb_info->lock, flags); |
| usb_info->read_ptr = d_req; |
| spin_unlock_irqrestore(&usb_info->lock, flags); |
| atomic_set(&usb_info->read_pending, 0); |
| queue_work(usb_info->usb_wq, |
| &usb_info->read_done_work); |
| break; |
| case USB_DIAG_WRITE_DONE: |
| diag_usb_write_done(usb_info, d_req); |
| break; |
| default: |
| pr_err_ratelimited("diag: Unknown event from USB diag\n"); |
| break; |
| } |
| } |
| |
| int diag_usb_queue_read(int id) |
| { |
| if (id < 0 || id >= NUM_DIAG_USB_DEV) { |
| pr_err_ratelimited("diag: In %s, Incorrect id %d\n", |
| __func__, id); |
| return -EINVAL; |
| } |
| queue_work(diag_usb[id].usb_wq, &(diag_usb[id].read_work)); |
| return 0; |
| } |
| |
| static int diag_usb_write_ext(struct diag_usb_info *usb_info, |
| unsigned char *buf, int len, int ctxt) |
| { |
| int err = 0; |
| int write_len = 0; |
| int bytes_remaining = len; |
| int offset = 0; |
| unsigned long flags; |
| struct diag_request *req = NULL; |
| |
| if (!usb_info || !buf || len <= 0) { |
| pr_err_ratelimited("diag: In %s, usb_info: %pK buf: %pK, len: %d\n", |
| __func__, usb_info, buf, len); |
| return -EINVAL; |
| } |
| |
| spin_lock_irqsave(&usb_info->write_lock, flags); |
| while (bytes_remaining > 0) { |
| req = diagmem_alloc(driver, sizeof(struct diag_request), |
| usb_info->mempool); |
| if (!req) { |
| /* |
| * This should never happen. It either means that we are |
| * trying to write more buffers than the max supported |
| * by this particualar diag USB channel at any given |
| * instance, or the previous write ptrs are stuck in |
| * the USB layer. |
| */ |
| pr_err_ratelimited("diag: In %s, cannot retrieve USB write ptrs for USB channel %s\n", |
| __func__, usb_info->name); |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| return -ENOMEM; |
| } |
| |
| write_len = (bytes_remaining > usb_info->max_size) ? |
| usb_info->max_size : (bytes_remaining); |
| |
| req->buf = buf + offset; |
| req->length = write_len; |
| req->context = (void *)buf; |
| |
| if (!usb_info->hdl || !atomic_read(&usb_info->connected) || |
| !atomic_read(&usb_info->diag_state)) { |
| pr_debug_ratelimited("diag: USB ch %s is not connected\n", |
| usb_info->name); |
| diagmem_free(driver, req, usb_info->mempool); |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| return -ENODEV; |
| } |
| |
| if (diag_usb_buf_tbl_add(usb_info, buf, len, ctxt)) { |
| diagmem_free(driver, req, usb_info->mempool); |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| return -ENOMEM; |
| } |
| |
| diag_ws_on_read(DIAG_WS_MUX, len); |
| err = usb_diag_write(usb_info->hdl, req); |
| diag_ws_on_copy(DIAG_WS_MUX); |
| if (err) { |
| pr_err_ratelimited("diag: In %s, error writing to usb channel %s, err: %d\n", |
| __func__, usb_info->name, err); |
| DIAG_LOG(DIAG_DEBUG_MUX, |
| "ERR! unable to write t usb, err: %d\n", err); |
| diag_ws_on_copy_fail(DIAG_WS_MUX); |
| diag_usb_buf_tbl_remove(usb_info, buf); |
| diagmem_free(driver, req, usb_info->mempool); |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| return err; |
| } |
| offset += write_len; |
| bytes_remaining -= write_len; |
| DIAG_LOG(DIAG_DEBUG_MUX, |
| "bytes_remaining: %d write_len: %d, len: %d\n", |
| bytes_remaining, write_len, len); |
| } |
| DIAG_LOG(DIAG_DEBUG_MUX, "done writing!"); |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| |
| return 0; |
| } |
| |
| int diag_usb_write(int id, unsigned char *buf, int len, int ctxt) |
| { |
| int err = 0; |
| struct diag_request *req = NULL; |
| struct diag_usb_info *usb_info = NULL; |
| unsigned long flags; |
| |
| if (id < 0 || id >= NUM_DIAG_USB_DEV) { |
| pr_err_ratelimited("diag: In %s, Incorrect id %d\n", |
| __func__, id); |
| return -EINVAL; |
| } |
| |
| usb_info = &diag_usb[id]; |
| |
| if (len > usb_info->max_size) { |
| DIAG_LOG(DIAG_DEBUG_MUX, "len: %d, max_size: %d\n", |
| len, usb_info->max_size); |
| return diag_usb_write_ext(usb_info, buf, len, ctxt); |
| } |
| |
| req = diagmem_alloc(driver, sizeof(struct diag_request), |
| usb_info->mempool); |
| if (!req) { |
| /* |
| * This should never happen. It either means that we are |
| * trying to write more buffers than the max supported by |
| * this particualar diag USB channel at any given instance, |
| * or the previous write ptrs are stuck in the USB layer. |
| */ |
| pr_err_ratelimited("diag: In %s, cannot retrieve USB write ptrs for USB channel %s\n", |
| __func__, usb_info->name); |
| return -ENOMEM; |
| } |
| |
| req->buf = buf; |
| req->length = len; |
| req->context = (void *)buf; |
| |
| if (!usb_info->hdl || !atomic_read(&usb_info->connected) || |
| !atomic_read(&usb_info->diag_state)) { |
| pr_debug_ratelimited("diag: USB ch %s is not connected\n", |
| usb_info->name); |
| diagmem_free(driver, req, usb_info->mempool); |
| return -ENODEV; |
| } |
| |
| spin_lock_irqsave(&usb_info->write_lock, flags); |
| if (diag_usb_buf_tbl_add(usb_info, buf, len, ctxt)) { |
| DIAG_LOG(DIAG_DEBUG_MUX, |
| "ERR! unable to add buf %pK to table\n", |
| buf); |
| diagmem_free(driver, req, usb_info->mempool); |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| return -ENOMEM; |
| } |
| |
| diag_ws_on_read(DIAG_WS_MUX, len); |
| err = usb_diag_write(usb_info->hdl, req); |
| diag_ws_on_copy(DIAG_WS_MUX); |
| if (err) { |
| pr_err_ratelimited("diag: In %s, error writing to usb channel %s, err: %d\n", |
| __func__, usb_info->name, err); |
| diag_ws_on_copy_fail(DIAG_WS_MUX); |
| DIAG_LOG(DIAG_DEBUG_MUX, |
| "ERR! unable to write t usb, err: %d\n", err); |
| diag_usb_buf_tbl_remove(usb_info, buf); |
| diagmem_free(driver, req, usb_info->mempool); |
| } |
| spin_unlock_irqrestore(&usb_info->write_lock, flags); |
| |
| return err; |
| } |
| |
| /* |
| * This functions performs USB connect operations wrt Diag synchronously. It |
| * doesn't translate to actual USB connect. This is used when Diag switches |
| * logging to USB mode and wants to mimic USB connection. |
| */ |
| void diag_usb_connect_all(void) |
| { |
| int i = 0; |
| struct diag_usb_info *usb_info = NULL; |
| |
| for (i = 0; i < NUM_DIAG_USB_DEV; i++) { |
| usb_info = &diag_usb[i]; |
| if (!usb_info->enabled) |
| continue; |
| atomic_set(&usb_info->diag_state, 1); |
| usb_connect(usb_info); |
| } |
| } |
| |
| /* |
| * This functions performs USB disconnect operations wrt Diag synchronously. |
| * It doesn't translate to actual USB disconnect. This is used when Diag |
| * switches logging from USB mode and want to mimic USB disconnect. |
| */ |
| void diag_usb_disconnect_all(void) |
| { |
| int i = 0; |
| struct diag_usb_info *usb_info = NULL; |
| |
| for (i = 0; i < NUM_DIAG_USB_DEV; i++) { |
| usb_info = &diag_usb[i]; |
| if (!usb_info->enabled) |
| continue; |
| atomic_set(&usb_info->diag_state, 0); |
| usb_disconnect(usb_info); |
| } |
| } |
| |
| int diag_usb_register(int id, int ctxt, struct diag_mux_ops *ops) |
| { |
| struct diag_usb_info *ch = NULL; |
| unsigned char wq_name[DIAG_USB_NAME_SZ + DIAG_USB_STRING_SZ]; |
| |
| if (id < 0 || id >= NUM_DIAG_USB_DEV) { |
| pr_err("diag: Unable to register with USB, id: %d\n", id); |
| return -EIO; |
| } |
| |
| if (!ops) { |
| pr_err("diag: Invalid operations for USB\n"); |
| return -EIO; |
| } |
| |
| ch = &diag_usb[id]; |
| ch->ops = ops; |
| ch->ctxt = ctxt; |
| spin_lock_init(&ch->lock); |
| spin_lock_init(&ch->write_lock); |
| ch->read_buf = kzalloc(USB_MAX_OUT_BUF, GFP_KERNEL); |
| if (!ch->read_buf) |
| goto err; |
| ch->read_ptr = kzalloc(sizeof(struct diag_request), GFP_KERNEL); |
| if (!ch->read_ptr) |
| goto err; |
| atomic_set(&ch->connected, 0); |
| atomic_set(&ch->read_pending, 0); |
| /* |
| * This function is called when the mux registers with Diag-USB. |
| * The registration happens during boot up and Diag always starts |
| * in USB mode. Set the state to 1. |
| */ |
| atomic_set(&ch->diag_state, 1); |
| INIT_LIST_HEAD(&ch->buf_tbl); |
| diagmem_init(driver, ch->mempool); |
| INIT_WORK(&(ch->read_work), usb_read_work_fn); |
| INIT_WORK(&(ch->read_done_work), usb_read_done_work_fn); |
| INIT_WORK(&(ch->connect_work), usb_connect_work_fn); |
| INIT_WORK(&(ch->disconnect_work), usb_disconnect_work_fn); |
| strlcpy(wq_name, "DIAG_USB_", DIAG_USB_STRING_SZ); |
| strlcat(wq_name, ch->name, sizeof(ch->name)); |
| ch->usb_wq = create_singlethread_workqueue(wq_name); |
| if (!ch->usb_wq) |
| goto err; |
| ch->hdl = usb_diag_open(ch->name, (void *)(uintptr_t)id, |
| diag_usb_notifier); |
| if (IS_ERR(ch->hdl)) { |
| pr_err("diag: Unable to open USB channel %s\n", ch->name); |
| goto err; |
| } |
| ch->enabled = 1; |
| pr_debug("diag: Successfully registered USB %s\n", ch->name); |
| return 0; |
| |
| err: |
| if (ch->usb_wq) |
| destroy_workqueue(ch->usb_wq); |
| kfree(ch->read_ptr); |
| kfree(ch->read_buf); |
| return -ENOMEM; |
| } |
| |
| void diag_usb_exit(int id) |
| { |
| struct diag_usb_info *ch = NULL; |
| |
| if (id < 0 || id >= NUM_DIAG_USB_DEV) { |
| pr_err("diag: In %s, incorrect id %d\n", __func__, id); |
| return; |
| } |
| |
| ch = &diag_usb[id]; |
| ch->ops = NULL; |
| atomic_set(&ch->connected, 0); |
| atomic_set(&ch->read_pending, 0); |
| atomic_set(&ch->diag_state, 0); |
| ch->enabled = 0; |
| ch->ctxt = 0; |
| ch->read_cnt = 0; |
| ch->write_cnt = 0; |
| diagmem_exit(driver, ch->mempool); |
| ch->mempool = 0; |
| if (ch->hdl) { |
| usb_diag_close(ch->hdl); |
| ch->hdl = NULL; |
| } |
| if (ch->usb_wq) |
| destroy_workqueue(ch->usb_wq); |
| kfree(ch->read_ptr); |
| ch->read_ptr = NULL; |
| kfree(ch->read_buf); |
| ch->read_buf = NULL; |
| } |
| |