/* Copyright (c) 2008-2013, 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 #include #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" #include "diag_dci.h" #ifdef CONFIG_DIAG_SDIO_PIPE #include "diagfwd_sdio.h" #endif #ifdef CONFIG_DIAGFWD_BRIDGE_CODE #include "diagfwd_hsic.h" #include "diagfwd_smux.h" #endif #include #include "diag_debugfs.h" #include "diag_masks.h" #include "diagfwd_bridge.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 = 4096; /*Size of item in the mempool */ static unsigned int poolsize = 12; /*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 = 10; /*Number of items in the mempool */ /* for user buffer */ static unsigned int itemsize_user = 8192; /*Size of item in the mempool */ static unsigned int poolsize_user = 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 = 10;/* 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*/ int diag_max_reg = 600; 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 /* returns the next delayed rsp id - rollsover the id if wrapping is enabled. */ uint16_t diagpkt_next_delayed_rsp_id(uint16_t rspid) { if (rspid < DIAGPKT_MAX_DELAYED_RSP) rspid++; else { if (wrap_enabled) { rspid = 1; wrap_count++; } else rspid = DIAGPKT_MAX_DELAYED_RSP; } delayed_rsp_id = rspid; return delayed_rsp_id; } #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); } #ifdef CONFIG_DIAGFWD_BRIDGE_CODE void diag_clear_hsic_tbl(void) { int i, j; /* Clear for all active HSIC bridges */ for (j = 0; j < MAX_HSIC_CH; j++) { if (diag_hsic[j].hsic_ch) { diag_hsic[j].num_hsic_buf_tbl_entries = 0; for (i = 0; i < diag_hsic[j].poolsize_hsic_write; i++) { if (diag_hsic[j].hsic_buf_tbl[i].buf) { /* Return the buffer to the pool */ diagmem_free(driver, (unsigned char *) (diag_hsic[j].hsic_buf_tbl[i]. buf), j+POOL_TYPE_HSIC); diag_hsic[j].hsic_buf_tbl[i].buf = 0; } diag_hsic[j].hsic_buf_tbl[i].length = 0; } } } } #else void diag_clear_hsic_tbl(void) { } #endif 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] = 0x0; 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 = -1; struct diagchar_priv *diagpriv_data = file->private_data; pr_debug("diag: process exit %s\n", current->comm); if (!(file->private_data)) { pr_alert("diag: Invalid file pointer"); return -ENOMEM; } if (!driver) return -ENOMEM; /* clean up any DCI registrations, if this is a DCI client * This will specially help in case of ungraceful exit of any DCI client * This call will remove any pending registrations of such client */ if (diag_dci_find_client_index(current->tgid) != DCI_CLIENT_INDEX_INVALID) diagchar_ioctl(NULL, DIAG_IOCTL_DCI_DEINIT, 0); /* If the exiting process is the socket process */ mutex_lock(&driver->diagchar_mutex); if (driver->socket_process && (driver->socket_process->tgid == current->tgid)) { driver->socket_process = NULL; } if (driver->callback_process && (driver->callback_process->tgid == current->tgid)) { driver->callback_process = NULL; } mutex_unlock(&driver->diagchar_mutex); #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; diag_send_diag_mode_update(MODE_REALTIME); diagfwd_connect(); #ifdef CONFIG_DIAGFWD_BRIDGE_CODE diag_clear_hsic_tbl(); diagfwd_cancel_hsic(REOPEN_HSIC); diagfwd_connect_bridge(0); #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; 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; } 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 peripheral) { 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 == peripheral) 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, unsigned 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_DATA; } else { driver->table[j].process_id = NON_APPS_PROC; driver->table[j].client_id = params->client_id; } (*count_entries)++; } static int diag_get_remote(int remote_info) { int val = (remote_info < 0) ? -remote_info : remote_info; int remote_val; switch (val) { case MDM: case MDM2: case MDM3: case MDM4: case QSC: remote_val = -remote_info; break; default: remote_val = 0; break; } return remote_val; } #ifdef CONFIG_DIAGFWD_BRIDGE_CODE uint16_t diag_get_remote_device_mask(void) { uint16_t remote_dev = 0; int i; /* Check for MDM processor */ for (i = 0; i < MAX_HSIC_CH; i++) if (diag_hsic[i].hsic_inited) remote_dev |= 1 << i; /* Check for QSC processor */ if (driver->diag_smux_enabled) remote_dev |= 1 << SMUX; return remote_dev; } int diag_copy_remote(char __user *buf, size_t count, int *pret, int *pnum_data) { int i; int index; int exit_stat = 1; int ret = *pret; int num_data = *pnum_data; int remote_token; unsigned long spin_lock_flags; struct diag_write_device hsic_buf_tbl[NUM_HSIC_BUF_TBL_ENTRIES]; remote_token = diag_get_remote(MDM); for (index = 0; index < MAX_HSIC_CH; index++) { if (!diag_hsic[index].hsic_inited) { remote_token--; continue; } spin_lock_irqsave(&diag_hsic[index].hsic_spinlock, spin_lock_flags); for (i = 0; i < diag_hsic[index].poolsize_hsic_write; i++) { hsic_buf_tbl[i].buf = diag_hsic[index].hsic_buf_tbl[i].buf; diag_hsic[index].hsic_buf_tbl[i].buf = 0; hsic_buf_tbl[i].length = diag_hsic[index].hsic_buf_tbl[i].length; diag_hsic[index].hsic_buf_tbl[i].length = 0; } diag_hsic[index].num_hsic_buf_tbl_entries = 0; spin_unlock_irqrestore(&diag_hsic[index].hsic_spinlock, spin_lock_flags); for (i = 0; i < diag_hsic[index].poolsize_hsic_write; i++) { if (hsic_buf_tbl[i].length > 0) { pr_debug("diag: HSIC copy to user, i: %d, buf: %x, len: %d\n", i, (unsigned int)hsic_buf_tbl[i].buf, hsic_buf_tbl[i].length); num_data++; /* Copy the negative token */ if (copy_to_user(buf+ret, &remote_token, 4)) { num_data--; goto drop_hsic; } ret += 4; /* Copy the length of data being passed */ if (copy_to_user(buf+ret, (void *)&(hsic_buf_tbl[i].length), 4)) { num_data--; goto drop_hsic; } ret += 4; /* Copy the actual data being passed */ if (copy_to_user(buf+ret, (void *)hsic_buf_tbl[i].buf, hsic_buf_tbl[i].length)) { ret -= 4; num_data--; goto drop_hsic; } ret += hsic_buf_tbl[i].length; drop_hsic: /* Return the buffer to the pool */ diagmem_free(driver, (unsigned char *)(hsic_buf_tbl[i].buf), index+POOL_TYPE_HSIC); /* Call the write complete function */ diagfwd_write_complete_hsic(NULL, index); } } remote_token--; } if (driver->in_busy_smux == 1) { remote_token = diag_get_remote(QSC); num_data++; /* Copy the negative token of data being passed */ COPY_USER_SPACE_OR_EXIT(buf+ret, remote_token, 4); /* 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_smux), driver->write_ptr_mdm->length); pr_debug("diag: SMUX data copied\n"); driver->in_busy_smux = 0; } exit_stat = 0; exit: *pret = ret; *pnum_data = num_data; return exit_stat; } static void diag_update_data_ready(int index) { int clear_bit = 1; unsigned long hsic_lock_flags; unsigned long ready_lock_flags; int i; /* * Determine whether the data_ready USER_SPACE_DATA_TYPE bit * should be updated/cleared or not. There is a race condition that * can occur when in MEMORY_DEVICE_MODE with the hsic data. * When new hsic data arrives we prepare the data so it can * later be copied to userspace. We set the USER_SPACE_DATA_TYPE * bit in data ready at that time. We later copy the hsic data * to userspace and clear the USER_SPACE_DATA_TYPE bit in * data ready. The race condition occurs if new data arrives (bit set) * while we are processing the current data and sending * it to userspace (bit clear). The clearing of the bit can * overwrite the setting of the bit. */ spin_lock_irqsave(&driver->hsic_ready_spinlock, ready_lock_flags); for (i = 0; i < MAX_HSIC_CH; i++) { if (diag_hsic[i].hsic_inited) { spin_lock_irqsave(&diag_hsic[i].hsic_spinlock, hsic_lock_flags); if ((diag_hsic[i].num_hsic_buf_tbl_entries > 0) && diag_hsic[i].hsic_device_enabled && diag_hsic[i].hsic_ch) { /* New data do not clear the bit */ clear_bit = 0; } spin_unlock_irqrestore(&diag_hsic[i].hsic_spinlock, hsic_lock_flags); if (!clear_bit) break; } } if (clear_bit) driver->data_ready[index] ^= USER_SPACE_DATA_TYPE; spin_unlock_irqrestore(&driver->hsic_ready_spinlock, ready_lock_flags); } #else inline uint16_t diag_get_remote_device_mask(void) { return 0; } inline int diag_copy_remote(char __user *buf, size_t count, int *pret, int *pnum_data) { return 0; } static void diag_update_data_ready(int index) { driver->data_ready[index] ^= USER_SPACE_DATA_TYPE; } #endif int diag_command_reg(unsigned long ioarg) { int i = 0, success = -EINVAL, j; void *temp_buf; unsigned int count_entries = 0, interim_count = 0; struct bindpkt_params_per_process pkt_params; struct bindpkt_params *params; struct bindpkt_params *head_params; if (copy_from_user(&pkt_params, (void *)ioarg, sizeof(struct bindpkt_params_per_process))) { return -EFAULT; } if ((UINT_MAX/sizeof(struct bindpkt_params)) < pkt_params.count) { pr_warn("diag: integer overflow while multiply\n"); return -EFAULT; } head_params = kzalloc(pkt_params.count*sizeof( struct bindpkt_params), GFP_KERNEL); if (ZERO_OR_NULL_PTR(head_params)) { pr_err("diag: unable to alloc memory\n"); return -ENOMEM; } else params = head_params; if (copy_from_user(params, pkt_params.params, pkt_params.count*sizeof(struct bindpkt_params))) { kfree(head_params); return -EFAULT; } mutex_lock(&driver->diagchar_mutex); for (i = 0; i < diag_max_reg; i++) { if (driver->table[i].process_id == 0) { diag_add_reg(i, params, &success, &count_entries); if (pkt_params.count > count_entries) { params++; } else { kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return success; } } } if (i < diag_threshold_reg) { /* Increase table size by amount required */ if (pkt_params.count >= count_entries) { interim_count = pkt_params.count - count_entries; } else { pr_warn("diag: error in params count\n"); kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return -EFAULT; } if (UINT_MAX - diag_max_reg >= interim_count) { diag_max_reg += interim_count; } else { pr_warn("diag: Integer overflow\n"); kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return -EFAULT; } /* Make sure size doesnt go beyond threshold */ if (diag_max_reg > diag_threshold_reg) { diag_max_reg = diag_threshold_reg; pr_err("diag: best case memory allocation\n"); } if (UINT_MAX/sizeof(struct diag_master_table) < diag_max_reg) { pr_warn("diag: integer overflow\n"); kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return -EFAULT; } temp_buf = krealloc(driver->table, diag_max_reg*sizeof(struct diag_master_table), GFP_KERNEL); if (!temp_buf) { pr_err("diag: Insufficient memory for reg.\n"); if (pkt_params.count >= count_entries) { interim_count = pkt_params.count - count_entries; } else { pr_warn("diag: params count error\n"); kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return -EFAULT; } if (diag_max_reg >= interim_count) { diag_max_reg -= interim_count; } else { pr_warn("diag: Integer underflow\n"); kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return -EFAULT; } kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return 0; } else { driver->table = temp_buf; } for (j = i; j < diag_max_reg; j++) { diag_add_reg(j, params, &success, &count_entries); if (pkt_params.count > count_entries) { params++; } else { kfree(head_params); mutex_unlock(&driver->diagchar_mutex); return success; } } kfree(head_params); mutex_unlock(&driver->diagchar_mutex); } else { kfree(head_params); mutex_unlock(&driver->diagchar_mutex); pr_err("Max size reached, Pkt Registration failed for Process %d", current->tgid); } success = 0; return success; } #ifdef CONFIG_DIAGFWD_BRIDGE_CODE void diag_cmp_logging_modes_diagfwd_bridge(int old_mode, int new_mode) { if (old_mode == MEMORY_DEVICE_MODE && new_mode == NO_LOGGING_MODE) { diagfwd_disconnect_bridge(0); diag_clear_hsic_tbl(); } else if (old_mode == NO_LOGGING_MODE && new_mode == MEMORY_DEVICE_MODE) { int i; for (i = 0; i < MAX_HSIC_CH; i++) if (diag_hsic[i].hsic_inited) diag_hsic[i].hsic_data_requested = driver->real_time_mode ? 1 : 0; diagfwd_connect_bridge(0); } else if (old_mode == USB_MODE && new_mode == NO_LOGGING_MODE) { diagfwd_disconnect_bridge(0); } else if (old_mode == NO_LOGGING_MODE && new_mode == USB_MODE) { diagfwd_connect_bridge(0); } else if (old_mode == USB_MODE && new_mode == MEMORY_DEVICE_MODE) { if (driver->real_time_mode) diagfwd_cancel_hsic(REOPEN_HSIC); else diagfwd_cancel_hsic(DONT_REOPEN_HSIC); diagfwd_connect_bridge(0); } else if (old_mode == MEMORY_DEVICE_MODE && new_mode == USB_MODE) { diag_clear_hsic_tbl(); diagfwd_cancel_hsic(REOPEN_HSIC); diagfwd_connect_bridge(0); } } #else void diag_cmp_logging_modes_diagfwd_bridge(int old_mode, int new_mode) { } #endif #ifdef CONFIG_DIAG_SDIO_PIPE void diag_cmp_logging_modes_sdio_pipe(int old_mode, int new_mode) { if (old_mode == MEMORY_DEVICE_MODE && new_mode == NO_LOGGING_MODE) { mutex_lock(&driver->diagchar_mutex); driver->in_busy_sdio = 1; mutex_unlock(&driver->diagchar_mutex); } else if (old_mode == NO_LOGGING_MODE && new_mode == MEMORY_DEVICE_MODE) { mutex_lock(&driver->diagchar_mutex); driver->in_busy_sdio = 0; mutex_unlock(&driver->diagchar_mutex); /* Poll SDIO channel to check for data */ if (driver->sdio_ch) queue_work(driver->diag_sdio_wq, &(driver->diag_read_sdio_work)); } else if (old_mode == USB_MODE && new_mode == MEMORY_DEVICE_MODE) { mutex_lock(&driver->diagchar_mutex); driver->in_busy_sdio = 0; mutex_unlock(&driver->diagchar_mutex); /* Poll SDIO channel to check for data */ if (driver->sdio_ch) queue_work(driver->diag_sdio_wq, &(driver->diag_read_sdio_work)); } } #else void diag_cmp_logging_modes_sdio_pipe(int old_mode, int new_mode) { } #endif int diag_switch_logging(unsigned long ioarg) { int temp = 0, success = -EINVAL, status = 0; int temp_realtime_mode = driver->real_time_mode; int requested_mode = (int)ioarg; switch (requested_mode) { case USB_MODE: case MEMORY_DEVICE_MODE: case NO_LOGGING_MODE: case UART_MODE: case SOCKET_MODE: case CALLBACK_MODE: case MEMORY_DEVICE_MODE_NRT: break; default: pr_err("diag: In %s, request to switch to invalid mode: %d\n", __func__, requested_mode); return -EINVAL; } mutex_lock(&driver->diagchar_mutex); temp = driver->logging_mode; driver->logging_mode = requested_mode; if (driver->logging_mode == MEMORY_DEVICE_MODE_NRT) { diag_send_diag_mode_update(MODE_NONREALTIME); driver->logging_mode = MEMORY_DEVICE_MODE; } else { diag_send_diag_mode_update(MODE_REALTIME); } if (temp == driver->logging_mode) { mutex_unlock(&driver->diagchar_mutex); if (driver->logging_mode != MEMORY_DEVICE_MODE || temp_realtime_mode) pr_info_ratelimited("diag: Already in logging mode change requested, mode: %d\n", driver->logging_mode); return 0; } if (driver->logging_mode == MEMORY_DEVICE_MODE) { diag_clear_hsic_tbl(); driver->mask_check = 1; if (driver->socket_process) { /* * Notify the socket logging process that we * are switching to MEMORY_DEVICE_MODE */ status = send_sig(SIGCONT, driver->socket_process, 0); if (status) { pr_err("diag: %s, Error notifying ", __func__); pr_err("socket process, status: %d\n", status); } } } else if (driver->logging_mode == SOCKET_MODE) { driver->socket_process = current; } else if (driver->logging_mode == CALLBACK_MODE) { driver->callback_process = current; } if (driver->logging_mode == UART_MODE || driver->logging_mode == SOCKET_MODE || driver->logging_mode == CALLBACK_MODE) { diag_clear_hsic_tbl(); 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) { diag_reset_smd_data(RESET_AND_NO_QUEUE); diag_cmp_logging_modes_sdio_pipe(temp, driver->logging_mode); diag_cmp_logging_modes_diagfwd_bridge(temp, driver->logging_mode); } else if (temp == NO_LOGGING_MODE && driver->logging_mode == MEMORY_DEVICE_MODE) { diag_reset_smd_data(RESET_AND_QUEUE); diag_cmp_logging_modes_sdio_pipe(temp, driver->logging_mode); diag_cmp_logging_modes_diagfwd_bridge(temp, driver->logging_mode); } else if (temp == USB_MODE && driver->logging_mode == NO_LOGGING_MODE) { diagfwd_disconnect(); diag_cmp_logging_modes_diagfwd_bridge(temp, driver->logging_mode); } else if (temp == NO_LOGGING_MODE && driver->logging_mode == USB_MODE) { diagfwd_connect(); diag_cmp_logging_modes_diagfwd_bridge(temp, driver->logging_mode); } else if (temp == USB_MODE && driver->logging_mode == MEMORY_DEVICE_MODE) { diagfwd_disconnect(); diag_reset_smd_data(RESET_AND_QUEUE); diag_cmp_logging_modes_sdio_pipe(temp, driver->logging_mode); diag_cmp_logging_modes_diagfwd_bridge(temp, driver->logging_mode); } else if (temp == MEMORY_DEVICE_MODE && driver->logging_mode == USB_MODE) { diagfwd_connect(); diag_cmp_logging_modes_diagfwd_bridge(temp, driver->logging_mode); } success = 1; return success; } long diagchar_ioctl(struct file *filp, unsigned int iocmd, unsigned long ioarg) { int i, result = -EINVAL, interim_size = 0, client_id = 0; uint16_t support_list = 0, interim_rsp_id, remote_dev; struct diag_dci_client_tbl *dci_params; struct diag_dci_health_stats stats; struct diag_log_event_stats le_stats; struct diagpkt_delay_params delay_params; switch (iocmd) { case DIAG_IOCTL_COMMAND_REG: result = diag_command_reg(ioarg); break; case DIAG_IOCTL_GET_DELAYED_RSP_ID: if (copy_from_user(&delay_params, (void *)ioarg, sizeof(struct diagpkt_delay_params))) return -EFAULT; if ((delay_params.rsp_ptr) && (delay_params.size == sizeof(delayed_rsp_id)) && (delay_params.num_bytes_ptr)) { interim_rsp_id = diagpkt_next_delayed_rsp_id( delayed_rsp_id); if (copy_to_user((void *)delay_params.rsp_ptr, &interim_rsp_id, sizeof(uint16_t))) return -EFAULT; interim_size = sizeof(delayed_rsp_id); if (copy_to_user((void *)delay_params.num_bytes_ptr, &interim_size, sizeof(int))) return -EFAULT; result = 0; } break; case DIAG_IOCTL_DCI_REG: if (driver->dci_state == DIAG_DCI_NO_REG) return DIAG_DCI_NO_REG; if (driver->num_dci_client >= MAX_DCI_CLIENTS) return DIAG_DCI_NO_REG; dci_params = kzalloc(sizeof(struct diag_dci_client_tbl), GFP_KERNEL); if (dci_params == NULL) { pr_err("diag: unable to alloc memory\n"); return -ENOMEM; } if (copy_from_user(dci_params, (void *)ioarg, sizeof(struct diag_dci_client_tbl))) { kfree(dci_params); return -EFAULT; } mutex_lock(&driver->dci_mutex); if (!(driver->num_dci_client)) { for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) driver->smd_dci[i].in_busy_1 = 0; if (driver->supports_separate_cmdrsp) for (i = 0; i < NUM_SMD_DCI_CMD_CHANNELS; i++) driver->smd_dci_cmd[i].in_busy_1 = 0; } driver->num_dci_client++; pr_debug("diag: In %s, id = %d\n", __func__, driver->dci_client_id); driver->dci_client_id++; for (i = 0; i < MAX_DCI_CLIENTS; i++) { if (driver->dci_client_tbl[i].client == NULL) { driver->dci_client_tbl[i].client = current; driver->dci_client_tbl[i].list = dci_params->list; driver->dci_client_tbl[i].signal_type = dci_params->signal_type; create_dci_log_mask_tbl(driver-> dci_client_tbl[i].dci_log_mask); create_dci_event_mask_tbl(driver-> dci_client_tbl[i].dci_event_mask); driver->dci_client_tbl[i].data_len = 0; driver->dci_client_tbl[i].dci_data = kzalloc(IN_BUF_SIZE, GFP_KERNEL); driver->dci_client_tbl[i].total_capacity = IN_BUF_SIZE; driver->dci_client_tbl[i].dropped_logs = 0; driver->dci_client_tbl[i].dropped_events = 0; driver->dci_client_tbl[i].received_logs = 0; driver->dci_client_tbl[i].received_events = 0; break; } } kfree(dci_params); mutex_unlock(&driver->dci_mutex); result = driver->dci_client_id; break; case DIAG_IOCTL_DCI_DEINIT: result = -EIO; /* Delete this process from DCI table */ mutex_lock(&driver->dci_mutex); i = diag_dci_find_client_index(current->tgid); if (i == DCI_CLIENT_INDEX_INVALID) { result = DIAG_DCI_NOT_SUPPORTED; } else { /* clear respective cumulative log masks */ clear_client_dci_cumulative_log_mask(i); /* send updated log mask to peripherals */ result = diag_send_dci_log_mask(driver->smd_cntl[MODEM_DATA].ch); if (result != DIAG_DCI_NO_ERROR) { mutex_unlock(&driver->dci_mutex); return result; } /* clear respective cumulative event masks */ clear_client_dci_cumulative_event_mask(i); /* send updated event mask to peripherals */ result = diag_send_dci_event_mask( driver->smd_cntl[MODEM_DATA].ch); if (result != DIAG_DCI_NO_ERROR) { mutex_unlock(&driver->dci_mutex); return result; } result = i; /* Delete this process from DCI table */ for (i = 0; i < dci_max_reg; i++) if (driver->req_tracking_tbl[i].pid == current->tgid) driver->req_tracking_tbl[i].pid = 0; driver->dci_client_tbl[result].client = NULL; kfree(driver->dci_client_tbl[result].dci_data); driver->dci_client_tbl[result].dci_data = NULL; driver->num_dci_client--; } mutex_unlock(&driver->dci_mutex); break; case DIAG_IOCTL_DCI_SUPPORT: for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) { if (driver->smd_dci[i].ch) support_list |= driver->smd_dci[i].peripheral_mask; } if (copy_to_user((void *)ioarg, &support_list, sizeof(uint16_t))) return -EFAULT; result = DIAG_DCI_NO_ERROR; break; case DIAG_IOCTL_DCI_HEALTH_STATS: if (copy_from_user(&stats, (void *)ioarg, sizeof(struct diag_dci_health_stats))) return -EFAULT; mutex_lock(&dci_health_mutex); i = diag_dci_find_client_index(current->tgid); if (i != DCI_CLIENT_INDEX_INVALID) { dci_params = &(driver->dci_client_tbl[i]); stats.dropped_logs = dci_params->dropped_logs; stats.dropped_events = dci_params->dropped_events; stats.received_logs = dci_params->received_logs; stats.received_events = dci_params->received_events; if (stats.reset_status) { dci_params->dropped_logs = 0; dci_params->dropped_events = 0; dci_params->received_logs = 0; dci_params->received_events = 0; } } mutex_unlock(&dci_health_mutex); if (copy_to_user((void *)ioarg, &stats, sizeof(struct diag_dci_health_stats))) return -EFAULT; result = DIAG_DCI_NO_ERROR; break; case DIAG_IOCTL_DCI_LOG_STATUS: if (copy_from_user(&le_stats, (void *)ioarg, sizeof(struct diag_log_event_stats))) return -EFAULT; le_stats.is_set = diag_dci_query_log_mask(le_stats.code); if (copy_to_user((void *)ioarg, &le_stats, sizeof(struct diag_log_event_stats))) return -EFAULT; result = DIAG_DCI_NO_ERROR; break; case DIAG_IOCTL_DCI_EVENT_STATUS: if (copy_from_user(&le_stats, (void *)ioarg, sizeof(struct diag_log_event_stats))) return -EFAULT; le_stats.is_set = diag_dci_query_event_mask(le_stats.code); if (copy_to_user((void *)ioarg, &le_stats, sizeof(struct diag_log_event_stats))) return -EFAULT; result = DIAG_DCI_NO_ERROR; break; case DIAG_IOCTL_DCI_CLEAR_LOGS: if (copy_from_user((void *)&client_id, (void *)ioarg, sizeof(int))) return -EFAULT; result = diag_dci_clear_log_mask(); break; case DIAG_IOCTL_DCI_CLEAR_EVENTS: if (copy_from_user(&client_id, (void *)ioarg, sizeof(int))) return -EFAULT; result = diag_dci_clear_event_mask(); break; case DIAG_IOCTL_LSM_DEINIT: for (i = 0; i < driver->num_clients; i++) if (driver->client_map[i].pid == current->tgid) break; if (i == driver->num_clients) return -EINVAL; driver->data_ready[i] |= DEINIT_TYPE; wake_up_interruptible(&driver->wait_q); result = 1; break; case DIAG_IOCTL_SWITCH_LOGGING: result = diag_switch_logging(ioarg); break; case DIAG_IOCTL_REMOTE_DEV: remote_dev = diag_get_remote_device_mask(); if (copy_to_user((void *)ioarg, &remote_dev, sizeof(uint16_t))) result = -EFAULT; else result = 1; break; } return result; } static int diagchar_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct diag_dci_client_tbl *entry; int index = -1, i = 0, ret = 0; int num_data = 0, data_type; int remote_token; int exit_stat; int clear_read_wakelock; 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); clear_read_wakelock = 0; if ((driver->data_ready[index] & USER_SPACE_DATA_TYPE) && (driver-> logging_mode == MEMORY_DEVICE_MODE)) { remote_token = 0; pr_debug("diag: process woken up\n"); /*Copy the type of data being passed*/ data_type = driver->data_ready[index] & USER_SPACE_DATA_TYPE; COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); /* place holder for number of data field */ ret += 4; for (i = 0; i < driver->buf_tbl_size; 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 peripheral data */ for (i = 0; i < NUM_SMD_DATA_CHANNELS; i++) { struct diag_smd_info *data = &driver->smd_data[i]; if (data->in_busy_1 == 1) { num_data++; /*Copy the length of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, (data->write_ptr_1->length), 4); /*Copy the actual data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, *(data->buf_in_1), data->write_ptr_1->length); if (!driver->real_time_mode) { process_lock_on_copy(&data->nrt_lock); clear_read_wakelock++; } data->in_busy_1 = 0; } if (data->in_busy_2 == 1) { num_data++; /*Copy the length of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, (data->write_ptr_2->length), 4); /*Copy the actual data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, *(data->buf_in_2), data->write_ptr_2->length); if (!driver->real_time_mode) { process_lock_on_copy(&data->nrt_lock); clear_read_wakelock++; } data->in_busy_2 = 0; } } if (driver->supports_separate_cmdrsp) { for (i = 0; i < NUM_SMD_CMD_CHANNELS; i++) { struct diag_smd_info *data = &driver->smd_cmd[i]; if (!driver->separate_cmdrsp[i]) continue; if (data->in_busy_1 == 1) { num_data++; /*Copy the length of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, (data->write_ptr_1->length), 4); /*Copy the actual data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, *(data->buf_in_1), data->write_ptr_1->length); data->in_busy_1 = 0; } if (data->in_busy_2 == 1) { num_data++; /*Copy the length of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, (data->write_ptr_2->length), 4); /*Copy the actual data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, *(data->buf_in_2), data->write_ptr_2->length); data->in_busy_2 = 0; } } } #ifdef CONFIG_DIAG_SDIO_PIPE /* copy 9K data over SDIO */ if (driver->in_busy_sdio == 1) { remote_token = diag_get_remote(MDM); num_data++; /*Copy the negative token of data being passed*/ COPY_USER_SPACE_OR_EXIT(buf+ret, remote_token, 4); /*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 date from remote processors */ exit_stat = diag_copy_remote(buf, count, &ret, &num_data); if (exit_stat == 1) goto exit; /* copy number of data fields */ COPY_USER_SPACE_OR_EXIT(buf+4, num_data, 4); ret -= 4; diag_update_data_ready(index); for (i = 0; i < NUM_SMD_DATA_CHANNELS; i++) { if (driver->smd_data[i].ch) queue_work(driver->diag_wq, &(driver->smd_data[i].diag_read_smd_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_DATA_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_DATA_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; driver->in_busy_pktdata = 0; goto exit; } if (driver->data_ready[index] & DCI_DATA_TYPE) { /* Copy the type of data being passed */ data_type = driver->data_ready[index] & DCI_DATA_TYPE; COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); /* check the current client and copy its data */ for (i = 0; i < MAX_DCI_CLIENTS; i++) { entry = &(driver->dci_client_tbl[i]); if (entry && entry->client) { if (current->tgid == entry->client->tgid) { COPY_USER_SPACE_OR_EXIT(buf+4, entry->data_len, 4); COPY_USER_SPACE_OR_EXIT(buf+8, *(entry->dci_data), entry->data_len); entry->data_len = 0; break; } } } driver->data_ready[index] ^= DCI_DATA_TYPE; for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) { driver->smd_dci[i].in_busy_1 = 0; if (driver->smd_dci[i].ch) queue_work(driver->diag_dci_wq, &(driver->smd_dci[i].diag_read_smd_work)); } if (driver->supports_separate_cmdrsp) { for (i = 0; i < NUM_SMD_DCI_CMD_CHANNELS; i++) { if (!driver->separate_cmdrsp[i]) continue; driver->smd_dci_cmd[i].in_busy_1 = 0; if (driver->smd_dci_cmd[i].ch) queue_work(driver->diag_dci_wq, &(driver->smd_dci_cmd[i]. diag_read_smd_work)); } } goto exit; } exit: if (clear_read_wakelock) { for (i = 0; i < NUM_SMD_DATA_CHANNELS; i++) process_lock_on_copy_complete( &driver->smd_data[i].nrt_lock); } 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, token_offset = 0; int remote_proc = 0, index; #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; void *user_space_data = NULL; unsigned int payload_size; index = 0; /* 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 */ if (count < 4) { pr_err("diag: Client sending short data\n"); return -EBADMSG; } payload_size = count - 4; if (payload_size > USER_SPACE_DATA) { pr_err("diag: Dropping packet, packet payload size crosses 8KB limit. Current payload size %d\n", payload_size); driver->dropped_count++; return -EBADMSG; } #ifdef CONFIG_DIAG_OVER_USB if (((pkt_type != DCI_DATA_TYPE) && (driver->logging_mode == USB_MODE) && (!driver->usb_connected)) || (driver->logging_mode == NO_LOGGING_MODE)) { /*Drop the diag payload */ return -EIO; } #endif /* DIAG over USB */ if (pkt_type == DCI_DATA_TYPE) { user_space_data = diagmem_alloc(driver, payload_size, POOL_TYPE_USER); if (!user_space_data) { driver->dropped_count++; return -ENOMEM; } err = copy_from_user(user_space_data, buf + 4, payload_size); if (err) { pr_alert("diag: copy failed for DCI data\n"); return DIAG_DCI_SEND_DATA_FAIL; } err = diag_process_dci_transaction(user_space_data, payload_size); diagmem_free(driver, user_space_data, POOL_TYPE_USER); return err; } if (pkt_type == CALLBACK_DATA_TYPE) { 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) { pr_err("diag: copy failed for user space data\n"); return -EIO; } /* Check for proc_type */ remote_proc = diag_get_remote(*(int *)buf_copy); if (!remote_proc) { wait_event_interruptible(driver->wait_q, (driver->in_busy_pktdata == 0)); ret = diag_process_apps_pkt(buf_copy, payload_size); diagmem_free(driver, buf_copy, POOL_TYPE_COPY); return ret; } /* The packet is for the remote processor */ token_offset = 4; payload_size -= 4; buf += 4; /* Perform HDLC encoding on incoming data */ send.state = DIAG_STATE_START; send.pkt = (void *)(buf_copy + token_offset); send.last = (void *)(buf_copy + token_offset - 1 + payload_size); send.terminate = 1; 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 < (2 * payload_size) + 3) { pr_err("diag: Dropping packet, HDLC encoded packet payload size crosses buffer limit. Current payload size %d\n", ((2*payload_size) + 3)); driver->dropped_count++; ret = -EBADMSG; diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); goto fail_free_hdlc; } enc.dest = buf_hdlc + driver->used; enc.dest_last = (void *)(buf_hdlc + driver->used + (2 * payload_size) + token_offset - 1); diag_hdlc_encode(&send, &enc); #ifdef CONFIG_DIAG_SDIO_PIPE /* send masks to 9k too */ if (driver->sdio_ch && (remote_proc == MDM)) { 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 *) (char *)buf_hdlc, payload_size + 3); } } #endif #ifdef CONFIG_DIAGFWD_BRIDGE_CODE /* send masks to All 9k */ if ((remote_proc >= MDM) && (remote_proc <= MDM4)) { index = remote_proc - MDM; if (diag_hsic[index].hsic_ch && (payload_size > 0)) { /* wait sending mask updates * if HSIC ch not ready */ if (diag_hsic[index].in_busy_hsic_write) wait_event_interruptible(driver->wait_q, (diag_hsic[index]. in_busy_hsic_write != 1)); diag_hsic[index].in_busy_hsic_write = 1; diag_hsic[index].in_busy_hsic_read_on_device = 0; err = diag_bridge_write(index, (char *)buf_hdlc, payload_size + 3); if (err) { pr_err("diag: err sending mask to MDM: %d\n", 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) diag_hsic[index]. in_busy_hsic_write = 0; } } } if (driver->diag_smux_enabled && (remote_proc == QSC) && driver->lcid) { if (payload_size > 0) { err = msm_smux_write(driver->lcid, NULL, (char *)buf_hdlc, payload_size + 3); if (err) { pr_err("diag:send mask to MDM err %d", err); ret = err; } } } #endif diagmem_free(driver, buf_copy, POOL_TYPE_COPY); diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); buf_copy = NULL; buf_hdlc = NULL; driver->used = 0; mutex_unlock(&driver->diagchar_mutex); return ret; } if (pkt_type == USER_SPACE_DATA_TYPE) { user_space_data = diagmem_alloc(driver, payload_size, POOL_TYPE_USER); if (!user_space_data) { driver->dropped_count++; return -ENOMEM; } err = copy_from_user(user_space_data, buf + 4, payload_size); if (err) { pr_err("diag: copy failed for user space data\n"); diagmem_free(driver, user_space_data, POOL_TYPE_USER); return -EIO; } /* Check for proc_type */ remote_proc = diag_get_remote(*(int *)user_space_data); if (remote_proc) { token_offset = 4; payload_size -= 4; buf += 4; } /* Check masks for On-Device logging */ if (driver->mask_check) { if (!mask_request_validate(user_space_data + token_offset)) { pr_alert("diag: mask request Invalid\n"); diagmem_free(driver, user_space_data, POOL_TYPE_USER); 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", *((user_space_data + token_offset)+i)); #endif #ifdef CONFIG_DIAG_SDIO_PIPE /* send masks to 9k too */ if (driver->sdio_ch && (remote_proc == MDM)) { 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 *) (user_space_data + token_offset), payload_size); } } #endif #ifdef CONFIG_DIAGFWD_BRIDGE_CODE /* send masks to All 9k */ if ((remote_proc >= MDM) && (remote_proc <= MDM4) && (payload_size > 0)) { index = remote_proc - MDM; /* * If hsic data is being requested for this remote * processor and its hsic in not open */ if (!diag_hsic[index].hsic_device_opened) { diag_hsic[index].hsic_data_requested = 1; connect_bridge(0, index); } if (diag_hsic[index].hsic_ch) { /* wait sending mask updates * if HSIC ch not ready */ if (diag_hsic[index].in_busy_hsic_write) wait_event_interruptible(driver->wait_q, (diag_hsic[index]. in_busy_hsic_write != 1)); diag_hsic[index].in_busy_hsic_write = 1; diag_hsic[index].in_busy_hsic_read_on_device = 0; err = diag_bridge_write(index, user_space_data + token_offset, payload_size); if (err) { pr_err("diag: err sending mask to MDM: %d\n", 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) diag_hsic[index]. in_busy_hsic_write = 0; } } } if (driver->diag_smux_enabled && (remote_proc == QSC) && driver->lcid) { if (payload_size > 0) { err = msm_smux_write(driver->lcid, NULL, user_space_data + token_offset, payload_size); if (err) { pr_err("diag:send mask to MDM err %d", err); diagmem_free(driver, user_space_data, POOL_TYPE_USER); return err; } } } #endif /* send masks to 8k now */ if (!remote_proc) diag_process_hdlc((void *) (user_space_data + token_offset), payload_size); diagmem_free(driver, user_space_data, POOL_TYPE_USER); return 0; } 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 < (2*payload_size) + 3) { pr_err("diag: Dropping packet, HDLC encoded packet payload size crosses buffer limit. Current payload size %d\n", ((2*payload_size) + 3)); driver->dropped_count++; ret = -EBADMSG; 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); if (driver->logging_mode == USB_MODE) 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); if (driver->logging_mode == USB_MODE) 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); if (driver->logging_mode == USB_MODE) 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; } diagmem_free(driver, buf_copy, POOL_TYPE_COPY); mutex_unlock(&driver->diagchar_mutex); 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_DIAGFWD_BRIDGE_CODE static void diag_connect_work_fn(struct work_struct *w) { diagfwd_connect_bridge(1); } static void diag_disconnect_work_fn(struct work_struct *w) { diagfwd_disconnect_bridge(1); } #endif #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_DIAGFWD_BRIDGE_CODE void diagfwd_bridge_fn(int type) { if (type == EXIT) diagfwd_bridge_exit(); } #else inline void diagfwd_bridge_fn(int type) { } #endif static int __init diagchar_init(void) { dev_t dev; int error, ret; pr_debug("diagfwd initializing ..\n"); ret = 0; driver = kzalloc(sizeof(struct diagchar_dev) + 5, GFP_KERNEL); #ifdef CONFIG_DIAGFWD_BRIDGE_CODE diag_bridge = kzalloc(MAX_BRIDGES * sizeof(struct diag_bridge_dev), GFP_KERNEL); if (!diag_bridge) pr_warn("diag: could not allocate memory for bridges\n"); diag_hsic = kzalloc(MAX_HSIC_CH * sizeof(struct diag_hsic_dev), GFP_KERNEL); if (!diag_hsic) pr_warn("diag: could not allocate memory for hsic ch\n"); #endif if (driver) { driver->used = 0; timer_in_progress = 0; driver->debug_flag = 1; driver->dci_state = DIAG_DCI_NO_ERROR; 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_user = itemsize_user; driver->poolsize_user = poolsize_user; 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->socket_process = NULL; driver->callback_process = NULL; driver->mask_check = 0; driver->in_busy_pktdata = 0; mutex_init(&driver->diagchar_mutex); init_waitqueue_head(&driver->wait_q); init_waitqueue_head(&driver->smd_wait_q); INIT_WORK(&(driver->diag_drain_work), diag_drain_work_fn); diag_debugfs_init(); diag_masks_init(); diagfwd_init(); #ifdef CONFIG_DIAGFWD_BRIDGE_CODE spin_lock_init(&driver->hsic_ready_spinlock); diagfwd_bridge_init(HSIC); diagfwd_bridge_init(HSIC_2); /* register HSIC device */ ret = platform_driver_register(&msm_hsic_ch_driver); if (ret) pr_err("diag: could not register HSIC device, ret: %d\n", ret); diagfwd_bridge_init(SMUX); INIT_WORK(&(driver->diag_connect_work), diag_connect_work_fn); INIT_WORK(&(driver->diag_disconnect_work), diag_disconnect_work_fn); #endif diagfwd_cntl_init(); driver->dci_state = diag_dci_init(); diag_sdio_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: diag_debugfs_cleanup(); diagchar_cleanup(); diagfwd_exit(); diagfwd_cntl_exit(); diag_dci_exit(); diag_masks_exit(); diag_sdio_fn(EXIT); diagfwd_bridge_fn(EXIT); return -1; } static void 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_dci_exit(); diag_masks_exit(); diag_sdio_fn(EXIT); diagfwd_bridge_fn(EXIT); diag_debugfs_cleanup(); diagchar_cleanup(); printk(KERN_INFO "done diagchar exit\n"); } module_init(diagchar_init); module_exit(diagchar_exit);