| /* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/uaccess.h> |
| #include <linux/diagchar.h> |
| #include <linux/sched.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| #include <linux/workqueue.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_wakeup.h> |
| #include <linux/spinlock.h> |
| #include <linux/ratelimit.h> |
| #include <linux/reboot.h> |
| #include <asm/current.h> |
| #include <mach/restart.h> |
| #ifdef CONFIG_DIAG_OVER_USB |
| #include <mach/usbdiag.h> |
| #endif |
| #include "diagchar_hdlc.h" |
| #include "diagmem.h" |
| #include "diagchar.h" |
| #include "diagfwd.h" |
| #include "diagfwd_cntl.h" |
| #include "diag_dci.h" |
| |
| static struct timer_list dci_drain_timer; |
| static int dci_timer_in_progress; |
| static struct work_struct dci_data_drain_work; |
| |
| unsigned int dci_max_reg = 100; |
| unsigned int dci_max_clients = 10; |
| unsigned char dci_cumulative_log_mask[DCI_LOG_MASK_SIZE]; |
| unsigned char dci_cumulative_event_mask[DCI_EVENT_MASK_SIZE]; |
| struct mutex dci_log_mask_mutex; |
| struct mutex dci_event_mask_mutex; |
| struct mutex dci_health_mutex; |
| |
| spinlock_t ws_lock; |
| unsigned long ws_lock_flags; |
| |
| /* Number of milliseconds anticipated to process the DCI data */ |
| #define DCI_WAKEUP_TIMEOUT 1 |
| |
| #define DCI_CAN_ADD_BUF_TO_LIST(buf) \ |
| (buf && buf->data && !buf->in_busy && buf->data_len > 0) \ |
| |
| #ifdef CONFIG_DEBUG_FS |
| struct diag_dci_data_info *dci_data_smd; |
| struct mutex dci_stat_mutex; |
| |
| void diag_dci_smd_record_info(int read_bytes, uint8_t ch_type, |
| uint8_t peripheral) |
| { |
| static int curr_dci_data_smd; |
| static unsigned long iteration; |
| struct diag_dci_data_info *temp_data = dci_data_smd; |
| if (!temp_data) |
| return; |
| mutex_lock(&dci_stat_mutex); |
| if (curr_dci_data_smd == DIAG_DCI_DEBUG_CNT) |
| curr_dci_data_smd = 0; |
| temp_data += curr_dci_data_smd; |
| temp_data->iteration = iteration + 1; |
| temp_data->data_size = read_bytes; |
| temp_data->peripheral = peripheral; |
| temp_data->ch_type = ch_type; |
| diag_get_timestamp(temp_data->time_stamp); |
| curr_dci_data_smd++; |
| iteration++; |
| mutex_unlock(&dci_stat_mutex); |
| } |
| #else |
| void diag_dci_smd_record_info(int read_bytes, uint8_t ch_type, |
| uint8_t peripheral) { } |
| #endif |
| |
| static void dci_drain_data(unsigned long data) |
| { |
| queue_work(driver->diag_dci_wq, &dci_data_drain_work); |
| } |
| |
| static void dci_check_drain_timer(void) |
| { |
| if (!dci_timer_in_progress) { |
| dci_timer_in_progress = 1; |
| mod_timer(&dci_drain_timer, jiffies + msecs_to_jiffies(500)); |
| } |
| } |
| |
| static int diag_dci_init_buffer(struct diag_dci_buffer_t *buffer, int type) |
| { |
| if (!buffer || buffer->data) |
| return -EINVAL; |
| |
| switch (type) { |
| case DCI_BUF_PRIMARY: |
| buffer->data = kzalloc(IN_BUF_SIZE, GFP_KERNEL); |
| if (!buffer->data) |
| return -ENOMEM; |
| buffer->capacity = IN_BUF_SIZE; |
| break; |
| case DCI_BUF_SECONDARY: |
| buffer->data = NULL; |
| buffer->capacity = IN_BUF_SIZE; |
| break; |
| case DCI_BUF_CMD: |
| buffer->data = kzalloc(PKT_SIZE, GFP_KERNEL); |
| if (!buffer->data) |
| return -ENOMEM; |
| buffer->capacity = PKT_SIZE; |
| break; |
| default: |
| pr_err("diag: In %s, unknown type %d", __func__, type); |
| return -EINVAL; |
| } |
| |
| buffer->data_len = 0; |
| buffer->in_busy = 0; |
| buffer->buf_type = type; |
| mutex_init(&buffer->data_mutex); |
| |
| return 0; |
| } |
| |
| static inline int diag_dci_check_buffer(struct diag_dci_buffer_t *buf, int len) |
| { |
| if (!buf) |
| return -EINVAL; |
| |
| /* Return 1 if the buffer is not busy and can hold new data */ |
| if ((buf->data_len + len < buf->capacity) && !buf->in_busy) |
| return 1; |
| |
| return 0; |
| } |
| |
| static void dci_add_buffer_to_list(struct diag_dci_client_tbl *client, |
| struct diag_dci_buffer_t *buf) |
| { |
| if (!buf || !client || !buf->data) |
| return; |
| |
| if (buf->in_list || buf->data_len == 0) |
| return; |
| |
| mutex_lock(&client->write_buf_mutex); |
| list_add_tail(&buf->buf_track, &client->list_write_buf); |
| mutex_lock(&buf->data_mutex); |
| buf->in_busy = 1; |
| buf->in_list = 1; |
| mutex_unlock(&buf->data_mutex); |
| mutex_unlock(&client->write_buf_mutex); |
| } |
| |
| static int diag_dci_get_buffer(struct diag_dci_client_tbl *client, |
| int data_source, int len) |
| { |
| struct diag_dci_buffer_t *buf_primary = NULL; |
| struct diag_dci_buffer_t *buf_temp = NULL; |
| struct diag_dci_buffer_t *curr = NULL; |
| |
| if (!client) |
| return -EINVAL; |
| if (len < 0 || len > IN_BUF_SIZE) |
| return -EINVAL; |
| |
| curr = client->buffers[data_source].buf_curr; |
| buf_primary = client->buffers[data_source].buf_primary; |
| |
| if (curr && diag_dci_check_buffer(curr, len) == 1) |
| return 0; |
| |
| dci_add_buffer_to_list(client, curr); |
| client->buffers[data_source].buf_curr = NULL; |
| |
| if (diag_dci_check_buffer(buf_primary, len) == 1) { |
| client->buffers[data_source].buf_curr = buf_primary; |
| return 0; |
| } |
| |
| buf_temp = kzalloc(sizeof(struct diag_dci_buffer_t), GFP_KERNEL); |
| if (!buf_temp) |
| return -EIO; |
| |
| if (!diag_dci_init_buffer(buf_temp, DCI_BUF_SECONDARY)) { |
| buf_temp->data = diagmem_alloc(driver, driver->itemsize_dci, |
| POOL_TYPE_DCI); |
| if (!buf_temp->data) { |
| kfree(buf_temp); |
| buf_temp = NULL; |
| return -ENOMEM; |
| } |
| client->buffers[data_source].buf_curr = buf_temp; |
| return 0; |
| } |
| |
| kfree(buf_temp); |
| buf_temp = NULL; |
| return -EIO; |
| } |
| |
| void diag_dci_wakeup_clients() |
| { |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| |
| /* |
| * Don't wake up the client when there is no pending buffer to |
| * write or when it is writing to user space |
| */ |
| if (!list_empty(&entry->list_write_buf) && !entry->in_service) { |
| mutex_lock(&entry->write_buf_mutex); |
| entry->in_service = 1; |
| mutex_unlock(&entry->write_buf_mutex); |
| diag_update_sleeping_process(entry->client->tgid, |
| DCI_DATA_TYPE); |
| } |
| } |
| } |
| |
| void dci_data_drain_work_fn(struct work_struct *work) |
| { |
| int i; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| struct diag_dci_buf_peripheral_t *proc_buf = NULL; |
| struct diag_dci_buffer_t *buf_temp = NULL; |
| |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| for (i = 0; i < NUM_DCI_PROC; i++) { |
| proc_buf = &entry->buffers[i]; |
| |
| buf_temp = proc_buf->buf_primary; |
| if (DCI_CAN_ADD_BUF_TO_LIST(buf_temp)) |
| dci_add_buffer_to_list(entry, buf_temp); |
| |
| buf_temp = proc_buf->buf_cmd; |
| if (DCI_CAN_ADD_BUF_TO_LIST(buf_temp)) |
| dci_add_buffer_to_list(entry, buf_temp); |
| |
| buf_temp = proc_buf->buf_curr; |
| if (DCI_CAN_ADD_BUF_TO_LIST(buf_temp)) { |
| dci_add_buffer_to_list(entry, buf_temp); |
| mutex_lock(&proc_buf->buf_mutex); |
| proc_buf->buf_curr = NULL; |
| mutex_unlock(&proc_buf->buf_mutex); |
| } |
| } |
| if (!list_empty(&entry->list_write_buf) && !entry->in_service) { |
| mutex_lock(&entry->write_buf_mutex); |
| entry->in_service = 1; |
| mutex_unlock(&entry->write_buf_mutex); |
| diag_update_sleeping_process(entry->client->tgid, |
| DCI_DATA_TYPE); |
| } |
| } |
| dci_timer_in_progress = 0; |
| } |
| |
| /* Process the data read from apps userspace client */ |
| void diag_process_apps_dci_read_data(int data_type, void *buf, int recd_bytes) |
| { |
| uint8_t cmd_code; |
| |
| if (!buf) { |
| pr_err_ratelimited("diag: In %s, Null buf pointer\n", __func__); |
| return; |
| } |
| |
| if (data_type != DATA_TYPE_DCI_LOG && data_type != DATA_TYPE_DCI_EVENT |
| && data_type != DCI_PKT_TYPE) { |
| pr_err("diag: In %s, unsupported data_type: 0x%x\n", |
| __func__, (unsigned int)data_type); |
| return; |
| } |
| |
| cmd_code = *(uint8_t *)buf; |
| |
| switch (cmd_code) { |
| case LOG_CMD_CODE: |
| extract_dci_log(buf, recd_bytes, APPS_DATA); |
| break; |
| case EVENT_CMD_CODE: |
| extract_dci_events(buf, recd_bytes, APPS_DATA); |
| break; |
| case DCI_PKT_RSP_CODE: |
| case DCI_DELAYED_RSP_CODE: |
| extract_dci_pkt_rsp(buf, recd_bytes, APPS_DATA, NULL); |
| break; |
| default: |
| pr_err("diag: In %s, unsupported command code: 0x%x, not log or event\n", |
| __func__, cmd_code); |
| return; |
| |
| } |
| |
| /* wake up all sleeping DCI clients which have some data */ |
| diag_dci_wakeup_clients(); |
| dci_check_drain_timer(); |
| } |
| |
| /* Process the data read from the smd dci channel */ |
| int diag_process_smd_dci_read_data(struct diag_smd_info *smd_info, void *buf, |
| int recd_bytes) |
| { |
| int read_bytes, dci_pkt_len; |
| uint8_t recv_pkt_cmd_code; |
| |
| /* |
| * Release wakeup source when there are no more clients to |
| * process DCI data |
| */ |
| if (driver->num_dci_client == 0) { |
| diag_dci_try_deactivate_wakeup_source(); |
| return 0; |
| } |
| |
| diag_dci_smd_record_info(recd_bytes, (uint8_t)smd_info->type, |
| (uint8_t)smd_info->peripheral); |
| /* Each SMD read can have multiple DCI packets */ |
| read_bytes = 0; |
| while (read_bytes < recd_bytes) { |
| /* read actual length of dci pkt */ |
| dci_pkt_len = *(uint16_t *)(buf+2); |
| |
| /* Check if the length of the current packet is lesser than the |
| * remaining bytes in the received buffer. This includes space |
| * for the Start byte (1), Version byte (1), length bytes (2) |
| * and End byte (1) |
| */ |
| if ((dci_pkt_len+5) > (recd_bytes-read_bytes)) { |
| pr_err("diag: Invalid length in %s, len: %d, dci_pkt_len: %d", |
| __func__, recd_bytes, dci_pkt_len); |
| diag_dci_try_deactivate_wakeup_source(); |
| return 0; |
| } |
| /* process one dci packet */ |
| pr_debug("diag: dci: peripheral = %d bytes read = %d, single dci pkt len = %d\n", |
| smd_info->peripheral, read_bytes, dci_pkt_len); |
| /* print_hex_dump(KERN_DEBUG, "Single DCI packet :", |
| DUMP_PREFIX_ADDRESS, 16, 1, buf, 5 + dci_pkt_len, 1); */ |
| recv_pkt_cmd_code = *(uint8_t *)(buf+4); |
| if (recv_pkt_cmd_code == LOG_CMD_CODE) { |
| /* Don't include the 4 bytes for command code */ |
| extract_dci_log(buf + 4, recd_bytes - 4, |
| smd_info->peripheral); |
| } else if (recv_pkt_cmd_code == EVENT_CMD_CODE) { |
| /* Don't include the 4 bytes for command code */ |
| extract_dci_events(buf + 4, recd_bytes - 4, |
| smd_info->peripheral); |
| } else |
| extract_dci_pkt_rsp(buf + 4, dci_pkt_len, |
| smd_info->peripheral, smd_info); |
| read_bytes += 5 + dci_pkt_len; |
| buf += 5 + dci_pkt_len; /* advance to next DCI pkt */ |
| } |
| |
| /* wake up all sleeping DCI clients which have some data */ |
| diag_dci_wakeup_clients(); |
| dci_check_drain_timer(); |
| diag_dci_try_deactivate_wakeup_source(); |
| return 0; |
| } |
| |
| static inline struct diag_dci_client_tbl *__diag_dci_get_client_entry( |
| int client_id) |
| { |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| if (entry->client->tgid == client_id) |
| return entry; |
| } |
| return NULL; |
| } |
| |
| static inline int __diag_dci_query_log_mask(struct diag_dci_client_tbl *entry, |
| uint16_t log_code) |
| { |
| uint16_t item_num; |
| uint8_t equip_id, *log_mask_ptr, byte_mask; |
| int byte_index, offset; |
| |
| if (!entry) { |
| pr_err("diag: In %s, invalid client entry\n", __func__); |
| return 0; |
| } |
| |
| equip_id = LOG_GET_EQUIP_ID(log_code); |
| item_num = LOG_GET_ITEM_NUM(log_code); |
| byte_index = item_num/8 + 2; |
| byte_mask = 0x01 << (item_num % 8); |
| offset = equip_id * 514; |
| |
| if (offset + byte_index > DCI_LOG_MASK_SIZE) { |
| pr_err("diag: In %s, invalid offset: %d, log_code: %d, byte_index: %d\n", |
| __func__, offset, log_code, byte_index); |
| return 0; |
| } |
| |
| log_mask_ptr = entry->dci_log_mask; |
| log_mask_ptr = log_mask_ptr + offset + byte_index; |
| return ((*log_mask_ptr & byte_mask) == byte_mask) ? 1 : 0; |
| |
| } |
| |
| static inline int __diag_dci_query_event_mask(struct diag_dci_client_tbl *entry, |
| uint16_t event_id) |
| { |
| uint8_t *event_mask_ptr, byte_mask; |
| int byte_index, bit_index; |
| |
| if (!entry) { |
| pr_err("diag: In %s, invalid client entry\n", __func__); |
| return 0; |
| } |
| |
| byte_index = event_id/8; |
| bit_index = event_id % 8; |
| byte_mask = 0x1 << bit_index; |
| |
| if (byte_index > DCI_EVENT_MASK_SIZE) { |
| pr_err("diag: In %s, invalid, event_id: %d, byte_index: %d\n", |
| __func__, event_id, byte_index); |
| return 0; |
| } |
| |
| event_mask_ptr = entry->dci_event_mask; |
| event_mask_ptr = event_mask_ptr + byte_index; |
| return ((*event_mask_ptr & byte_mask) == byte_mask) ? 1 : 0; |
| } |
| |
| static int diag_dci_filter_commands(struct diag_pkt_header_t *header) |
| { |
| if (!header) |
| return -ENOMEM; |
| |
| switch (header->cmd_code) { |
| case 0x7d: /* Msg Mask Configuration */ |
| case 0x73: /* Log Mask Configuration */ |
| case 0x81: /* Event Mask Configuration */ |
| case 0x82: /* Event Mask Change */ |
| case 0x60: /* Event Mask Toggle */ |
| return 1; |
| } |
| |
| if (header->cmd_code == 0x4b && header->subsys_id == 0x12) { |
| switch (header->subsys_cmd_code) { |
| case 0x60: /* Extended Event Mask Config */ |
| case 0x61: /* Extended Msg Mask Config */ |
| case 0x62: /* Extended Log Mask Config */ |
| case 0x20C: /* Set current Preset ID */ |
| case 0x20D: /* Get current Preset ID */ |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static struct dci_pkt_req_entry_t *diag_register_dci_transaction(int uid) |
| { |
| struct dci_pkt_req_entry_t *entry = NULL; |
| entry = kzalloc(sizeof(struct dci_pkt_req_entry_t), GFP_KERNEL); |
| if (!entry) |
| return NULL; |
| |
| mutex_lock(&driver->dci_mutex); |
| driver->dci_tag++; |
| entry->pid = current->tgid; |
| entry->uid = uid; |
| entry->tag = driver->dci_tag; |
| list_add_tail(&entry->track, &driver->dci_req_list); |
| mutex_unlock(&driver->dci_mutex); |
| |
| return entry; |
| } |
| |
| static struct dci_pkt_req_entry_t *diag_dci_get_request_entry(int tag) |
| { |
| struct list_head *start, *temp; |
| struct dci_pkt_req_entry_t *entry = NULL; |
| list_for_each_safe(start, temp, &driver->dci_req_list) { |
| entry = list_entry(start, struct dci_pkt_req_entry_t, track); |
| if (entry->tag == tag) |
| return entry; |
| } |
| return NULL; |
| } |
| |
| static int diag_dci_remove_req_entry(unsigned char *buf, int len, |
| struct dci_pkt_req_entry_t *entry) |
| { |
| uint16_t rsp_count = 0, delayed_rsp_id = 0; |
| if (!buf || len <= 0 || !entry) { |
| pr_err("diag: In %s, invalid input buf: %p, len: %d, entry: %p\n", |
| __func__, buf, len, entry); |
| return -EIO; |
| } |
| |
| /* It is an immediate response, delete it from the table */ |
| if (*buf != 0x80) { |
| list_del(&entry->track); |
| kfree(entry); |
| return 1; |
| } |
| |
| /* It is a delayed response. Check if the length is valid */ |
| if (len < MIN_DELAYED_RSP_LEN) { |
| pr_err("diag: Invalid delayed rsp packet length %d\n", len); |
| return -EINVAL; |
| } |
| |
| /* |
| * If the delayed response id field (uint16_t at byte 8) is 0 then |
| * there is only one response and we can remove the request entry. |
| */ |
| delayed_rsp_id = *(uint16_t *)(buf + 8); |
| if (delayed_rsp_id == 0) { |
| list_del(&entry->track); |
| kfree(entry); |
| return 1; |
| } |
| |
| /* |
| * Check the response count field (uint16 at byte 10). The request |
| * entry can be deleted it it is the last response in the sequence. |
| * It is the last response in the sequence if the response count |
| * is 1 or if the signed bit gets dropped. |
| */ |
| rsp_count = *(uint16_t *)(buf + 10); |
| if (rsp_count > 0 && rsp_count < 0x1000) { |
| list_del(&entry->track); |
| kfree(entry); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| void extract_dci_pkt_rsp(unsigned char *buf, int len, int data_source, |
| struct diag_smd_info *smd_info) |
| { |
| int tag, curr_client_pid = 0; |
| struct diag_dci_client_tbl *entry = NULL; |
| void *temp_buf = NULL; |
| uint8_t dci_cmd_code, cmd_code_len, delete_flag = 0; |
| uint32_t rsp_len = 0; |
| struct diag_dci_buffer_t *rsp_buf = NULL; |
| struct dci_pkt_req_entry_t *req_entry = NULL; |
| unsigned char *temp = buf; |
| |
| if (!buf) { |
| pr_err("diag: Invalid pointer in %s\n", __func__); |
| return; |
| } |
| dci_cmd_code = *(uint8_t *)(temp); |
| if (dci_cmd_code == DCI_PKT_RSP_CODE) { |
| cmd_code_len = sizeof(uint8_t); |
| } else if (dci_cmd_code == DCI_DELAYED_RSP_CODE) { |
| cmd_code_len = sizeof(uint32_t); |
| } else { |
| pr_err("diag: In %s, invalid command code %d\n", __func__, |
| dci_cmd_code); |
| return; |
| } |
| temp += cmd_code_len; |
| tag = *(int *)temp; |
| temp += sizeof(int); |
| |
| /* |
| * The size of the response is (total length) - (length of the command |
| * code, the tag (int) |
| */ |
| rsp_len = len - (cmd_code_len + sizeof(int)); |
| /* |
| * Check if the length embedded in the packet is correct. |
| * Include the start (1), version (1), length (2) and the end |
| * (1) bytes while checking. Total = 5 bytes |
| */ |
| if ((rsp_len == 0) || (rsp_len > (len - 5))) { |
| pr_err("diag: Invalid length in %s, len: %d, rsp_len: %d", |
| __func__, len, rsp_len); |
| return; |
| } |
| |
| req_entry = diag_dci_get_request_entry(tag); |
| if (!req_entry) { |
| pr_err("diag: No matching PID for DCI data\n"); |
| return; |
| } |
| curr_client_pid = req_entry->pid; |
| |
| /* Remove the headers and send only the response to this function */ |
| delete_flag = diag_dci_remove_req_entry(temp, rsp_len, req_entry); |
| if (delete_flag < 0) |
| return; |
| |
| entry = __diag_dci_get_client_entry(curr_client_pid); |
| if (!entry) { |
| pr_err("diag: In %s, couldn't find entry\n", __func__); |
| return; |
| } |
| |
| rsp_buf = entry->buffers[data_source].buf_cmd; |
| |
| mutex_lock(&rsp_buf->data_mutex); |
| /* |
| * Check if we can fit the data in the rsp buffer. The total length of |
| * the rsp is the rsp length (write_len) + DCI_PKT_RSP_TYPE header (int) |
| * + field for length (int) + delete_flag (uint8_t) |
| */ |
| if ((rsp_buf->data_len + 9 + rsp_len) > rsp_buf->capacity) { |
| pr_alert("diag: create capacity for pkt rsp\n"); |
| rsp_buf->capacity += 9 + rsp_len; |
| temp_buf = krealloc(rsp_buf->data, rsp_buf->capacity, |
| GFP_KERNEL); |
| if (!temp_buf) { |
| pr_err("diag: DCI realloc failed\n"); |
| mutex_unlock(&rsp_buf->data_mutex); |
| return; |
| } else { |
| rsp_buf->data = temp_buf; |
| } |
| } |
| |
| /* Fill in packet response header information */ |
| *(int *)(rsp_buf->data + rsp_buf->data_len) = DCI_PKT_RSP_TYPE; |
| rsp_buf->data_len += sizeof(int); |
| /* Packet Length = Response Length + Length of uid field (int) */ |
| *(int *)(rsp_buf->data + rsp_buf->data_len) = rsp_len + sizeof(int); |
| rsp_buf->data_len += sizeof(int); |
| *(uint8_t *)(rsp_buf->data + rsp_buf->data_len) = delete_flag; |
| rsp_buf->data_len += sizeof(uint8_t); |
| *(int *)(rsp_buf->data + rsp_buf->data_len) = req_entry->uid; |
| rsp_buf->data_len += sizeof(int); |
| memcpy(rsp_buf->data + rsp_buf->data_len, temp, rsp_len); |
| rsp_buf->data_len += rsp_len; |
| rsp_buf->data_source = data_source; |
| if (smd_info) |
| smd_info->in_busy_1 = 1; |
| mutex_unlock(&rsp_buf->data_mutex); |
| |
| |
| /* |
| * Add directly to the list for writing responses to the |
| * userspace as these shouldn't be buffered and shouldn't wait |
| * for log and event buffers to be full |
| */ |
| dci_add_buffer_to_list(entry, rsp_buf); |
| } |
| |
| static void copy_dci_event(unsigned char *buf, int len, |
| struct diag_dci_client_tbl *client, int data_source) |
| { |
| struct diag_dci_buffer_t *data_buffer = NULL; |
| struct diag_dci_buf_peripheral_t *proc_buf = NULL; |
| int err = 0, total_len = 0; |
| |
| if (!buf || !client) { |
| pr_err("diag: Invalid pointers in %s", __func__); |
| return; |
| } |
| |
| total_len = sizeof(int) + len; |
| |
| proc_buf = &client->buffers[data_source]; |
| mutex_lock(&proc_buf->buf_mutex); |
| mutex_lock(&proc_buf->health_mutex); |
| err = diag_dci_get_buffer(client, data_source, total_len); |
| if (err) { |
| if (err == -ENOMEM) |
| proc_buf->health.dropped_events++; |
| else |
| pr_err("diag: In %s, invalid packet\n", __func__); |
| mutex_unlock(&proc_buf->health_mutex); |
| mutex_unlock(&proc_buf->buf_mutex); |
| return; |
| } |
| |
| data_buffer = proc_buf->buf_curr; |
| |
| proc_buf->health.received_events++; |
| mutex_unlock(&proc_buf->health_mutex); |
| mutex_unlock(&proc_buf->buf_mutex); |
| |
| mutex_lock(&data_buffer->data_mutex); |
| *(int *)(data_buffer->data + data_buffer->data_len) = DCI_EVENT_TYPE; |
| data_buffer->data_len += sizeof(int); |
| memcpy(data_buffer->data + data_buffer->data_len, buf, len); |
| data_buffer->data_len += len; |
| data_buffer->data_source = data_source; |
| mutex_unlock(&data_buffer->data_mutex); |
| |
| } |
| |
| void extract_dci_events(unsigned char *buf, int len, int data_source) |
| { |
| uint16_t event_id, event_id_packet, length, temp_len; |
| uint8_t payload_len, payload_len_field; |
| uint8_t timestamp[8], timestamp_len; |
| unsigned char event_data[MAX_EVENT_SIZE]; |
| unsigned int total_event_len; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| length = *(uint16_t *)(buf + 1); /* total length of event series */ |
| if (length == 0) { |
| pr_err("diag: Incoming dci event length is invalid\n"); |
| return; |
| } |
| /* Move directly to the start of the event series. 1 byte for |
| * event code and 2 bytes for the length field. |
| */ |
| temp_len = 3; |
| while (temp_len < (length - 1)) { |
| event_id_packet = *(uint16_t *)(buf + temp_len); |
| event_id = event_id_packet & 0x0FFF; /* extract 12 bits */ |
| if (event_id_packet & 0x8000) { |
| /* The packet has the two smallest byte of the |
| * timestamp |
| */ |
| timestamp_len = 2; |
| } else { |
| /* The packet has the full timestamp. The first event |
| * will always have full timestamp. Save it in the |
| * timestamp buffer and use it for subsequent events if |
| * necessary. |
| */ |
| timestamp_len = 8; |
| memcpy(timestamp, buf + temp_len + 2, timestamp_len); |
| } |
| /* 13th and 14th bit represent the payload length */ |
| if (((event_id_packet & 0x6000) >> 13) == 3) { |
| payload_len_field = 1; |
| payload_len = *(uint8_t *) |
| (buf + temp_len + 2 + timestamp_len); |
| if (payload_len < (MAX_EVENT_SIZE - 13)) { |
| /* copy the payload length and the payload */ |
| memcpy(event_data + 12, buf + temp_len + 2 + |
| timestamp_len, 1); |
| memcpy(event_data + 13, buf + temp_len + 2 + |
| timestamp_len + 1, payload_len); |
| } else { |
| pr_err("diag: event > %d, payload_len = %d\n", |
| (MAX_EVENT_SIZE - 13), payload_len); |
| return; |
| } |
| } else { |
| payload_len_field = 0; |
| payload_len = (event_id_packet & 0x6000) >> 13; |
| /* copy the payload */ |
| memcpy(event_data + 12, buf + temp_len + 2 + |
| timestamp_len, payload_len); |
| } |
| |
| /* Before copying the data to userspace, check if we are still |
| * within the buffer limit. This is an error case, don't count |
| * it towards the health statistics. |
| * |
| * Here, the offset of 2 bytes(uint16_t) is for the |
| * event_id_packet length |
| */ |
| temp_len += sizeof(uint16_t) + timestamp_len + |
| payload_len_field + payload_len; |
| if (temp_len > len) { |
| pr_err("diag: Invalid length in %s, len: %d, read: %d", |
| __func__, len, temp_len); |
| return; |
| } |
| |
| /* 2 bytes for the event id & timestamp len is hard coded to 8, |
| as individual events have full timestamp */ |
| *(uint16_t *)(event_data) = 10 + |
| payload_len_field + payload_len; |
| *(uint16_t *)(event_data + 2) = event_id_packet & 0x7FFF; |
| memcpy(event_data + 4, timestamp, 8); |
| /* 2 bytes for the event length field which is added to |
| the event data */ |
| total_event_len = 2 + 10 + payload_len_field + payload_len; |
| /* parse through event mask tbl of each client and check mask */ |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, |
| track); |
| if (__diag_dci_query_event_mask(entry, event_id)) { |
| /* copy to client buffer */ |
| copy_dci_event(event_data, total_event_len, |
| entry, data_source); |
| } |
| } |
| } |
| } |
| |
| static void copy_dci_log(unsigned char *buf, int len, |
| struct diag_dci_client_tbl *client, int data_source) |
| { |
| uint16_t log_length = 0; |
| struct diag_dci_buffer_t *data_buffer = NULL; |
| struct diag_dci_buf_peripheral_t *proc_buf = NULL; |
| int err = 0, total_len = 0; |
| |
| if (!buf || !client) { |
| pr_err("diag: Invalid pointers in %s", __func__); |
| return; |
| } |
| |
| log_length = *(uint16_t *)(buf + 2); |
| if (log_length > USHRT_MAX - 4) { |
| pr_err("diag: Integer overflow in %s, log_len: %d", |
| __func__, log_length); |
| return; |
| } |
| total_len = sizeof(int) + log_length; |
| |
| /* Check if we are within the len. The check should include the |
| * first 4 bytes for the Log code(2) and the length bytes (2) |
| */ |
| if ((log_length + sizeof(uint16_t) + 2) > len) { |
| pr_err("diag: Invalid length in %s, log_len: %d, len: %d", |
| __func__, log_length, len); |
| return; |
| } |
| |
| proc_buf = &client->buffers[data_source]; |
| mutex_lock(&proc_buf->buf_mutex); |
| mutex_lock(&proc_buf->health_mutex); |
| err = diag_dci_get_buffer(client, data_source, total_len); |
| if (err) { |
| if (err == -ENOMEM) |
| proc_buf->health.dropped_logs++; |
| else |
| pr_err("diag: In %s, invalid packet\n", __func__); |
| mutex_unlock(&proc_buf->health_mutex); |
| mutex_unlock(&proc_buf->buf_mutex); |
| return; |
| } |
| |
| data_buffer = proc_buf->buf_curr; |
| proc_buf->health.received_logs++; |
| mutex_unlock(&proc_buf->health_mutex); |
| mutex_unlock(&proc_buf->buf_mutex); |
| |
| mutex_lock(&data_buffer->data_mutex); |
| if (!data_buffer->data) { |
| mutex_unlock(&data_buffer->data_mutex); |
| return; |
| } |
| |
| *(int *)(data_buffer->data + data_buffer->data_len) = DCI_LOG_TYPE; |
| data_buffer->data_len += sizeof(int); |
| memcpy(data_buffer->data + data_buffer->data_len, buf + sizeof(int), |
| log_length); |
| data_buffer->data_len += log_length; |
| data_buffer->data_source = data_source; |
| mutex_unlock(&data_buffer->data_mutex); |
| } |
| |
| void extract_dci_log(unsigned char *buf, int len, int data_source) |
| { |
| uint16_t log_code, read_bytes = 0; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| if (!buf) { |
| pr_err("diag: In %s buffer is NULL\n", __func__); |
| return; |
| } |
| |
| /* The first six bytes for the incoming log packet contains |
| * Command code (2), the length of the packet (2) and the length |
| * of the log (2) |
| */ |
| log_code = *(uint16_t *)(buf + 6); |
| read_bytes += sizeof(uint16_t) + 6; |
| if (read_bytes > len) { |
| pr_err("diag: Invalid length in %s, len: %d, read: %d", |
| __func__, len, read_bytes); |
| return; |
| } |
| |
| /* parse through log mask table of each client and check mask */ |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| if (__diag_dci_query_log_mask(entry, log_code)) { |
| pr_debug("\t log code %x needed by client %d", |
| log_code, entry->client->tgid); |
| /* copy to client buffer */ |
| copy_dci_log(buf, len, entry, data_source); |
| } |
| } |
| } |
| |
| void diag_update_smd_dci_work_fn(struct work_struct *work) |
| { |
| struct diag_smd_info *smd_info = container_of(work, |
| struct diag_smd_info, |
| diag_notify_update_smd_work); |
| int i, j; |
| char dirty_bits[16]; |
| uint8_t *client_log_mask_ptr; |
| uint8_t *log_mask_ptr; |
| int ret; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| /* Update apps and peripheral(s) with the dci log and event masks */ |
| memset(dirty_bits, 0, 16 * sizeof(uint8_t)); |
| |
| /* |
| * From each log entry used by each client, determine |
| * which log entries in the cumulative logs that need |
| * to be updated on the peripheral. |
| */ |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| client_log_mask_ptr = entry->dci_log_mask; |
| for (j = 0; j < 16; j++) { |
| if (*(client_log_mask_ptr+1)) |
| dirty_bits[j] = 1; |
| client_log_mask_ptr += 514; |
| } |
| } |
| |
| mutex_lock(&dci_log_mask_mutex); |
| /* Update the appropriate dirty bits in the cumulative mask */ |
| log_mask_ptr = dci_cumulative_log_mask; |
| for (i = 0; i < 16; i++) { |
| if (dirty_bits[i]) |
| *(log_mask_ptr+1) = dirty_bits[i]; |
| |
| log_mask_ptr += 514; |
| } |
| mutex_unlock(&dci_log_mask_mutex); |
| |
| /* Send updated mask to userspace clients */ |
| diag_update_userspace_clients(DCI_LOG_MASKS_TYPE); |
| /* Send updated log mask to peripherals */ |
| ret = diag_send_dci_log_mask(); |
| |
| /* Send updated event mask to userspace clients */ |
| diag_update_userspace_clients(DCI_EVENT_MASKS_TYPE); |
| /* Send updated event mask to peripheral */ |
| ret = diag_send_dci_event_mask(); |
| |
| smd_info->notify_context = 0; |
| } |
| |
| void diag_dci_notify_client(int peripheral_mask, int data) |
| { |
| int stat; |
| struct siginfo info; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| memset(&info, 0, sizeof(struct siginfo)); |
| info.si_code = SI_QUEUE; |
| info.si_int = (peripheral_mask | data); |
| |
| /* Notify the DCI process that the peripheral DCI Channel is up */ |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| if (entry->client_info.notification_list & peripheral_mask) { |
| info.si_signo = entry->client_info.signal_type; |
| stat = send_sig_info(entry->client_info.signal_type, |
| &info, entry->client); |
| if (stat) |
| pr_err("diag: Err sending dci signal to client, signal data: 0x%x, stat: %d\n", |
| info.si_int, stat); |
| } |
| } |
| } |
| |
| static int diag_send_dci_pkt(struct diag_master_table entry, |
| unsigned char *buf, int len, int tag) |
| { |
| int i, status = DIAG_DCI_NO_ERROR; |
| unsigned int read_len = 0; |
| |
| /* The first 4 bytes is the uid tag and the next four bytes is |
| the minmum packet length of a request packet */ |
| if (len < DCI_PKT_REQ_MIN_LEN) { |
| pr_err("diag: dci: Invalid pkt len %d in %s\n", len, __func__); |
| return -EIO; |
| } |
| if (len > APPS_BUF_SIZE - 10) { |
| pr_err("diag: dci: Invalid payload length in %s\n", __func__); |
| return -EIO; |
| } |
| /* remove UID from user space pkt before sending to peripheral*/ |
| buf = buf + sizeof(int); |
| read_len += sizeof(int); |
| len = len - sizeof(int); |
| mutex_lock(&driver->dci_mutex); |
| /* prepare DCI packet */ |
| driver->apps_dci_buf[0] = CONTROL_CHAR; /* start */ |
| driver->apps_dci_buf[1] = 1; /* version */ |
| *(uint16_t *)(driver->apps_dci_buf + 2) = len + 4 + 1; /* length */ |
| driver->apps_dci_buf[4] = DCI_PKT_RSP_CODE; |
| *(int *)(driver->apps_dci_buf + 5) = tag; |
| for (i = 0; i < len; i++) |
| driver->apps_dci_buf[i+9] = *(buf+i); |
| read_len += len; |
| driver->apps_dci_buf[9+len] = CONTROL_CHAR; /* end */ |
| if ((read_len + 9) >= USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length while forming dci pkt in %s", |
| __func__); |
| mutex_unlock(&driver->dci_mutex); |
| return -EIO; |
| } |
| /* This command is registered locally on the Apps */ |
| if (entry.client_id == APPS_DATA) { |
| driver->dci_pkt_length = len + 10; |
| diag_update_pkt_buffer(driver->apps_dci_buf, DCI_PKT_TYPE); |
| diag_update_sleeping_process(entry.process_id, DCI_PKT_TYPE); |
| mutex_unlock(&driver->dci_mutex); |
| return DIAG_DCI_NO_ERROR; |
| } |
| |
| for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) |
| if (entry.client_id == i) { |
| status = 1; |
| break; |
| } |
| |
| if (status) { |
| status = diag_dci_write_proc(entry.client_id, |
| DIAG_DATA_TYPE, |
| driver->apps_dci_buf, |
| len + 10); |
| } else { |
| pr_err("diag: Cannot send packet to peripheral %d", |
| entry.client_id); |
| status = DIAG_DCI_SEND_DATA_FAIL; |
| } |
| mutex_unlock(&driver->dci_mutex); |
| return status; |
| } |
| |
| static int diag_dci_process_apps_pkt(struct diag_pkt_header_t *pkt_header, |
| unsigned char *req_buf, int tag) |
| { |
| uint8_t cmd_code, subsys_id, i, goto_download = 0; |
| uint8_t header_len = sizeof(struct diag_dci_pkt_header_t); |
| uint16_t ss_cmd_code; |
| uint32_t write_len = 0; |
| unsigned char *dest_buf = driver->apps_dci_buf; |
| unsigned char *payload_ptr = driver->apps_dci_buf + header_len; |
| struct diag_dci_pkt_header_t dci_header; |
| |
| if (!pkt_header || !req_buf || tag < 0) |
| return -EIO; |
| |
| cmd_code = pkt_header->cmd_code; |
| subsys_id = pkt_header->subsys_id; |
| ss_cmd_code = pkt_header->subsys_cmd_code; |
| |
| if (cmd_code == DIAG_CMD_DOWNLOAD) { |
| *payload_ptr = DIAG_CMD_DOWNLOAD; |
| write_len = sizeof(uint8_t); |
| goto_download = 1; |
| goto fill_buffer; |
| } else if (cmd_code == DIAG_CMD_VERSION) { |
| if (chk_polling_response()) { |
| for (i = 0; i < 55; i++, write_len++, payload_ptr++) |
| *(payload_ptr) = 0; |
| goto fill_buffer; |
| } |
| } else if (cmd_code == DIAG_CMD_EXT_BUILD) { |
| if (chk_polling_response()) { |
| *payload_ptr = DIAG_CMD_EXT_BUILD; |
| write_len = sizeof(uint8_t); |
| payload_ptr += sizeof(uint8_t); |
| for (i = 0; i < 8; i++, write_len++, payload_ptr++) |
| *(payload_ptr) = 0; |
| *(int *)(payload_ptr) = chk_config_get_id(); |
| write_len += sizeof(int); |
| goto fill_buffer; |
| } |
| } else if (cmd_code == DIAG_CMD_LOG_ON_DMND) { |
| if (driver->log_on_demand_support) { |
| *payload_ptr = DIAG_CMD_LOG_ON_DMND; |
| write_len = sizeof(uint8_t); |
| payload_ptr += sizeof(uint8_t); |
| *(uint16_t *)(payload_ptr) = *(uint16_t *)(req_buf + 1); |
| write_len += sizeof(uint16_t); |
| payload_ptr += sizeof(uint16_t); |
| *payload_ptr = 0x1; /* Unknown */ |
| write_len += sizeof(uint8_t); |
| goto fill_buffer; |
| } |
| } else if (cmd_code != DIAG_CMD_DIAG_SUBSYS) { |
| return DIAG_DCI_TABLE_ERR; |
| } |
| |
| if (subsys_id == DIAG_SS_DIAG) { |
| if (ss_cmd_code == DIAG_DIAG_MAX_PKT_SZ) { |
| memcpy(payload_ptr, pkt_header, |
| sizeof(struct diag_pkt_header_t)); |
| write_len = sizeof(struct diag_pkt_header_t); |
| *(uint32_t *)(payload_ptr + write_len) = PKT_SIZE; |
| write_len += sizeof(uint32_t); |
| } else if (ss_cmd_code == DIAG_DIAG_STM) { |
| write_len = diag_process_stm_cmd(req_buf, payload_ptr); |
| } |
| } else if (subsys_id == DIAG_SS_PARAMS) { |
| if (ss_cmd_code == DIAG_DIAG_POLL) { |
| if (chk_polling_response()) { |
| memcpy(payload_ptr, pkt_header, |
| sizeof(struct diag_pkt_header_t)); |
| write_len = sizeof(struct diag_pkt_header_t); |
| payload_ptr += write_len; |
| for (i = 0; i < 12; i++, write_len++) { |
| *(payload_ptr) = 0; |
| payload_ptr++; |
| } |
| } |
| } else if (ss_cmd_code == DIAG_DEL_RSP_WRAP) { |
| memcpy(payload_ptr, pkt_header, |
| sizeof(struct diag_pkt_header_t)); |
| write_len = sizeof(struct diag_pkt_header_t); |
| *(int *)(payload_ptr + write_len) = wrap_enabled; |
| write_len += sizeof(int); |
| } else if (ss_cmd_code == DIAG_DEL_RSP_WRAP_CNT) { |
| wrap_enabled = true; |
| memcpy(payload_ptr, pkt_header, |
| sizeof(struct diag_pkt_header_t)); |
| write_len = sizeof(struct diag_pkt_header_t); |
| *(uint16_t *)(payload_ptr + write_len) = wrap_count; |
| write_len += sizeof(uint16_t); |
| } |
| } |
| |
| fill_buffer: |
| if (write_len > 0) { |
| /* Check if we are within the range of the buffer*/ |
| if (write_len + header_len > PKT_SIZE) { |
| pr_err("diag: In %s, invalid length %d\n", __func__, |
| write_len + header_len); |
| return -ENOMEM; |
| } |
| dci_header.start = CONTROL_CHAR; |
| dci_header.version = 1; |
| /* |
| * Length of the rsp pkt = actual data len + pkt rsp code |
| * (uint8_t) + tag (int) |
| */ |
| dci_header.len = write_len + sizeof(uint8_t) + sizeof(int); |
| dci_header.pkt_code = DCI_PKT_RSP_CODE; |
| dci_header.tag = tag; |
| driver->in_busy_dcipktdata = 1; |
| memcpy(dest_buf, &dci_header, header_len); |
| diag_process_apps_dci_read_data(DCI_PKT_TYPE, dest_buf + 4, |
| dci_header.len); |
| driver->in_busy_dcipktdata = 0; |
| |
| if (goto_download) { |
| /* |
| * Sleep for sometime so that the response reaches the |
| * client. The value 5000 empirically as an optimum |
| * time for the response to reach the client. |
| */ |
| usleep_range(5000, 5100); |
| /* call download API */ |
| msm_set_restart_mode(RESTART_DLOAD); |
| pr_alert("diag: download mode set, Rebooting SoC..\n"); |
| kernel_restart(NULL); |
| } |
| return DIAG_DCI_NO_ERROR; |
| } |
| |
| return DIAG_DCI_TABLE_ERR; |
| } |
| |
| static int diag_process_dci_pkt_rsp(unsigned char *buf, int len) |
| { |
| int req_uid, ret = DIAG_DCI_TABLE_ERR, i; |
| struct diag_pkt_header_t *header = NULL; |
| unsigned char *temp = buf; |
| unsigned char *req_buf = NULL; |
| uint8_t retry_count = 0, max_retries = 3, found = 0; |
| uint32_t read_len = 0; |
| struct diag_master_table entry; |
| struct dci_pkt_req_entry_t *req_entry = NULL; |
| |
| if (!buf) |
| return -EIO; |
| |
| if (len < DCI_PKT_REQ_MIN_LEN || len > USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length %d len in %s", len, __func__); |
| return -EIO; |
| } |
| |
| req_uid = *(int *)temp; /* UID of the request */ |
| temp += sizeof(int); |
| req_buf = temp; /* Start of the Request */ |
| header = (struct diag_pkt_header_t *)temp; |
| temp += sizeof(struct diag_pkt_header_t); |
| read_len = sizeof(int) + sizeof(struct diag_pkt_header_t); |
| if (read_len >= USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length in %s\n", __func__); |
| return -EIO; |
| } |
| |
| /* Check if the command is allowed on DCI */ |
| if (diag_dci_filter_commands(header)) { |
| pr_debug("diag: command not supported %d %d %d", |
| header->cmd_code, header->subsys_id, |
| header->subsys_cmd_code); |
| return DIAG_DCI_SEND_DATA_FAIL; |
| } |
| |
| /* |
| * Previous packet is yet to be consumed by the client. Wait |
| * till the buffer is free. |
| */ |
| while (retry_count < max_retries) { |
| retry_count++; |
| if (driver->in_busy_dcipktdata) |
| usleep_range(10000, 10100); |
| else |
| break; |
| } |
| /* The buffer is still busy */ |
| if (driver->in_busy_dcipktdata) { |
| pr_err("diag: In %s, apps dci buffer is still busy. Dropping packet\n", |
| __func__); |
| return -EAGAIN; |
| } |
| |
| /* Register this new DCI packet */ |
| req_entry = diag_register_dci_transaction(req_uid); |
| if (!req_entry) { |
| pr_alert("diag: registering new DCI transaction failed\n"); |
| return DIAG_DCI_NO_REG; |
| } |
| |
| /* Check if it is a dedicated Apps command */ |
| ret = diag_dci_process_apps_pkt(header, req_buf, req_entry->tag); |
| if (ret == DIAG_DCI_NO_ERROR || ret < 0) |
| return ret; |
| |
| /* Check the registration table for command entries */ |
| for (i = 0; i < diag_max_reg && !found; i++) { |
| entry = driver->table[i]; |
| if (entry.process_id == NO_PROCESS) |
| continue; |
| if (entry.cmd_code == header->cmd_code && |
| entry.subsys_id == header->subsys_id && |
| entry.cmd_code_lo <= header->subsys_cmd_code && |
| entry.cmd_code_hi >= header->subsys_cmd_code) { |
| ret = diag_send_dci_pkt(entry, buf, len, |
| req_entry->tag); |
| found = 1; |
| } else if (entry.cmd_code == 255 && header->cmd_code == 75) { |
| if (entry.subsys_id == header->subsys_id && |
| entry.cmd_code_lo <= header->subsys_cmd_code && |
| entry.cmd_code_hi >= header->subsys_cmd_code) { |
| ret = diag_send_dci_pkt(entry, buf, len, |
| req_entry->tag); |
| found = 1; |
| } |
| } else if (entry.cmd_code == 255 && entry.subsys_id == 255) { |
| if (entry.cmd_code_lo <= header->cmd_code && |
| entry.cmd_code_hi >= header->cmd_code) { |
| /* |
| * If its a Mode reset command, make sure it is |
| * registered on the Apps Processor |
| */ |
| if (entry.cmd_code_lo == MODE_CMD && |
| entry.cmd_code_hi == MODE_CMD) |
| if (entry.client_id != APPS_DATA) |
| continue; |
| ret = diag_send_dci_pkt(entry, buf, len, |
| req_entry->tag); |
| found = 1; |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| int diag_process_dci_transaction(unsigned char *buf, int len) |
| { |
| unsigned char *temp = buf; |
| uint16_t log_code, item_num; |
| int ret = -1, found = 0; |
| int count, set_mask, num_codes, bit_index, event_id, offset = 0; |
| unsigned int byte_index, read_len = 0; |
| uint8_t equip_id, *log_mask_ptr, *head_log_mask_ptr, byte_mask; |
| uint8_t *event_mask_ptr; |
| struct diag_dci_client_tbl *dci_entry = NULL; |
| |
| if (!temp) { |
| pr_err("diag: Invalid buffer in %s\n", __func__); |
| return -ENOMEM; |
| } |
| |
| /* This is Pkt request/response transaction */ |
| if (*(int *)temp > 0) { |
| return diag_process_dci_pkt_rsp(buf, len); |
| } else if (*(int *)temp == DCI_LOG_TYPE) { |
| /* Minimum length of a log mask config is 12 + 2 bytes for |
| atleast one log code to be set or reset */ |
| if (len < DCI_LOG_CON_MIN_LEN || len > USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length in %s\n", __func__); |
| return -EIO; |
| } |
| /* find client table entry */ |
| dci_entry = diag_dci_get_client_entry(); |
| if (!dci_entry) { |
| pr_err("diag: In %s, invalid client\n", __func__); |
| return ret; |
| } |
| |
| /* Extract each log code and put in client table */ |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| set_mask = *(int *)temp; |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| num_codes = *(int *)temp; |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| |
| if (num_codes == 0 || (num_codes >= (USER_SPACE_DATA - 8)/2)) { |
| pr_err("diag: dci: Invalid number of log codes %d\n", |
| num_codes); |
| return -EIO; |
| } |
| |
| head_log_mask_ptr = dci_entry->dci_log_mask; |
| if (!head_log_mask_ptr) { |
| pr_err("diag: dci: Invalid Log mask pointer in %s\n", |
| __func__); |
| return -ENOMEM; |
| } |
| pr_debug("diag: head of dci log mask %p\n", head_log_mask_ptr); |
| count = 0; /* iterator for extracting log codes */ |
| while (count < num_codes) { |
| if (read_len >= USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length for log type in %s", |
| __func__); |
| return -EIO; |
| } |
| log_code = *(uint16_t *)temp; |
| equip_id = LOG_GET_EQUIP_ID(log_code); |
| item_num = LOG_GET_ITEM_NUM(log_code); |
| byte_index = item_num/8 + 2; |
| if (byte_index >= (DCI_MAX_ITEMS_PER_LOG_CODE+2)) { |
| pr_err("diag: dci: Log type, invalid byte index\n"); |
| return ret; |
| } |
| byte_mask = 0x01 << (item_num % 8); |
| /* |
| * Parse through log mask table and find |
| * relevant range |
| */ |
| log_mask_ptr = head_log_mask_ptr; |
| found = 0; |
| offset = 0; |
| while (log_mask_ptr && (offset < DCI_LOG_MASK_SIZE)) { |
| if (*log_mask_ptr == equip_id) { |
| found = 1; |
| pr_debug("diag: find equip id = %x at %p\n", |
| equip_id, log_mask_ptr); |
| break; |
| } else { |
| pr_debug("diag: did not find equip id = %x at %p\n", |
| equip_id, log_mask_ptr); |
| log_mask_ptr += 514; |
| offset += 514; |
| } |
| } |
| if (!found) { |
| pr_err("diag: dci equip id not found\n"); |
| return ret; |
| } |
| *(log_mask_ptr+1) = 1; /* set the dirty byte */ |
| log_mask_ptr = log_mask_ptr + byte_index; |
| if (set_mask) |
| *log_mask_ptr |= byte_mask; |
| else |
| *log_mask_ptr &= ~byte_mask; |
| /* add to cumulative mask */ |
| update_dci_cumulative_log_mask( |
| offset, byte_index, |
| byte_mask); |
| temp += 2; |
| read_len += 2; |
| count++; |
| ret = DIAG_DCI_NO_ERROR; |
| } |
| /* send updated mask to userspace clients */ |
| diag_update_userspace_clients(DCI_LOG_MASKS_TYPE); |
| /* send updated mask to peripherals */ |
| ret = diag_send_dci_log_mask(); |
| } else if (*(int *)temp == DCI_EVENT_TYPE) { |
| /* Minimum length of a event mask config is 12 + 4 bytes for |
| atleast one event id to be set or reset. */ |
| if (len < DCI_EVENT_CON_MIN_LEN || len > USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length in %s\n", __func__); |
| return -EIO; |
| } |
| /* find client table entry */ |
| dci_entry = diag_dci_get_client_entry(); |
| if (!dci_entry) { |
| pr_err("diag: In %s, invalid client\n", __func__); |
| return ret; |
| } |
| /* Extract each log code and put in client table */ |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| set_mask = *(int *)temp; |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| num_codes = *(int *)temp; |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| |
| /* Check for positive number of event ids. Also, the number of |
| event ids should fit in the buffer along with set_mask and |
| num_codes which are 4 bytes each */ |
| if (num_codes == 0 || (num_codes >= (USER_SPACE_DATA - 8)/2)) { |
| pr_err("diag: dci: Invalid number of event ids %d\n", |
| num_codes); |
| return -EIO; |
| } |
| |
| event_mask_ptr = dci_entry->dci_event_mask; |
| if (!event_mask_ptr) { |
| pr_err("diag: dci: Invalid event mask pointer in %s\n", |
| __func__); |
| return -ENOMEM; |
| } |
| pr_debug("diag: head of dci event mask %p\n", event_mask_ptr); |
| count = 0; /* iterator for extracting log codes */ |
| while (count < num_codes) { |
| if (read_len >= USER_SPACE_DATA) { |
| pr_err("diag: dci: Invalid length for event type in %s", |
| __func__); |
| return -EIO; |
| } |
| event_id = *(int *)temp; |
| byte_index = event_id/8; |
| if (byte_index >= DCI_EVENT_MASK_SIZE) { |
| pr_err("diag: dci: Event type, invalid byte index\n"); |
| return ret; |
| } |
| bit_index = event_id % 8; |
| byte_mask = 0x1 << bit_index; |
| /* |
| * Parse through event mask table and set |
| * relevant byte & bit combination |
| */ |
| if (set_mask) |
| *(event_mask_ptr + byte_index) |= byte_mask; |
| else |
| *(event_mask_ptr + byte_index) &= ~byte_mask; |
| /* add to cumulative mask */ |
| update_dci_cumulative_event_mask(byte_index, byte_mask); |
| temp += sizeof(int); |
| read_len += sizeof(int); |
| count++; |
| ret = DIAG_DCI_NO_ERROR; |
| } |
| /* send updated mask to userspace clients */ |
| diag_update_userspace_clients(DCI_EVENT_MASKS_TYPE); |
| /* send updated mask to peripherals */ |
| ret = diag_send_dci_event_mask(); |
| } else { |
| pr_alert("diag: Incorrect DCI transaction\n"); |
| } |
| return ret; |
| } |
| |
| |
| struct diag_dci_client_tbl *diag_dci_get_client_entry() |
| { |
| return __diag_dci_get_client_entry(current->tgid); |
| } |
| |
| void update_dci_cumulative_event_mask(int offset, uint8_t byte_mask) |
| { |
| uint8_t *event_mask_ptr; |
| uint8_t *update_ptr = dci_cumulative_event_mask; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| bool is_set = false; |
| |
| mutex_lock(&dci_event_mask_mutex); |
| update_ptr += offset; |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| event_mask_ptr = entry->dci_event_mask; |
| event_mask_ptr += offset; |
| if ((*event_mask_ptr & byte_mask) == byte_mask) { |
| is_set = true; |
| /* break even if one client has the event mask set */ |
| break; |
| } |
| } |
| if (is_set == false) |
| *update_ptr &= ~byte_mask; |
| else |
| *update_ptr |= byte_mask; |
| mutex_unlock(&dci_event_mask_mutex); |
| } |
| |
| void diag_dci_invalidate_cumulative_event_mask() |
| { |
| int i = 0; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| uint8_t *update_ptr, *event_mask_ptr; |
| update_ptr = dci_cumulative_event_mask; |
| |
| mutex_lock(&dci_event_mask_mutex); |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| event_mask_ptr = entry->dci_event_mask; |
| for (i = 0; i < DCI_EVENT_MASK_SIZE; i++) |
| *(update_ptr+i) |= *(event_mask_ptr+i); |
| } |
| mutex_unlock(&dci_event_mask_mutex); |
| } |
| |
| int diag_send_dci_event_mask() |
| { |
| void *buf = driver->buf_event_mask_update; |
| int header_size = sizeof(struct diag_ctrl_event_mask); |
| int ret = DIAG_DCI_NO_ERROR, err = DIAG_DCI_NO_ERROR, i; |
| |
| mutex_lock(&driver->diag_cntl_mutex); |
| /* send event mask update */ |
| driver->event_mask->cmd_type = DIAG_CTRL_MSG_EVENT_MASK; |
| driver->event_mask->data_len = 7 + DCI_EVENT_MASK_SIZE; |
| driver->event_mask->stream_id = DCI_MASK_STREAM; |
| driver->event_mask->status = 3; /* status for valid mask */ |
| driver->event_mask->event_config = 0; /* event config */ |
| driver->event_mask->event_mask_size = DCI_EVENT_MASK_SIZE; |
| for (i = 0; i < DCI_EVENT_MASK_SIZE; i++) { |
| if (dci_cumulative_event_mask[i] != 0) { |
| driver->event_mask->event_config = 1; |
| break; |
| } |
| } |
| memcpy(buf, driver->event_mask, header_size); |
| memcpy(buf+header_size, dci_cumulative_event_mask, DCI_EVENT_MASK_SIZE); |
| for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) { |
| /* |
| * Don't send to peripheral if its regular channel |
| * is down. It may also mean that the peripheral doesn't |
| * support DCI. |
| */ |
| if (!driver->smd_dci[i].ch) |
| continue; |
| err = diag_dci_write_proc(i, DIAG_CNTL_TYPE, buf, |
| header_size + DCI_EVENT_MASK_SIZE); |
| if (err != DIAG_DCI_NO_ERROR) |
| ret = DIAG_DCI_SEND_DATA_FAIL; |
| } |
| mutex_unlock(&driver->diag_cntl_mutex); |
| |
| return ret; |
| } |
| |
| void update_dci_cumulative_log_mask(int offset, unsigned int byte_index, |
| uint8_t byte_mask) |
| { |
| int i; |
| uint8_t *update_ptr = dci_cumulative_log_mask; |
| uint8_t *log_mask_ptr; |
| bool is_set = false; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| mutex_lock(&dci_log_mask_mutex); |
| *update_ptr = 0; |
| /* set the equipment IDs */ |
| for (i = 0; i < 16; i++) |
| *(update_ptr + (i*514)) = i; |
| |
| update_ptr += offset; |
| /* update the dirty bit */ |
| *(update_ptr+1) = 1; |
| update_ptr = update_ptr + byte_index; |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| log_mask_ptr = entry->dci_log_mask; |
| log_mask_ptr = log_mask_ptr + offset + byte_index; |
| if ((*log_mask_ptr & byte_mask) == byte_mask) { |
| is_set = true; |
| /* break even if one client has the log mask set */ |
| break; |
| } |
| } |
| |
| if (is_set == false) |
| *update_ptr &= ~byte_mask; |
| else |
| *update_ptr |= byte_mask; |
| mutex_unlock(&dci_log_mask_mutex); |
| } |
| |
| void diag_dci_invalidate_cumulative_log_mask() |
| { |
| int i = 0; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| uint8_t *update_ptr, *log_mask_ptr; |
| update_ptr = dci_cumulative_log_mask; |
| |
| mutex_lock(&dci_log_mask_mutex); |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| log_mask_ptr = entry->dci_log_mask; |
| for (i = 0; i < DCI_LOG_MASK_SIZE; i++) |
| *(update_ptr+i) |= *(log_mask_ptr+i); |
| } |
| mutex_unlock(&dci_log_mask_mutex); |
| } |
| |
| int diag_send_dci_log_mask() |
| { |
| void *buf = driver->buf_log_mask_update; |
| int header_size = sizeof(struct diag_ctrl_log_mask); |
| uint8_t *log_mask_ptr = dci_cumulative_log_mask; |
| int i, j, ret = DIAG_DCI_NO_ERROR, err = DIAG_DCI_NO_ERROR; |
| int updated; |
| |
| mutex_lock(&driver->diag_cntl_mutex); |
| for (i = 0; i < 16; i++) { |
| updated = 1; |
| driver->log_mask->cmd_type = DIAG_CTRL_MSG_LOG_MASK; |
| driver->log_mask->num_items = 512; |
| driver->log_mask->data_len = 11 + 512; |
| driver->log_mask->stream_id = DCI_MASK_STREAM; |
| driver->log_mask->status = 3; /* status for valid mask */ |
| driver->log_mask->equip_id = *log_mask_ptr; |
| driver->log_mask->log_mask_size = 512; |
| memcpy(buf, driver->log_mask, header_size); |
| memcpy(buf+header_size, log_mask_ptr+2, 512); |
| /* if dirty byte is set and channel is valid */ |
| for (j = 0; j < NUM_SMD_DCI_CHANNELS; j++) { |
| /* |
| * Don't send to peripheral if its regular channel |
| * is down. It may also mean that the peripheral |
| * doesn't support DCI. |
| */ |
| if (!driver->smd_dci[j].ch) |
| continue; |
| |
| if (!(*(log_mask_ptr+1))) |
| continue; |
| err = diag_dci_write_proc(j, DIAG_CNTL_TYPE, buf, |
| header_size + DCI_MAX_ITEMS_PER_LOG_CODE); |
| if (err != DIAG_DCI_NO_ERROR) { |
| updated = 0; |
| ret = DIAG_DCI_SEND_DATA_FAIL; |
| } |
| } |
| if (updated) |
| *(log_mask_ptr+1) = 0; /* clear dirty byte */ |
| log_mask_ptr += 514; |
| } |
| mutex_unlock(&driver->diag_cntl_mutex); |
| |
| return ret; |
| } |
| |
| void create_dci_log_mask_tbl(unsigned char *tbl_buf) |
| { |
| uint8_t i; int count = 0; |
| |
| if (!tbl_buf) |
| return; |
| |
| /* create hard coded table for log mask with 16 categories */ |
| for (i = 0; i < 16; i++) { |
| *(uint8_t *)tbl_buf = i; |
| pr_debug("diag: put value %x at %p\n", i, tbl_buf); |
| memset(tbl_buf+1, 0, 513); /* set dirty bit as 0 */ |
| tbl_buf += 514; |
| count += 514; |
| } |
| } |
| |
| void create_dci_event_mask_tbl(unsigned char *tbl_buf) |
| { |
| memset(tbl_buf, 0, 512); |
| } |
| |
| static int diag_dci_probe(struct platform_device *pdev) |
| { |
| int err = 0; |
| int index; |
| |
| if (pdev->id == SMD_APPS_MODEM) { |
| index = MODEM_DATA; |
| err = smd_named_open_on_edge("DIAG_2", |
| SMD_APPS_MODEM, |
| &driver->smd_dci[index].ch, |
| &driver->smd_dci[index], |
| diag_smd_notify); |
| driver->smd_dci[index].ch_save = |
| driver->smd_dci[index].ch; |
| if (err) |
| pr_err("diag: In %s, cannot open DCI Modem port, Id = %d, err: %d\n", |
| __func__, pdev->id, err); |
| } |
| |
| return err; |
| } |
| |
| static int diag_dci_cmd_probe(struct platform_device *pdev) |
| { |
| int err = 0; |
| int index; |
| |
| if (pdev->id == SMD_APPS_MODEM) { |
| index = MODEM_DATA; |
| err = smd_named_open_on_edge("DIAG_2_CMD", |
| pdev->id, |
| &driver->smd_dci_cmd[index].ch, |
| &driver->smd_dci_cmd[index], |
| diag_smd_notify); |
| driver->smd_dci_cmd[index].ch_save = |
| driver->smd_dci_cmd[index].ch; |
| if (err) |
| pr_err("diag: In %s, cannot open DCI Modem CMD port, Id = %d, err: %d\n", |
| __func__, pdev->id, err); |
| } |
| |
| return err; |
| } |
| |
| static int diag_dci_runtime_suspend(struct device *dev) |
| { |
| dev_dbg(dev, "pm_runtime: suspending...\n"); |
| return 0; |
| } |
| |
| static int diag_dci_runtime_resume(struct device *dev) |
| { |
| dev_dbg(dev, "pm_runtime: resuming...\n"); |
| return 0; |
| } |
| |
| static const struct dev_pm_ops diag_dci_dev_pm_ops = { |
| .runtime_suspend = diag_dci_runtime_suspend, |
| .runtime_resume = diag_dci_runtime_resume, |
| }; |
| |
| struct platform_driver msm_diag_dci_driver = { |
| .probe = diag_dci_probe, |
| .driver = { |
| .name = "DIAG_2", |
| .owner = THIS_MODULE, |
| .pm = &diag_dci_dev_pm_ops, |
| }, |
| }; |
| |
| struct platform_driver msm_diag_dci_cmd_driver = { |
| .probe = diag_dci_cmd_probe, |
| .driver = { |
| .name = "DIAG_2_CMD", |
| .owner = THIS_MODULE, |
| .pm = &diag_dci_dev_pm_ops, |
| }, |
| }; |
| |
| int diag_dci_init(void) |
| { |
| int success = 0; |
| int i; |
| |
| driver->dci_tag = 0; |
| driver->dci_client_id = 0; |
| driver->num_dci_client = 0; |
| mutex_init(&driver->dci_mutex); |
| mutex_init(&dci_log_mask_mutex); |
| mutex_init(&dci_event_mask_mutex); |
| mutex_init(&dci_health_mutex); |
| spin_lock_init(&ws_lock); |
| |
| for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) { |
| success = diag_smd_constructor(&driver->smd_dci[i], i, |
| SMD_DCI_TYPE); |
| if (!success) |
| goto err; |
| } |
| |
| if (driver->supports_separate_cmdrsp) { |
| for (i = 0; i < NUM_SMD_DCI_CMD_CHANNELS; i++) { |
| success = diag_smd_constructor(&driver->smd_dci_cmd[i], |
| i, SMD_DCI_CMD_TYPE); |
| if (!success) |
| goto err; |
| } |
| } |
| |
| if (driver->apps_dci_buf == NULL) { |
| driver->apps_dci_buf = kzalloc(APPS_BUF_SIZE, GFP_KERNEL); |
| if (driver->apps_dci_buf == NULL) |
| goto err; |
| } |
| INIT_LIST_HEAD(&driver->dci_client_list); |
| INIT_LIST_HEAD(&driver->dci_req_list); |
| |
| driver->diag_dci_wq = create_singlethread_workqueue("diag_dci_wq"); |
| INIT_WORK(&dci_data_drain_work, dci_data_drain_work_fn); |
| success = platform_driver_register(&msm_diag_dci_driver); |
| if (success) { |
| pr_err("diag: Could not register DCI driver\n"); |
| goto err; |
| } |
| if (driver->supports_separate_cmdrsp) { |
| success = platform_driver_register(&msm_diag_dci_cmd_driver); |
| if (success) { |
| pr_err("diag: Could not register DCI cmd driver\n"); |
| goto err; |
| } |
| } |
| setup_timer(&dci_drain_timer, dci_drain_data, 0); |
| return DIAG_DCI_NO_ERROR; |
| err: |
| pr_err("diag: Could not initialize diag DCI buffers"); |
| kfree(driver->apps_dci_buf); |
| for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) |
| diag_smd_destructor(&driver->smd_dci[i]); |
| |
| if (driver->supports_separate_cmdrsp) |
| for (i = 0; i < NUM_SMD_DCI_CMD_CHANNELS; i++) |
| diag_smd_destructor(&driver->smd_dci_cmd[i]); |
| |
| if (driver->diag_dci_wq) |
| destroy_workqueue(driver->diag_dci_wq); |
| mutex_destroy(&driver->dci_mutex); |
| mutex_destroy(&dci_log_mask_mutex); |
| mutex_destroy(&dci_event_mask_mutex); |
| mutex_destroy(&dci_health_mutex); |
| return DIAG_DCI_NO_REG; |
| } |
| |
| void diag_dci_exit(void) |
| { |
| int i; |
| |
| for (i = 0; i < NUM_SMD_DCI_CHANNELS; i++) |
| diag_smd_destructor(&driver->smd_dci[i]); |
| |
| platform_driver_unregister(&msm_diag_dci_driver); |
| |
| if (driver->supports_separate_cmdrsp) { |
| for (i = 0; i < NUM_SMD_DCI_CMD_CHANNELS; i++) |
| diag_smd_destructor(&driver->smd_dci_cmd[i]); |
| |
| platform_driver_unregister(&msm_diag_dci_cmd_driver); |
| } |
| kfree(driver->apps_dci_buf); |
| mutex_destroy(&driver->dci_mutex); |
| mutex_destroy(&dci_log_mask_mutex); |
| mutex_destroy(&dci_event_mask_mutex); |
| mutex_destroy(&dci_health_mutex); |
| destroy_workqueue(driver->diag_dci_wq); |
| } |
| |
| int diag_dci_clear_log_mask() |
| { |
| int j, k, err = DIAG_DCI_NO_ERROR; |
| uint8_t *log_mask_ptr, *update_ptr; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| entry = diag_dci_get_client_entry(); |
| if (!entry) { |
| pr_err("diag: In %s, invalid client entry\n", __func__); |
| return DIAG_DCI_TABLE_ERR; |
| } |
| |
| mutex_lock(&dci_log_mask_mutex); |
| create_dci_log_mask_tbl(entry->dci_log_mask); |
| memset(dci_cumulative_log_mask, 0x0, DCI_LOG_MASK_SIZE); |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| update_ptr = dci_cumulative_log_mask; |
| log_mask_ptr = entry->dci_log_mask; |
| for (j = 0; j < 16; j++) { |
| *update_ptr = j; |
| *(update_ptr + 1) = 1; |
| update_ptr += 2; |
| log_mask_ptr += 2; |
| for (k = 0; k < 513; k++) { |
| *update_ptr |= *log_mask_ptr; |
| update_ptr++; |
| log_mask_ptr++; |
| } |
| } |
| } |
| mutex_unlock(&dci_log_mask_mutex); |
| /* send updated mask to userspace clients */ |
| diag_update_userspace_clients(DCI_LOG_MASKS_TYPE); |
| /* Send updated mask to peripherals */ |
| err = diag_send_dci_log_mask(); |
| return err; |
| } |
| |
| int diag_dci_clear_event_mask() |
| { |
| int j, err = DIAG_DCI_NO_ERROR; |
| uint8_t *event_mask_ptr, *update_ptr; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| entry = diag_dci_get_client_entry(); |
| if (!entry) { |
| pr_err("diag: In %s, invalid client entry\n", __func__); |
| return DIAG_DCI_TABLE_ERR; |
| } |
| |
| mutex_lock(&dci_event_mask_mutex); |
| memset(entry->dci_event_mask, 0x0, DCI_EVENT_MASK_SIZE); |
| memset(dci_cumulative_event_mask, 0x0, DCI_EVENT_MASK_SIZE); |
| update_ptr = dci_cumulative_event_mask; |
| |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| event_mask_ptr = entry->dci_event_mask; |
| for (j = 0; j < DCI_EVENT_MASK_SIZE; j++) |
| *(update_ptr + j) |= *(event_mask_ptr + j); |
| } |
| mutex_unlock(&dci_event_mask_mutex); |
| /* send updated mask to userspace clients */ |
| diag_update_userspace_clients(DCI_EVENT_MASKS_TYPE); |
| /* Send updated mask to peripherals */ |
| err = diag_send_dci_event_mask(); |
| return err; |
| } |
| |
| int diag_dci_query_log_mask(uint16_t log_code) |
| { |
| return __diag_dci_query_log_mask(diag_dci_get_client_entry(), |
| log_code); |
| } |
| |
| int diag_dci_query_event_mask(uint16_t event_id) |
| { |
| return __diag_dci_query_event_mask(diag_dci_get_client_entry(), |
| event_id); |
| } |
| |
| uint8_t diag_dci_get_cumulative_real_time() |
| { |
| uint8_t real_time = MODE_NONREALTIME; |
| struct list_head *start, *temp; |
| struct diag_dci_client_tbl *entry = NULL; |
| |
| list_for_each_safe(start, temp, &driver->dci_client_list) { |
| entry = list_entry(start, struct diag_dci_client_tbl, track); |
| if (entry->real_time == MODE_REALTIME) { |
| real_time = 1; |
| break; |
| } |
| } |
| return real_time; |
| } |
| |
| int diag_dci_set_real_time(uint8_t real_time) |
| { |
| struct diag_dci_client_tbl *entry = NULL; |
| entry = diag_dci_get_client_entry(); |
| if (!entry) { |
| pr_err("diag: In %s, invalid client entry\n", __func__); |
| return 0; |
| } |
| entry->real_time = real_time; |
| return 1; |
| } |
| |
| void diag_dci_try_activate_wakeup_source() |
| { |
| spin_lock_irqsave(&ws_lock, ws_lock_flags); |
| pm_wakeup_event(driver->diag_dev, DCI_WAKEUP_TIMEOUT); |
| pm_stay_awake(driver->diag_dev); |
| spin_unlock_irqrestore(&ws_lock, ws_lock_flags); |
| } |
| |
| void diag_dci_try_deactivate_wakeup_source() |
| { |
| spin_lock_irqsave(&ws_lock, ws_lock_flags); |
| pm_relax(driver->diag_dev); |
| spin_unlock_irqrestore(&ws_lock, ws_lock_flags); |
| } |
| |
| int diag_dci_register_client(struct diag_dci_reg_tbl_t *reg_entry) |
| { |
| int i, err = 0; |
| struct diag_dci_client_tbl *new_entry = NULL; |
| struct diag_dci_buf_peripheral_t *proc_buf = NULL; |
| |
| if (!reg_entry) |
| return DIAG_DCI_NO_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; |
| |
| new_entry = kzalloc(sizeof(struct diag_dci_client_tbl), GFP_KERNEL); |
| if (new_entry == NULL) { |
| pr_err("diag: unable to alloc memory\n"); |
| return DIAG_DCI_NO_REG; |
| } |
| |
| 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; |
| } |
| |
| new_entry->client = current; |
| new_entry->client_info.notification_list = |
| reg_entry->notification_list; |
| new_entry->client_info.signal_type = |
| reg_entry->signal_type; |
| new_entry->real_time = MODE_REALTIME; |
| new_entry->in_service = 0; |
| INIT_LIST_HEAD(&new_entry->list_write_buf); |
| mutex_init(&new_entry->write_buf_mutex); |
| new_entry->dci_log_mask = kzalloc(DCI_LOG_MASK_SIZE, GFP_KERNEL); |
| if (!new_entry->dci_log_mask) { |
| pr_err("diag: Unable to create log mask for client, %d", |
| driver->dci_client_id); |
| goto fail_alloc; |
| } |
| create_dci_log_mask_tbl(new_entry->dci_log_mask); |
| |
| new_entry->dci_event_mask = kzalloc(DCI_EVENT_MASK_SIZE, GFP_KERNEL); |
| if (!new_entry->dci_event_mask) { |
| pr_err("diag: Unable to create event mask for client, %d", |
| driver->dci_client_id); |
| goto fail_alloc; |
| } |
| create_dci_event_mask_tbl(new_entry->dci_event_mask); |
| |
| for (i = 0; i < NUM_DCI_PROC; i++) { |
| proc_buf = &new_entry->buffers[i]; |
| if (!proc_buf) |
| goto fail_alloc; |
| |
| mutex_init(&proc_buf->health_mutex); |
| mutex_init(&proc_buf->buf_mutex); |
| proc_buf->health.dropped_events = 0; |
| proc_buf->health.dropped_logs = 0; |
| proc_buf->health.received_events = 0; |
| proc_buf->health.received_logs = 0; |
| proc_buf->buf_primary = kzalloc( |
| sizeof(struct diag_dci_buffer_t), |
| GFP_KERNEL); |
| if (!proc_buf->buf_primary) |
| goto fail_alloc; |
| proc_buf->buf_cmd = kzalloc(sizeof(struct diag_dci_buffer_t), |
| GFP_KERNEL); |
| if (!proc_buf->buf_cmd) |
| goto fail_alloc; |
| err = diag_dci_init_buffer(proc_buf->buf_primary, |
| DCI_BUF_PRIMARY); |
| if (err) |
| goto fail_alloc; |
| err = diag_dci_init_buffer(proc_buf->buf_cmd, DCI_BUF_CMD); |
| if (err) |
| goto fail_alloc; |
| proc_buf->buf_curr = proc_buf->buf_primary; |
| } |
| |
| list_add_tail(&new_entry->track, &driver->dci_client_list); |
| driver->dci_client_id++; |
| new_entry->client_info.client_id = driver->dci_client_id; |
| reg_entry->client_id = driver->dci_client_id; |
| driver->num_dci_client++; |
| if (driver->num_dci_client == 1) |
| diag_update_proc_vote(DIAG_PROC_DCI, VOTE_UP); |
| queue_work(driver->diag_real_time_wq, &driver->diag_real_time_work); |
| mutex_unlock(&driver->dci_mutex); |
| |
| return driver->dci_client_id; |
| |
| fail_alloc: |
| if (new_entry) { |
| for (i = 0; i < NUM_DCI_PROC; i++) { |
| proc_buf = &new_entry->buffers[i]; |
| mutex_destroy(&proc_buf->health_mutex); |
| mutex_destroy(&proc_buf->buf_primary->data_mutex); |
| mutex_destroy(&proc_buf->buf_cmd->data_mutex); |
| if (proc_buf->buf_primary) |
| kfree(proc_buf->buf_primary->data); |
| kfree(proc_buf->buf_primary); |
| if (proc_buf->buf_cmd) |
| kfree(proc_buf->buf_cmd->data); |
| kfree(proc_buf->buf_cmd); |
| } |
| kfree(new_entry->dci_event_mask); |
| kfree(new_entry->dci_log_mask); |
| } |
| kfree(new_entry); |
| mutex_unlock(&driver->dci_mutex); |
| return DIAG_DCI_NO_REG; |
| } |
| |
| int diag_dci_deinit_client() |
| { |
| int ret = DIAG_DCI_NO_ERROR, real_time = MODE_REALTIME, i, peripheral; |
| struct diag_dci_buf_peripheral_t *proc_buf = NULL; |
| struct diag_dci_client_tbl *entry = diag_dci_get_client_entry(); |
| struct diag_dci_buffer_t *buf_entry, *temp; |
| struct list_head *start, *req_temp; |
| struct dci_pkt_req_entry_t *req_entry = NULL; |
| struct diag_smd_info *smd_info = NULL; |
| |
| if (!entry) |
| return DIAG_DCI_NOT_SUPPORTED; |
| |
| mutex_lock(&driver->dci_mutex); |
| /* |
| * Remove the entry from the list before freeing the buffers |
| * to ensure that we don't have any invalid access. |
| */ |
| list_del(&entry->track); |
| driver->num_dci_client--; |
| /* |
| * Clear the client's log and event masks, update the cumulative |
| * masks and send the masks to peripherals |
| */ |
| kfree(entry->dci_log_mask); |
| diag_update_userspace_clients(DCI_LOG_MASKS_TYPE); |
| diag_dci_invalidate_cumulative_log_mask(); |
| ret = diag_send_dci_event_mask(); |
| if (ret != DIAG_DCI_NO_ERROR) { |
| mutex_unlock(&driver->dci_mutex); |
| return ret; |
| } |
| kfree(entry->dci_event_mask); |
| diag_update_userspace_clients(DCI_EVENT_MASKS_TYPE); |
| diag_dci_invalidate_cumulative_event_mask(); |
| ret = diag_send_dci_log_mask(); |
| if (ret != DIAG_DCI_NO_ERROR) { |
| mutex_unlock(&driver->dci_mutex); |
| return ret; |
| } |
| |
| list_for_each_safe(start, req_temp, &driver->dci_req_list) { |
| req_entry = list_entry(start, struct dci_pkt_req_entry_t, |
| track); |
| if (req_entry->pid == current->tgid) { |
| list_del(&req_entry->track); |
| kfree(req_entry); |
| } |
| } |
| |
| /* Clean up any buffer that is pending write */ |
| mutex_lock(&entry->write_buf_mutex); |
| list_for_each_entry_safe(buf_entry, temp, &entry->list_write_buf, |
| buf_track) { |
| list_del(&buf_entry->buf_track); |
| if (buf_entry->buf_type == DCI_BUF_SECONDARY) { |
| mutex_lock(&buf_entry->data_mutex); |
| diagmem_free(driver, buf_entry->data, POOL_TYPE_DCI); |
| buf_entry->data = NULL; |
| mutex_unlock(&buf_entry->data_mutex); |
| kfree(buf_entry); |
| } else if (buf_entry->buf_type == DCI_BUF_CMD) { |
| peripheral = buf_entry->data_source; |
| if (peripheral == APPS_DATA) |
| continue; |
| mutex_lock(&buf_entry->data_mutex); |
| smd_info = driver->separate_cmdrsp[peripheral] ? |
| &driver->smd_dci_cmd[peripheral] : |
| &driver->smd_dci[peripheral]; |
| smd_info->in_busy_1 = 0; |
| mutex_unlock(&buf_entry->data_mutex); |
| } |
| diag_dci_try_deactivate_wakeup_source(); |
| } |
| mutex_unlock(&entry->write_buf_mutex); |
| |
| for (i = 0; i < NUM_DCI_PROC; i++) { |
| proc_buf = &entry->buffers[i]; |
| buf_entry = proc_buf->buf_curr; |
| mutex_lock(&proc_buf->buf_mutex); |
| /* Clean up secondary buffer from mempool that is active */ |
| if (buf_entry && buf_entry->buf_type == DCI_BUF_SECONDARY) { |
| mutex_lock(&buf_entry->data_mutex); |
| diagmem_free(driver, buf_entry->data, POOL_TYPE_DCI); |
| buf_entry->data = NULL; |
| mutex_unlock(&buf_entry->data_mutex); |
| mutex_destroy(&buf_entry->data_mutex); |
| kfree(buf_entry); |
| } |
| |
| mutex_lock(&proc_buf->buf_primary->data_mutex); |
| kfree(proc_buf->buf_primary->data); |
| mutex_unlock(&proc_buf->buf_primary->data_mutex); |
| |
| mutex_lock(&proc_buf->buf_cmd->data_mutex); |
| kfree(proc_buf->buf_cmd->data); |
| mutex_unlock(&proc_buf->buf_cmd->data_mutex); |
| |
| mutex_destroy(&proc_buf->health_mutex); |
| mutex_destroy(&proc_buf->buf_primary->data_mutex); |
| mutex_destroy(&proc_buf->buf_cmd->data_mutex); |
| |
| kfree(proc_buf->buf_primary); |
| kfree(proc_buf->buf_cmd); |
| mutex_unlock(&proc_buf->buf_mutex); |
| } |
| mutex_destroy(&entry->write_buf_mutex); |
| |
| kfree(entry); |
| |
| if (driver->num_dci_client == 0) { |
| diag_update_proc_vote(DIAG_PROC_DCI, VOTE_DOWN); |
| } else { |
| real_time = diag_dci_get_cumulative_real_time(); |
| diag_update_real_time_vote(DIAG_PROC_DCI, real_time); |
| } |
| queue_work(driver->diag_real_time_wq, &driver->diag_real_time_work); |
| |
| mutex_unlock(&driver->dci_mutex); |
| |
| return DIAG_DCI_NO_ERROR; |
| } |
| |
| int diag_dci_write_proc(int peripheral, int pkt_type, char *buf, int len) |
| { |
| struct diag_smd_info *smd_info = NULL; |
| int wr_size = 0, retry = 0, err = -EAGAIN, timer = 0, i; |
| |
| if (!buf || (peripheral < 0 || peripheral > NUM_SMD_DCI_CHANNELS) |
| || len < 0) { |
| pr_err("diag: In %s, invalid data 0x%p, peripheral: %d, len: %d\n", |
| __func__, buf, peripheral, len); |
| return -EINVAL; |
| } |
| |
| if (pkt_type == DIAG_DATA_TYPE) { |
| for (i = 0; i < NUM_SMD_DCI_CMD_CHANNELS; i++) |
| if (peripheral == i) |
| smd_info = &driver->smd_dci_cmd[peripheral]; |
| /* |
| * This peripheral doesn't support separate channel for |
| * command response. |
| */ |
| if (!smd_info) |
| smd_info = &driver->smd_dci[peripheral]; |
| } else if (pkt_type == DIAG_CNTL_TYPE) { |
| smd_info = &driver->smd_cntl[peripheral]; |
| } else { |
| pr_err("diag: Invalid DCI pkt type in %s", __func__); |
| return -EINVAL; |
| } |
| |
| if (!smd_info || !smd_info->ch) |
| return -EINVAL; |
| |
| while (retry < 3) { |
| mutex_lock(&smd_info->smd_ch_mutex); |
| wr_size = smd_write(smd_info->ch, buf, len); |
| if (wr_size == len) { |
| pr_debug("diag: successfully wrote pkt_type %d of len %d to %d in trial %d", |
| pkt_type, len, peripheral, (retry+1)); |
| err = DIAG_DCI_NO_ERROR; |
| mutex_unlock(&smd_info->smd_ch_mutex); |
| break; |
| } |
| pr_debug("diag: cannot write pkt_type %d of len %d to %d in trial %d", |
| pkt_type, len, peripheral, (retry+1)); |
| retry++; |
| mutex_unlock(&smd_info->smd_ch_mutex); |
| |
| /* |
| * Sleep for sometime before retrying. The delay of 2000 was |
| * determined empirically as best value to use. |
| */ |
| for (timer = 0; timer < 5; timer++) |
| usleep(2000); |
| } |
| return err; |
| } |
| |
| int diag_dci_copy_health_stats(struct diag_dci_health_stats *stats, int proc) |
| { |
| struct diag_dci_client_tbl *entry = NULL; |
| struct diag_dci_health_t *health = NULL; |
| int i; |
| |
| if (!stats) |
| return -EINVAL; |
| |
| if (proc < ALL_PROC || proc > APPS_DATA) |
| return -EINVAL; |
| |
| entry = diag_dci_get_client_entry(); |
| if (!entry) |
| return DIAG_DCI_NOT_SUPPORTED; |
| |
| stats->stats.dropped_logs = 0; |
| stats->stats.dropped_events = 0; |
| stats->stats.received_logs = 0; |
| stats->stats.received_events = 0; |
| |
| if (proc != ALL_PROC) { |
| health = &entry->buffers[proc].health; |
| stats->stats.dropped_logs = health->dropped_logs; |
| stats->stats.dropped_events = health->dropped_events; |
| stats->stats.received_logs = health->received_logs; |
| stats->stats.received_events = health->received_events; |
| if (stats->reset_status) { |
| mutex_lock(&entry->buffers[proc].health_mutex); |
| health->dropped_logs = 0; |
| health->dropped_events = 0; |
| health->received_logs = 0; |
| health->received_events = 0; |
| mutex_unlock(&entry->buffers[proc].health_mutex); |
| } |
| return DIAG_DCI_NO_ERROR; |
| } |
| |
| |
| for (i = 0; i < NUM_DCI_PROC; i++) { |
| health = &entry->buffers[i].health; |
| stats->stats.dropped_logs += health->dropped_logs; |
| stats->stats.dropped_events += health->dropped_events; |
| stats->stats.received_logs += health->received_logs; |
| stats->stats.received_events += health->received_events; |
| if (stats->reset_status) { |
| mutex_lock(&entry->buffers[i].health_mutex); |
| health->dropped_logs = 0; |
| health->dropped_events = 0; |
| health->received_logs = 0; |
| health->received_events = 0; |
| mutex_unlock(&entry->buffers[i].health_mutex); |
| } |
| } |
| return DIAG_DCI_NO_ERROR; |
| } |