/* 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 #include #include #include #include #include #include #include #include #ifdef CONFIG_DIAG_OVER_USB #include #endif #include #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 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 = 4096; /*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_reg = 600; unsigned int diag_threshold_reg = 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_HSIC_PIPE if (driver->logging_mode == MEMORY_DEVICE_MODE) queue_work(driver->diag_hsic_wq, &driver->diag_disconnect_work); #endif #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; #ifndef CONFIG_DIAG_HSIC_PIPE /* HSIC PIPE use case, connect over usb is not required */ diagfwd_connect(); #endif } #endif /* DIAG over USB */ /* Delete the pkt response table entry for the exiting process */ for (i = 0; i < diag_max_reg; 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_reg; 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_reg; 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_reg; 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_reg) { /* Increase table size by amount required */ diag_max_reg += pkt_params->count - count_entries; /* Make sure size doesnt go beyond threshold */ if (diag_max_reg > diag_threshold_reg) { diag_max_reg = diag_threshold_reg; pr_info("diag: best case memory allocation\n"); } temp_buf = krealloc(driver->table, diag_max_reg*sizeof(struct diag_master_table), GFP_KERNEL); if (!temp_buf) { diag_max_reg -= 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_reg; 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; } } mutex_unlock(&driver->diagchar_mutex); } 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->sub_logging_mode = UART_MODE; } else driver->sub_logging_mode = NO_LOGGING_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 = 1; driver->in_busy_wcnss_2 = 1; #ifdef CONFIG_DIAG_SDIO_PIPE driver->in_busy_sdio = 1; #endif #ifdef CONFIG_DIAG_HSIC_PIPE driver->in_busy_hsic_read = 1; driver->in_busy_hsic_write = 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_1 = 0; driver->in_busy_wcnss_2 = 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_HSIC_PIPE driver->in_busy_hsic_read = 0; if (driver->hsic_ch) queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_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(); #ifdef CONFIG_DIAG_HSIC_PIPE diagfwd_connect_hsic(WRITE_TO_SD); #endif 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_1 = 0; driver->in_busy_wcnss_2 = 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_HSIC_PIPE if (driver->hsic_ch) queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_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 == 1) { num_data++; /*Copy the length of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, (driver->write_ptr_wcnss_1->length), 4); /*Copy the actual data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> buf_in_wcnss_1), driver->write_ptr_wcnss_1->length); driver->in_busy_wcnss_1 = 0; } if (driver->in_busy_wcnss_2 == 1) { num_data++; /*Copy the length of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, (driver->write_ptr_wcnss_2->length), 4); /*Copy the actual data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> buf_in_wcnss_2), driver->write_ptr_wcnss_2->length); driver->in_busy_wcnss_2 = 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 #ifdef CONFIG_DIAG_HSIC_PIPE 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_hsic), driver->write_ptr_mdm->length); #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 #ifdef CONFIG_DIAG_HSIC_PIPE /* driver->in_busy_hsic_read = 0; */ if (driver->hsic_ch) queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_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; /* To removed "0x7E", when received only "0x7E" */ if (0x7e == *(((unsigned char *)buf))) return 0; #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 #ifdef CONFIG_DIAG_HSIC_PIPE if (driver->hsic_ch) { // QXDM Logging zero_pkt (with silent log on) second fix by JAGADISH KRISHNAMOORTHY pr_info("%s: hsic line busy %d \n",__func__,driver->in_busy_hsic_write); /* wait till write is succesfully written to CP */ if (driver->in_busy_hsic_write) wait_event_interruptible(driver->wait_q, (driver->in_busy_hsic_write != 1)); if (!driver->in_busy_hsic_write) { driver->in_busy_hsic_write = 1; err = diag_bridge_write((driver->user_space_data), payload_size); if (err) { pr_err("%s: data on hsic write err: %d\n", __func__, err); /* * If the error is recoverable, then clear * the write flag, so we will resubmit a * write on the next frame. Otherwise, don't * resubmit a write on the next frame. */ if ((-ESHUTDOWN) != err) driver->in_busy_hsic_write = 0; } } } #endif #if 0 /* send masks to modem now */ diag_process_hdlc((void *)(driver->user_space_data), payload_size); #endif return count; } if (payload_size > itemsize) { pr_err("diag: Dropping packet, packet payload size crosses" "4KB limit. Current payload size %d\n", payload_size); driver->dropped_count++; return -EBADMSG; } 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 count; 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);