| /* Copyright (c) 2008-2012, Code Aurora Forum. 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/cdev.h> |
| #include <linux/fs.h> |
| #include <linux/device.h> |
| #include <linux/uaccess.h> |
| #include <linux/diagchar.h> |
| #include <linux/sched.h> |
| #ifdef CONFIG_DIAG_OVER_USB |
| #include <mach/usbdiag.h> |
| #endif |
| #include <asm/current.h> |
| #include "diagchar_hdlc.h" |
| #include "diagmem.h" |
| #include "diagchar.h" |
| #include "diagfwd.h" |
| #include "diagfwd_cntl.h" |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| #include "diagfwd_sdio.h" |
| #endif |
| #ifdef CONFIG_DIAG_HSIC_PIPE |
| #include "diagfwd_hsic.h" |
| #endif |
| #include <linux/timer.h> |
| |
| MODULE_DESCRIPTION("Diag Char Driver"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_VERSION("1.0"); |
| |
| #define INIT 1 |
| #define EXIT -1 |
| struct diagchar_dev *driver; |
| struct diagchar_priv { |
| int pid; |
| }; |
| /* The following variables can be specified by module options */ |
| /* for copy buffer */ |
| static unsigned int itemsize = 2048; /*Size of item in the mempool */ |
| static unsigned int poolsize = 10; /*Number of items in the mempool */ |
| /* for hdlc buffer */ |
| static unsigned int itemsize_hdlc = 8192; /*Size of item in the mempool */ |
| static unsigned int poolsize_hdlc = 8; /*Number of items in the mempool */ |
| /* for write structure buffer */ |
| static unsigned int itemsize_write_struct = 20; /*Size of item in the mempool */ |
| static unsigned int poolsize_write_struct = 8; /* Num of items in the mempool */ |
| /* This is the max number of user-space clients supported at initialization*/ |
| static unsigned int max_clients = 15; |
| static unsigned int threshold_client_limit = 30; |
| /* This is the maximum number of pkt registrations supported at initialization*/ |
| unsigned int diag_max_registration = 600; |
| unsigned int diag_threshold_registration = 750; |
| |
| /* Timer variables */ |
| static struct timer_list drain_timer; |
| static int timer_in_progress; |
| void *buf_hdlc; |
| module_param(itemsize, uint, 0); |
| module_param(poolsize, uint, 0); |
| module_param(max_clients, uint, 0); |
| |
| /* delayed_rsp_id 0 represents no delay in the response. Any other number |
| means that the diag packet has a delayed response. */ |
| static uint16_t delayed_rsp_id = 1; |
| #define DIAGPKT_MAX_DELAYED_RSP 0xFFFF |
| /* This macro gets the next delayed respose id. Once it reaches |
| DIAGPKT_MAX_DELAYED_RSP, it stays at DIAGPKT_MAX_DELAYED_RSP */ |
| |
| #define DIAGPKT_NEXT_DELAYED_RSP_ID(x) \ |
| ((x < DIAGPKT_MAX_DELAYED_RSP) ? x++ : DIAGPKT_MAX_DELAYED_RSP) |
| |
| #define COPY_USER_SPACE_OR_EXIT(buf, data, length) \ |
| do { \ |
| if ((count < ret+length) || (copy_to_user(buf, \ |
| (void *)&data, length))) { \ |
| ret = -EFAULT; \ |
| goto exit; \ |
| } \ |
| ret += length; \ |
| } while (0) |
| |
| static void drain_timer_func(unsigned long data) |
| { |
| queue_work(driver->diag_wq , &(driver->diag_drain_work)); |
| } |
| |
| void diag_drain_work_fn(struct work_struct *work) |
| { |
| int err = 0; |
| timer_in_progress = 0; |
| |
| mutex_lock(&driver->diagchar_mutex); |
| if (buf_hdlc) { |
| err = diag_device_write(buf_hdlc, APPS_DATA, NULL); |
| if (err) { |
| /*Free the buffer right away if write failed */ |
| diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); |
| diagmem_free(driver, (unsigned char *)driver-> |
| write_ptr_svc, POOL_TYPE_WRITE_STRUCT); |
| } |
| buf_hdlc = NULL; |
| #ifdef DIAG_DEBUG |
| pr_debug("diag: Number of bytes written " |
| "from timer is %d ", driver->used); |
| #endif |
| driver->used = 0; |
| } |
| mutex_unlock(&driver->diagchar_mutex); |
| } |
| |
| void diag_read_smd_work_fn(struct work_struct *work) |
| { |
| __diag_smd_send_req(); |
| } |
| |
| void diag_read_smd_qdsp_work_fn(struct work_struct *work) |
| { |
| __diag_smd_qdsp_send_req(); |
| } |
| |
| void diag_read_smd_wcnss_work_fn(struct work_struct *work) |
| { |
| __diag_smd_wcnss_send_req(); |
| } |
| |
| void diag_add_client(int i, struct file *file) |
| { |
| struct diagchar_priv *diagpriv_data; |
| |
| driver->client_map[i].pid = current->tgid; |
| diagpriv_data = kmalloc(sizeof(struct diagchar_priv), |
| GFP_KERNEL); |
| if (diagpriv_data) |
| diagpriv_data->pid = current->tgid; |
| file->private_data = diagpriv_data; |
| strlcpy(driver->client_map[i].name, current->comm, 20); |
| driver->client_map[i].name[19] = '\0'; |
| } |
| |
| static int diagchar_open(struct inode *inode, struct file *file) |
| { |
| int i = 0; |
| void *temp; |
| |
| if (driver) { |
| mutex_lock(&driver->diagchar_mutex); |
| |
| for (i = 0; i < driver->num_clients; i++) |
| if (driver->client_map[i].pid == 0) |
| break; |
| |
| if (i < driver->num_clients) { |
| diag_add_client(i, file); |
| } else { |
| if (i < threshold_client_limit) { |
| driver->num_clients++; |
| temp = krealloc(driver->client_map |
| , (driver->num_clients) * sizeof(struct |
| diag_client_map), GFP_KERNEL); |
| if (!temp) |
| goto fail; |
| else |
| driver->client_map = temp; |
| temp = krealloc(driver->data_ready |
| , (driver->num_clients) * sizeof(int), |
| GFP_KERNEL); |
| if (!temp) |
| goto fail; |
| else |
| driver->data_ready = temp; |
| diag_add_client(i, file); |
| } else { |
| mutex_unlock(&driver->diagchar_mutex); |
| pr_alert("Max client limit for DIAG reached\n"); |
| pr_info("Cannot open handle %s" |
| " %d", current->comm, current->tgid); |
| for (i = 0; i < driver->num_clients; i++) |
| pr_debug("%d) %s PID=%d", i, driver-> |
| client_map[i].name, |
| driver->client_map[i].pid); |
| return -ENOMEM; |
| } |
| } |
| driver->data_ready[i] |= MSG_MASKS_TYPE; |
| driver->data_ready[i] |= EVENT_MASKS_TYPE; |
| driver->data_ready[i] |= LOG_MASKS_TYPE; |
| |
| if (driver->ref_count == 0) |
| diagmem_init(driver); |
| driver->ref_count++; |
| mutex_unlock(&driver->diagchar_mutex); |
| return 0; |
| } |
| return -ENOMEM; |
| |
| fail: |
| mutex_unlock(&driver->diagchar_mutex); |
| driver->num_clients--; |
| pr_alert("diag: Insufficient memory for new client"); |
| return -ENOMEM; |
| } |
| |
| static int diagchar_close(struct inode *inode, struct file *file) |
| { |
| int i = 0; |
| struct diagchar_priv *diagpriv_data = file->private_data; |
| |
| if (!(file->private_data)) { |
| pr_alert("diag: Invalid file pointer"); |
| return -ENOMEM; |
| } |
| |
| #ifdef CONFIG_DIAG_OVER_USB |
| /* If the SD logging process exits, change logging to USB mode */ |
| if (driver->logging_process_id == current->tgid) { |
| driver->logging_mode = USB_MODE; |
| diagfwd_connect(); |
| } |
| #endif /* DIAG over USB */ |
| /* Delete the pkt response table entry for the exiting process */ |
| for (i = 0; i < diag_max_registration; i++) |
| if (driver->table[i].process_id == current->tgid) |
| driver->table[i].process_id = 0; |
| |
| if (driver) { |
| mutex_lock(&driver->diagchar_mutex); |
| driver->ref_count--; |
| /* On Client exit, try to destroy all 3 pools */ |
| diagmem_exit(driver, POOL_TYPE_COPY); |
| diagmem_exit(driver, POOL_TYPE_HDLC); |
| diagmem_exit(driver, POOL_TYPE_WRITE_STRUCT); |
| for (i = 0; i < driver->num_clients; i++) { |
| if (NULL != diagpriv_data && diagpriv_data->pid == |
| driver->client_map[i].pid) { |
| driver->client_map[i].pid = 0; |
| kfree(diagpriv_data); |
| diagpriv_data = NULL; |
| break; |
| } |
| } |
| mutex_unlock(&driver->diagchar_mutex); |
| return 0; |
| } |
| return -ENOMEM; |
| } |
| |
| int diag_find_polling_reg(int i) |
| { |
| uint16_t subsys_id, cmd_code_lo, cmd_code_hi; |
| |
| subsys_id = driver->table[i].subsys_id; |
| cmd_code_lo = driver->table[i].cmd_code_lo; |
| cmd_code_hi = driver->table[i].cmd_code_hi; |
| if (driver->table[i].cmd_code == 0x0C) |
| return 1; |
| else if (driver->table[i].cmd_code == 0xFF) { |
| if (subsys_id == 0x04 && cmd_code_hi == 0x0E && |
| cmd_code_lo == 0x0E) |
| return 1; |
| else if (subsys_id == 0x08 && cmd_code_hi == 0x02 && |
| cmd_code_lo == 0x02) |
| return 1; |
| else if (subsys_id == 0x32 && cmd_code_hi == 0x03 && |
| cmd_code_lo == 0x03) |
| return 1; |
| } |
| return 0; |
| } |
| |
| void diag_clear_reg(int proc_num) |
| { |
| int i; |
| |
| mutex_lock(&driver->diagchar_mutex); |
| /* reset polling flag */ |
| driver->polling_reg_flag = 0; |
| for (i = 0; i < diag_max_registration; i++) { |
| if (driver->table[i].client_id == proc_num) { |
| driver->table[i].process_id = 0; |
| } |
| } |
| /* re-scan the registration table */ |
| for (i = 0; i < diag_max_registration; i++) { |
| if (diag_find_polling_reg(i) == 1) { |
| driver->polling_reg_flag = 1; |
| break; |
| } |
| } |
| mutex_unlock(&driver->diagchar_mutex); |
| } |
| |
| void diag_add_reg(int j, struct bindpkt_params *params, |
| int *success, int *count_entries) |
| { |
| *success = 1; |
| driver->table[j].cmd_code = params->cmd_code; |
| driver->table[j].subsys_id = params->subsys_id; |
| driver->table[j].cmd_code_lo = params->cmd_code_lo; |
| driver->table[j].cmd_code_hi = params->cmd_code_hi; |
| |
| /* check if incoming reg is polling & polling is yet not registered */ |
| if (driver->polling_reg_flag == 0) |
| if (diag_find_polling_reg(j) == 1) |
| driver->polling_reg_flag = 1; |
| if (params->proc_id == APPS_PROC) { |
| driver->table[j].process_id = current->tgid; |
| driver->table[j].client_id = APPS_PROC; |
| } else { |
| driver->table[j].process_id = NON_APPS_PROC; |
| driver->table[j].client_id = params->client_id; |
| } |
| (*count_entries)++; |
| } |
| |
| long diagchar_ioctl(struct file *filp, |
| unsigned int iocmd, unsigned long ioarg) |
| { |
| int i, j, count_entries = 0, temp; |
| int success = -1; |
| void *temp_buf; |
| |
| if (iocmd == DIAG_IOCTL_COMMAND_REG) { |
| struct bindpkt_params_per_process *pkt_params = |
| (struct bindpkt_params_per_process *) ioarg; |
| mutex_lock(&driver->diagchar_mutex); |
| for (i = 0; i < diag_max_registration; i++) { |
| if (driver->table[i].process_id == 0) { |
| diag_add_reg(i, pkt_params->params, |
| &success, &count_entries); |
| if (pkt_params->count > count_entries) { |
| pkt_params->params++; |
| } else { |
| mutex_unlock(&driver->diagchar_mutex); |
| return success; |
| } |
| } |
| } |
| if (i < diag_threshold_registration) { |
| /* Increase table size by amount required */ |
| diag_max_registration += pkt_params->count - |
| count_entries; |
| /* Make sure size doesnt go beyond threshold */ |
| if (diag_max_registration > diag_threshold_registration) |
| diag_max_registration = |
| diag_threshold_registration; |
| temp_buf = krealloc(driver->table, |
| diag_max_registration*sizeof(struct |
| diag_master_table), GFP_KERNEL); |
| if (!temp_buf) { |
| diag_max_registration -= pkt_params->count - |
| count_entries; |
| pr_alert("diag: Insufficient memory for reg."); |
| mutex_unlock(&driver->diagchar_mutex); |
| return 0; |
| } else { |
| driver->table = temp_buf; |
| } |
| for (j = i; j < diag_max_registration; j++) { |
| diag_add_reg(j, pkt_params->params, |
| &success, &count_entries); |
| if (pkt_params->count > count_entries) { |
| pkt_params->params++; |
| } else { |
| mutex_unlock(&driver->diagchar_mutex); |
| return success; |
| } |
| } |
| } else { |
| mutex_unlock(&driver->diagchar_mutex); |
| pr_err("Max size reached, Pkt Registration failed for" |
| " Process %d", current->tgid); |
| } |
| success = 0; |
| } else if (iocmd == DIAG_IOCTL_GET_DELAYED_RSP_ID) { |
| struct diagpkt_delay_params *delay_params = |
| (struct diagpkt_delay_params *) ioarg; |
| |
| if ((delay_params->rsp_ptr) && |
| (delay_params->size == sizeof(delayed_rsp_id)) && |
| (delay_params->num_bytes_ptr)) { |
| *((uint16_t *)delay_params->rsp_ptr) = |
| DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id); |
| *(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id); |
| success = 0; |
| } |
| } else if (iocmd == DIAG_IOCTL_LSM_DEINIT) { |
| for (i = 0; i < driver->num_clients; i++) |
| if (driver->client_map[i].pid == current->tgid) |
| break; |
| if (i == -1) |
| return -EINVAL; |
| driver->data_ready[i] |= DEINIT_TYPE; |
| wake_up_interruptible(&driver->wait_q); |
| success = 1; |
| } else if (iocmd == DIAG_IOCTL_SWITCH_LOGGING) { |
| mutex_lock(&driver->diagchar_mutex); |
| temp = driver->logging_mode; |
| driver->logging_mode = (int)ioarg; |
| if (driver->logging_mode == MEMORY_DEVICE_MODE) |
| driver->mask_check = 1; |
| if (driver->logging_mode == UART_MODE) { |
| driver->mask_check = 0; |
| driver->logging_mode = MEMORY_DEVICE_MODE; |
| } |
| driver->logging_process_id = current->tgid; |
| mutex_unlock(&driver->diagchar_mutex); |
| if (temp == MEMORY_DEVICE_MODE && driver->logging_mode |
| == NO_LOGGING_MODE) { |
| driver->in_busy_1 = 1; |
| driver->in_busy_2 = 1; |
| driver->in_busy_qdsp_1 = 1; |
| driver->in_busy_qdsp_2 = 1; |
| driver->in_busy_wcnss = 1; |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| driver->in_busy_sdio = 1; |
| #endif |
| } else if (temp == NO_LOGGING_MODE && driver->logging_mode |
| == MEMORY_DEVICE_MODE) { |
| driver->in_busy_1 = 0; |
| driver->in_busy_2 = 0; |
| driver->in_busy_qdsp_1 = 0; |
| driver->in_busy_qdsp_2 = 0; |
| driver->in_busy_wcnss = 0; |
| /* Poll SMD channels to check for data*/ |
| if (driver->ch) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_work)); |
| if (driver->chqdsp) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_qdsp_work)); |
| if (driver->ch_wcnss) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_wcnss_work)); |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| driver->in_busy_sdio = 0; |
| /* Poll SDIO channel to check for data */ |
| if (driver->sdio_ch) |
| queue_work(driver->diag_sdio_wq, |
| &(driver->diag_read_sdio_work)); |
| #endif |
| } |
| #ifdef CONFIG_DIAG_OVER_USB |
| else if (temp == USB_MODE && driver->logging_mode |
| == NO_LOGGING_MODE) |
| diagfwd_disconnect(); |
| else if (temp == NO_LOGGING_MODE && driver->logging_mode |
| == USB_MODE) |
| diagfwd_connect(); |
| else if (temp == USB_MODE && driver->logging_mode |
| == MEMORY_DEVICE_MODE) { |
| diagfwd_disconnect(); |
| driver->in_busy_1 = 0; |
| driver->in_busy_2 = 0; |
| driver->in_busy_qdsp_2 = 0; |
| driver->in_busy_qdsp_2 = 0; |
| driver->in_busy_wcnss = 0; |
| /* Poll SMD channels to check for data*/ |
| if (driver->ch) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_work)); |
| if (driver->chqdsp) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_qdsp_work)); |
| if (driver->ch_wcnss) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_wcnss_work)); |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| driver->in_busy_sdio = 0; |
| /* Poll SDIO channel to check for data */ |
| if (driver->sdio_ch) |
| queue_work(driver->diag_sdio_wq, |
| &(driver->diag_read_sdio_work)); |
| #endif |
| } else if (temp == MEMORY_DEVICE_MODE && |
| driver->logging_mode == USB_MODE) |
| diagfwd_connect(); |
| #endif /* DIAG over USB */ |
| success = 1; |
| } |
| |
| return success; |
| } |
| |
| static int diagchar_read(struct file *file, char __user *buf, size_t count, |
| loff_t *ppos) |
| { |
| int index = -1, i = 0, ret = 0; |
| int num_data = 0, data_type; |
| for (i = 0; i < driver->num_clients; i++) |
| if (driver->client_map[i].pid == current->tgid) |
| index = i; |
| |
| if (index == -1) { |
| pr_err("diag: Client PID not found in table"); |
| return -EINVAL; |
| } |
| |
| wait_event_interruptible(driver->wait_q, |
| driver->data_ready[index]); |
| mutex_lock(&driver->diagchar_mutex); |
| |
| if ((driver->data_ready[index] & USER_SPACE_LOG_TYPE) && (driver-> |
| logging_mode == MEMORY_DEVICE_MODE)) { |
| /*Copy the type of data being passed*/ |
| data_type = driver->data_ready[index] & USER_SPACE_LOG_TYPE; |
| COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); |
| /* place holder for number of data field */ |
| ret += 4; |
| |
| for (i = 0; i < driver->poolsize_write_struct; i++) { |
| if (driver->buf_tbl[i].length > 0) { |
| #ifdef DIAG_DEBUG |
| pr_debug("diag: WRITING the buf address " |
| "and length is %x , %d\n", (unsigned int) |
| (driver->buf_tbl[i].buf), |
| driver->buf_tbl[i].length); |
| #endif |
| num_data++; |
| /* Copy the length of data being passed */ |
| if (copy_to_user(buf+ret, (void *)&(driver-> |
| buf_tbl[i].length), 4)) { |
| num_data--; |
| goto drop; |
| } |
| ret += 4; |
| |
| /* Copy the actual data being passed */ |
| if (copy_to_user(buf+ret, (void *)driver-> |
| buf_tbl[i].buf, driver->buf_tbl[i].length)) { |
| ret -= 4; |
| num_data--; |
| goto drop; |
| } |
| ret += driver->buf_tbl[i].length; |
| drop: |
| #ifdef DIAG_DEBUG |
| pr_debug("diag: DEQUEUE buf address and" |
| " length is %x,%d\n", (unsigned int) |
| (driver->buf_tbl[i].buf), driver-> |
| buf_tbl[i].length); |
| #endif |
| diagmem_free(driver, (unsigned char *) |
| (driver->buf_tbl[i].buf), POOL_TYPE_HDLC); |
| driver->buf_tbl[i].length = 0; |
| driver->buf_tbl[i].buf = 0; |
| } |
| } |
| |
| /* copy modem data */ |
| if (driver->in_busy_1 == 1) { |
| num_data++; |
| /*Copy the length of data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| (driver->write_ptr_1->length), 4); |
| /*Copy the actual data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| *(driver->buf_in_1), |
| driver->write_ptr_1->length); |
| driver->in_busy_1 = 0; |
| } |
| if (driver->in_busy_2 == 1) { |
| num_data++; |
| /*Copy the length of data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| (driver->write_ptr_2->length), 4); |
| /*Copy the actual data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| *(driver->buf_in_2), |
| driver->write_ptr_2->length); |
| driver->in_busy_2 = 0; |
| } |
| /* copy lpass data */ |
| if (driver->in_busy_qdsp_1 == 1) { |
| num_data++; |
| /*Copy the length of data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| (driver->write_ptr_qdsp_1->length), 4); |
| /*Copy the actual data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> |
| buf_in_qdsp_1), |
| driver->write_ptr_qdsp_1->length); |
| driver->in_busy_qdsp_1 = 0; |
| } |
| if (driver->in_busy_qdsp_2 == 1) { |
| num_data++; |
| /*Copy the length of data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| (driver->write_ptr_qdsp_2->length), 4); |
| /*Copy the actual data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> |
| buf_in_qdsp_2), driver-> |
| write_ptr_qdsp_2->length); |
| driver->in_busy_qdsp_2 = 0; |
| } |
| /* copy wncss data */ |
| if (driver->in_busy_wcnss == 1) { |
| num_data++; |
| /*Copy the length of data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| (driver->write_ptr_wcnss->length), 4); |
| /*Copy the actual data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> |
| buf_in_wcnss), |
| driver->write_ptr_wcnss->length); |
| driver->in_busy_wcnss = 0; |
| } |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| /* copy 9K data over SDIO */ |
| if (driver->in_busy_sdio == 1) { |
| num_data++; |
| /*Copy the length of data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| (driver->write_ptr_mdm->length), 4); |
| /*Copy the actual data being passed*/ |
| COPY_USER_SPACE_OR_EXIT(buf+ret, |
| *(driver->buf_in_sdio), |
| driver->write_ptr_mdm->length); |
| driver->in_busy_sdio = 0; |
| } |
| #endif |
| /* copy number of data fields */ |
| COPY_USER_SPACE_OR_EXIT(buf+4, num_data, 4); |
| ret -= 4; |
| driver->data_ready[index] ^= USER_SPACE_LOG_TYPE; |
| if (driver->ch) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_work)); |
| if (driver->chqdsp) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_qdsp_work)); |
| if (driver->ch_wcnss) |
| queue_work(driver->diag_wq, |
| &(driver->diag_read_smd_wcnss_work)); |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| if (driver->sdio_ch) |
| queue_work(driver->diag_sdio_wq, |
| &(driver->diag_read_sdio_work)); |
| #endif |
| APPEND_DEBUG('n'); |
| goto exit; |
| } else if (driver->data_ready[index] & USER_SPACE_LOG_TYPE) { |
| /* In case, the thread wakes up and the logging mode is |
| not memory device any more, the condition needs to be cleared */ |
| driver->data_ready[index] ^= USER_SPACE_LOG_TYPE; |
| } |
| |
| if (driver->data_ready[index] & DEINIT_TYPE) { |
| /*Copy the type of data being passed*/ |
| data_type = driver->data_ready[index] & DEINIT_TYPE; |
| COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); |
| driver->data_ready[index] ^= DEINIT_TYPE; |
| goto exit; |
| } |
| |
| if (driver->data_ready[index] & MSG_MASKS_TYPE) { |
| /*Copy the type of data being passed*/ |
| data_type = driver->data_ready[index] & MSG_MASKS_TYPE; |
| COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); |
| COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->msg_masks), |
| MSG_MASK_SIZE); |
| driver->data_ready[index] ^= MSG_MASKS_TYPE; |
| goto exit; |
| } |
| |
| if (driver->data_ready[index] & EVENT_MASKS_TYPE) { |
| /*Copy the type of data being passed*/ |
| data_type = driver->data_ready[index] & EVENT_MASKS_TYPE; |
| COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); |
| COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->event_masks), |
| EVENT_MASK_SIZE); |
| driver->data_ready[index] ^= EVENT_MASKS_TYPE; |
| goto exit; |
| } |
| |
| if (driver->data_ready[index] & LOG_MASKS_TYPE) { |
| /*Copy the type of data being passed*/ |
| data_type = driver->data_ready[index] & LOG_MASKS_TYPE; |
| COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); |
| COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->log_masks), |
| LOG_MASK_SIZE); |
| driver->data_ready[index] ^= LOG_MASKS_TYPE; |
| goto exit; |
| } |
| |
| if (driver->data_ready[index] & PKT_TYPE) { |
| /*Copy the type of data being passed*/ |
| data_type = driver->data_ready[index] & PKT_TYPE; |
| COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); |
| COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->pkt_buf), |
| driver->pkt_length); |
| driver->data_ready[index] ^= PKT_TYPE; |
| goto exit; |
| } |
| |
| exit: |
| mutex_unlock(&driver->diagchar_mutex); |
| return ret; |
| } |
| |
| static int diagchar_write(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| int err, ret = 0, pkt_type; |
| #ifdef DIAG_DEBUG |
| int length = 0, i; |
| #endif |
| struct diag_send_desc_type send = { NULL, NULL, DIAG_STATE_START, 0 }; |
| struct diag_hdlc_dest_type enc = { NULL, NULL, 0 }; |
| void *buf_copy = NULL; |
| int payload_size; |
| #ifdef CONFIG_DIAG_OVER_USB |
| if (((driver->logging_mode == USB_MODE) && (!driver->usb_connected)) || |
| (driver->logging_mode == NO_LOGGING_MODE)) { |
| /*Drop the diag payload */ |
| return -EIO; |
| } |
| #endif /* DIAG over USB */ |
| /* Get the packet type F3/log/event/Pkt response */ |
| err = copy_from_user((&pkt_type), buf, 4); |
| /* First 4 bytes indicate the type of payload - ignore these */ |
| payload_size = count - 4; |
| |
| if (pkt_type == USER_SPACE_LOG_TYPE) { |
| err = copy_from_user(driver->user_space_data, buf + 4, |
| payload_size); |
| /* Check masks for On-Device logging */ |
| if (driver->mask_check) { |
| if (!mask_request_validate(driver->user_space_data)) { |
| pr_alert("diag: mask request Invalid\n"); |
| return -EFAULT; |
| } |
| } |
| buf = buf + 4; |
| #ifdef DIAG_DEBUG |
| pr_debug("diag: user space data %d\n", payload_size); |
| for (i = 0; i < payload_size; i++) |
| pr_debug("\t %x", *((driver->user_space_data)+i)); |
| #endif |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| /* send masks to 9k too */ |
| if (driver->sdio_ch) { |
| wait_event_interruptible(driver->wait_q, |
| (sdio_write_avail(driver->sdio_ch) >= |
| payload_size)); |
| if (driver->sdio_ch && (payload_size > 0)) { |
| sdio_write(driver->sdio_ch, (void *) |
| (driver->user_space_data), payload_size); |
| } |
| } |
| #endif |
| /* send masks to modem now */ |
| diag_process_hdlc((void *)(driver->user_space_data), |
| payload_size); |
| return 0; |
| } |
| |
| buf_copy = diagmem_alloc(driver, payload_size, POOL_TYPE_COPY); |
| if (!buf_copy) { |
| driver->dropped_count++; |
| return -ENOMEM; |
| } |
| |
| err = copy_from_user(buf_copy, buf + 4, payload_size); |
| if (err) { |
| printk(KERN_INFO "diagchar : copy_from_user failed\n"); |
| ret = -EFAULT; |
| goto fail_free_copy; |
| } |
| #ifdef DIAG_DEBUG |
| printk(KERN_DEBUG "data is -->\n"); |
| for (i = 0; i < payload_size; i++) |
| printk(KERN_DEBUG "\t %x \t", *(((unsigned char *)buf_copy)+i)); |
| #endif |
| send.state = DIAG_STATE_START; |
| send.pkt = buf_copy; |
| send.last = (void *)(buf_copy + payload_size - 1); |
| send.terminate = 1; |
| #ifdef DIAG_DEBUG |
| pr_debug("diag: Already used bytes in buffer %d, and" |
| " incoming payload size is %d\n", driver->used, payload_size); |
| printk(KERN_DEBUG "hdlc encoded data is -->\n"); |
| for (i = 0; i < payload_size + 8; i++) { |
| printk(KERN_DEBUG "\t %x \t", *(((unsigned char *)buf_hdlc)+i)); |
| if (*(((unsigned char *)buf_hdlc)+i) != 0x7e) |
| length++; |
| } |
| #endif |
| mutex_lock(&driver->diagchar_mutex); |
| if (!buf_hdlc) |
| buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE, |
| POOL_TYPE_HDLC); |
| if (!buf_hdlc) { |
| ret = -ENOMEM; |
| goto fail_free_hdlc; |
| } |
| if (HDLC_OUT_BUF_SIZE - driver->used <= (2*payload_size) + 3) { |
| err = diag_device_write(buf_hdlc, APPS_DATA, NULL); |
| if (err) { |
| /*Free the buffer right away if write failed */ |
| diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); |
| diagmem_free(driver, (unsigned char *)driver-> |
| write_ptr_svc, POOL_TYPE_WRITE_STRUCT); |
| ret = -EIO; |
| goto fail_free_hdlc; |
| } |
| buf_hdlc = NULL; |
| driver->used = 0; |
| buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE, |
| POOL_TYPE_HDLC); |
| if (!buf_hdlc) { |
| ret = -ENOMEM; |
| goto fail_free_hdlc; |
| } |
| } |
| |
| enc.dest = buf_hdlc + driver->used; |
| enc.dest_last = (void *)(buf_hdlc + driver->used + 2*payload_size + 3); |
| diag_hdlc_encode(&send, &enc); |
| |
| /* This is to check if after HDLC encoding, we are still within the |
| limits of aggregation buffer. If not, we write out the current buffer |
| and start aggregation in a newly allocated buffer */ |
| if ((unsigned int) enc.dest >= |
| (unsigned int)(buf_hdlc + HDLC_OUT_BUF_SIZE)) { |
| err = diag_device_write(buf_hdlc, APPS_DATA, NULL); |
| if (err) { |
| /*Free the buffer right away if write failed */ |
| diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); |
| diagmem_free(driver, (unsigned char *)driver-> |
| write_ptr_svc, POOL_TYPE_WRITE_STRUCT); |
| ret = -EIO; |
| goto fail_free_hdlc; |
| } |
| buf_hdlc = NULL; |
| driver->used = 0; |
| buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE, |
| POOL_TYPE_HDLC); |
| if (!buf_hdlc) { |
| ret = -ENOMEM; |
| goto fail_free_hdlc; |
| } |
| enc.dest = buf_hdlc + driver->used; |
| enc.dest_last = (void *)(buf_hdlc + driver->used + |
| (2*payload_size) + 3); |
| diag_hdlc_encode(&send, &enc); |
| } |
| |
| driver->used = (uint32_t) enc.dest - (uint32_t) buf_hdlc; |
| if (pkt_type == DATA_TYPE_RESPONSE) { |
| err = diag_device_write(buf_hdlc, APPS_DATA, NULL); |
| if (err) { |
| /*Free the buffer right away if write failed */ |
| diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); |
| diagmem_free(driver, (unsigned char *)driver-> |
| write_ptr_svc, POOL_TYPE_WRITE_STRUCT); |
| ret = -EIO; |
| goto fail_free_hdlc; |
| } |
| buf_hdlc = NULL; |
| driver->used = 0; |
| } |
| |
| mutex_unlock(&driver->diagchar_mutex); |
| diagmem_free(driver, buf_copy, POOL_TYPE_COPY); |
| if (!timer_in_progress) { |
| timer_in_progress = 1; |
| ret = mod_timer(&drain_timer, jiffies + msecs_to_jiffies(500)); |
| } |
| return 0; |
| |
| fail_free_hdlc: |
| buf_hdlc = NULL; |
| driver->used = 0; |
| diagmem_free(driver, buf_copy, POOL_TYPE_COPY); |
| mutex_unlock(&driver->diagchar_mutex); |
| return ret; |
| |
| fail_free_copy: |
| diagmem_free(driver, buf_copy, POOL_TYPE_COPY); |
| return ret; |
| } |
| |
| int mask_request_validate(unsigned char mask_buf[]) |
| { |
| uint8_t packet_id; |
| uint8_t subsys_id; |
| uint16_t ss_cmd; |
| |
| packet_id = mask_buf[0]; |
| |
| if (packet_id == 0x4B) { |
| subsys_id = mask_buf[1]; |
| ss_cmd = *(uint16_t *)(mask_buf + 2); |
| /* Packets with SSID which are allowed */ |
| switch (subsys_id) { |
| case 0x04: /* DIAG_SUBSYS_WCDMA */ |
| if ((ss_cmd == 0) || (ss_cmd == 0xF)) |
| return 1; |
| break; |
| case 0x08: /* DIAG_SUBSYS_GSM */ |
| if ((ss_cmd == 0) || (ss_cmd == 0x1)) |
| return 1; |
| break; |
| case 0x09: /* DIAG_SUBSYS_UMTS */ |
| case 0x0F: /* DIAG_SUBSYS_CM */ |
| if (ss_cmd == 0) |
| return 1; |
| break; |
| case 0x0C: /* DIAG_SUBSYS_OS */ |
| if ((ss_cmd == 2) || (ss_cmd == 0x100)) |
| return 1; /* MPU and APU */ |
| break; |
| case 0x12: /* DIAG_SUBSYS_DIAG_SERV */ |
| if ((ss_cmd == 0) || (ss_cmd == 0x6) || (ss_cmd == 0x7)) |
| return 1; |
| break; |
| case 0x13: /* DIAG_SUBSYS_FS */ |
| if ((ss_cmd == 0) || (ss_cmd == 0x1)) |
| return 1; |
| break; |
| default: |
| return 0; |
| break; |
| } |
| } else { |
| switch (packet_id) { |
| case 0x00: /* Version Number */ |
| case 0x0C: /* CDMA status packet */ |
| case 0x1C: /* Diag Version */ |
| case 0x1D: /* Time Stamp */ |
| case 0x60: /* Event Report Control */ |
| case 0x63: /* Status snapshot */ |
| case 0x73: /* Logging Configuration */ |
| case 0x7C: /* Extended build ID */ |
| case 0x7D: /* Extended Message configuration */ |
| case 0x81: /* Event get mask */ |
| case 0x82: /* Set the event mask */ |
| return 1; |
| break; |
| default: |
| return 0; |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static const struct file_operations diagcharfops = { |
| .owner = THIS_MODULE, |
| .read = diagchar_read, |
| .write = diagchar_write, |
| .unlocked_ioctl = diagchar_ioctl, |
| .open = diagchar_open, |
| .release = diagchar_close |
| }; |
| |
| static int diagchar_setup_cdev(dev_t devno) |
| { |
| |
| int err; |
| |
| cdev_init(driver->cdev, &diagcharfops); |
| |
| driver->cdev->owner = THIS_MODULE; |
| driver->cdev->ops = &diagcharfops; |
| |
| err = cdev_add(driver->cdev, devno, 1); |
| |
| if (err) { |
| printk(KERN_INFO "diagchar cdev registration failed !\n\n"); |
| return -1; |
| } |
| |
| driver->diagchar_class = class_create(THIS_MODULE, "diag"); |
| |
| if (IS_ERR(driver->diagchar_class)) { |
| printk(KERN_ERR "Error creating diagchar class.\n"); |
| return -1; |
| } |
| |
| device_create(driver->diagchar_class, NULL, devno, |
| (void *)driver, "diag"); |
| |
| return 0; |
| |
| } |
| |
| static int diagchar_cleanup(void) |
| { |
| if (driver) { |
| if (driver->cdev) { |
| /* TODO - Check if device exists before deleting */ |
| device_destroy(driver->diagchar_class, |
| MKDEV(driver->major, |
| driver->minor_start)); |
| cdev_del(driver->cdev); |
| } |
| if (!IS_ERR(driver->diagchar_class)) |
| class_destroy(driver->diagchar_class); |
| kfree(driver); |
| } |
| return 0; |
| } |
| |
| #ifdef CONFIG_DIAG_SDIO_PIPE |
| void diag_sdio_fn(int type) |
| { |
| if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { |
| if (type == INIT) |
| diagfwd_sdio_init(); |
| else if (type == EXIT) |
| diagfwd_sdio_exit(); |
| } |
| } |
| #else |
| inline void diag_sdio_fn(int type) {} |
| #endif |
| |
| #ifdef CONFIG_DIAG_HSIC_PIPE |
| void diag_hsic_fn(int type) |
| { |
| if (type == INIT) |
| diagfwd_hsic_init(); |
| else if (type == EXIT) |
| diagfwd_hsic_exit(); |
| } |
| #else |
| inline void diag_hsic_fn(int type) {} |
| #endif |
| |
| static int __init diagchar_init(void) |
| { |
| dev_t dev; |
| int error; |
| |
| pr_debug("diagfwd initializing ..\n"); |
| driver = kzalloc(sizeof(struct diagchar_dev) + 5, GFP_KERNEL); |
| |
| if (driver) { |
| driver->used = 0; |
| timer_in_progress = 0; |
| driver->debug_flag = 1; |
| setup_timer(&drain_timer, drain_timer_func, 1234); |
| driver->itemsize = itemsize; |
| driver->poolsize = poolsize; |
| driver->itemsize_hdlc = itemsize_hdlc; |
| driver->poolsize_hdlc = poolsize_hdlc; |
| driver->itemsize_write_struct = itemsize_write_struct; |
| driver->poolsize_write_struct = poolsize_write_struct; |
| driver->num_clients = max_clients; |
| driver->logging_mode = USB_MODE; |
| driver->mask_check = 0; |
| mutex_init(&driver->diagchar_mutex); |
| init_waitqueue_head(&driver->wait_q); |
| INIT_WORK(&(driver->diag_drain_work), diag_drain_work_fn); |
| INIT_WORK(&(driver->diag_read_smd_work), diag_read_smd_work_fn); |
| INIT_WORK(&(driver->diag_read_smd_cntl_work), |
| diag_read_smd_cntl_work_fn); |
| INIT_WORK(&(driver->diag_read_smd_qdsp_work), |
| diag_read_smd_qdsp_work_fn); |
| INIT_WORK(&(driver->diag_read_smd_qdsp_cntl_work), |
| diag_read_smd_qdsp_cntl_work_fn); |
| INIT_WORK(&(driver->diag_read_smd_wcnss_work), |
| diag_read_smd_wcnss_work_fn); |
| INIT_WORK(&(driver->diag_read_smd_wcnss_cntl_work), |
| diag_read_smd_wcnss_cntl_work_fn); |
| diagfwd_init(); |
| diagfwd_cntl_init(); |
| diag_sdio_fn(INIT); |
| diag_hsic_fn(INIT); |
| pr_debug("diagchar initializing ..\n"); |
| driver->num = 1; |
| driver->name = ((void *)driver) + sizeof(struct diagchar_dev); |
| strlcpy(driver->name, "diag", 4); |
| |
| /* Get major number from kernel and initialize */ |
| error = alloc_chrdev_region(&dev, driver->minor_start, |
| driver->num, driver->name); |
| if (!error) { |
| driver->major = MAJOR(dev); |
| driver->minor_start = MINOR(dev); |
| } else { |
| printk(KERN_INFO "Major number not allocated\n"); |
| goto fail; |
| } |
| driver->cdev = cdev_alloc(); |
| error = diagchar_setup_cdev(dev); |
| if (error) |
| goto fail; |
| } else { |
| printk(KERN_INFO "kzalloc failed\n"); |
| goto fail; |
| } |
| |
| pr_info("diagchar initialized now"); |
| return 0; |
| |
| fail: |
| diagchar_cleanup(); |
| diagfwd_exit(); |
| diagfwd_cntl_exit(); |
| diag_sdio_fn(EXIT); |
| diag_hsic_fn(EXIT); |
| return -1; |
| } |
| |
| static void __exit diagchar_exit(void) |
| { |
| printk(KERN_INFO "diagchar exiting ..\n"); |
| /* On Driver exit, send special pool type to |
| ensure no memory leaks */ |
| diagmem_exit(driver, POOL_TYPE_ALL); |
| diagfwd_exit(); |
| diagfwd_cntl_exit(); |
| diag_sdio_fn(EXIT); |
| diag_hsic_fn(EXIT); |
| diagchar_cleanup(); |
| printk(KERN_INFO "done diagchar exit\n"); |
| } |
| |
| module_init(diagchar_init); |
| module_exit(diagchar_exit); |