| /* |
| * Copyright (c) 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 <linux/types.h> |
| #include <linux/cdev.h> |
| #include <linux/gfp.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/completion.h> |
| #include <linux/of_gpio.h> |
| #include <linux/mutex.h> |
| #include <mach/msm_smsm.h> |
| #include <linux/uaccess.h> |
| #include <asm/system.h> |
| |
| #define SMP2P_NUM_PROCS 8 |
| |
| #define SM_VERSION 1 |
| #define SM_BLOCKSIZE 128 |
| |
| #define SMQ_MAGIC_INIT 0xFF00FF00 |
| #define SMQ_MAGIC_PRODUCER (SMQ_MAGIC_INIT | 0x1) |
| #define SMQ_MAGIC_CONSUMER (SMQ_MAGIC_INIT | 0x2) |
| |
| enum SMQ_STATUS { |
| SMQ_SUCCESS = 0, |
| SMQ_ENOMEMORY = -1, |
| SMQ_EBADPARM = -2, |
| SMQ_UNDERFLOW = -3, |
| SMQ_OVERFLOW = -4 |
| }; |
| |
| enum smq_type { |
| PRODUCER = 1, |
| CONSUMER = 2, |
| INVALID = 3 |
| }; |
| |
| struct smq_block_map { |
| uint32_t index_read; |
| uint32_t num_blocks; |
| uint8_t *map; |
| }; |
| |
| struct smq_node { |
| uint16_t index_block; |
| uint16_t num_blocks; |
| } __attribute__ ((__packed__)); |
| |
| struct smq_hdr { |
| uint8_t producer_version; |
| uint8_t consumer_version; |
| } __attribute__ ((__packed__)); |
| |
| struct smq_out_state { |
| uint32_t init; |
| uint32_t index_check_queue_for_reset; |
| uint32_t index_sent_write; |
| uint32_t index_free_read; |
| } __attribute__ ((__packed__)); |
| |
| struct smq_out { |
| struct smq_out_state s; |
| struct smq_node sent[1]; |
| }; |
| |
| struct smq_in_state { |
| uint32_t init; |
| uint32_t index_check_queue_for_reset_ack; |
| uint32_t index_sent_read; |
| uint32_t index_free_write; |
| } __attribute__ ((__packed__)); |
| |
| struct smq_in { |
| struct smq_in_state s; |
| struct smq_node free[1]; |
| }; |
| |
| struct smq { |
| struct smq_hdr *hdr; |
| struct smq_out *out; |
| struct smq_in *in; |
| uint8_t *blocks; |
| uint32_t num_blocks; |
| struct mutex *lock; |
| uint32_t initialized; |
| struct smq_block_map block_map; |
| enum smq_type type; |
| }; |
| |
| struct gpio_info { |
| int gpio_base_id; |
| int irq_base_id; |
| }; |
| |
| struct rdbg_data { |
| struct device *device; |
| struct completion work; |
| struct gpio_info in; |
| struct gpio_info out; |
| bool device_initialized; |
| int gpio_out_offset; |
| bool device_opened; |
| void *smem_addr; |
| size_t smem_size; |
| struct smq producer_smrb; |
| struct smq consumer_smrb; |
| struct mutex write_mutex; |
| }; |
| |
| struct rdbg_device { |
| struct cdev cdev; |
| struct class *class; |
| dev_t dev_no; |
| int num_devices; |
| struct rdbg_data *rdbg_data; |
| }; |
| |
| static struct rdbg_device g_rdbg_instance = { |
| { {0} }, |
| NULL, |
| 0, |
| SMP2P_NUM_PROCS, |
| NULL |
| }; |
| |
| struct processor_specific_info { |
| char *name; |
| unsigned int smem_buffer_addr; |
| size_t smem_buffer_size; |
| }; |
| |
| static struct processor_specific_info proc_info[SMP2P_NUM_PROCS] = { |
| {0}, /*APPS*/ |
| {"rdbg_modem", 0, 0}, /*MODEM*/ |
| {"rdbg_adsp", SMEM_LC_DEBUGGER, 16*1024}, /*ADSP*/ |
| {0}, /*SMP2P_RESERVED_PROC_1*/ |
| {"rdbg_wcnss", 0, 0}, /*WCNSS*/ |
| {0}, /*SMP2P_RESERVED_PROC_2*/ |
| {0}, /*SMP2P_POWER_PROC*/ |
| {0} /*SMP2P_REMOTE_MOCK_PROC*/ |
| }; |
| |
| static int smq_blockmap_get(struct smq_block_map *block_map, |
| uint32_t *block_index, uint32_t n) |
| { |
| uint32_t start; |
| uint32_t mark = 0; |
| uint32_t found = 0; |
| uint32_t i = 0; |
| |
| start = block_map->index_read; |
| |
| if (n == 1) { |
| do { |
| if (!block_map->map[block_map->index_read]) { |
| *block_index = block_map->index_read; |
| block_map->map[block_map->index_read] = 1; |
| block_map->index_read++; |
| block_map->index_read %= block_map->num_blocks; |
| return SMQ_SUCCESS; |
| } |
| block_map->index_read++; |
| } while (start != (block_map->index_read %= |
| block_map->num_blocks)); |
| } else { |
| mark = block_map->num_blocks; |
| |
| do { |
| if (!block_map->map[block_map->index_read]) { |
| if (mark > block_map->index_read) { |
| mark = block_map->index_read; |
| start = block_map->index_read; |
| found = 0; |
| } |
| |
| found++; |
| if (found == n) { |
| *block_index = mark; |
| for (i = 0; i < n; i++) |
| block_map->map[mark + i] = |
| (uint8_t)(n - i); |
| block_map->index_read += block_map->map |
| [block_map->index_read] - 1; |
| return SMQ_SUCCESS; |
| } |
| } else { |
| found = 0; |
| block_map->index_read += block_map->map |
| [block_map->index_read] - 1; |
| mark = block_map->num_blocks; |
| } |
| block_map->index_read++; |
| } while (start != (block_map->index_read %= |
| block_map->num_blocks)); |
| } |
| |
| return SMQ_ENOMEMORY; |
| } |
| |
| static void smq_blockmap_put(struct smq_block_map *block_map, uint32_t i) |
| { |
| uint32_t num_blocks = block_map->map[i]; |
| |
| while (num_blocks--) { |
| block_map->map[i] = 0; |
| i++; |
| } |
| } |
| |
| static int smq_blockmap_reset(struct smq_block_map *block_map) |
| { |
| if (!block_map->map) |
| return SMQ_ENOMEMORY; |
| memset(block_map->map, 0 , block_map->num_blocks + 1); |
| block_map->index_read = 0; |
| |
| return SMQ_SUCCESS; |
| } |
| |
| static int smq_blockmap_ctor(struct smq_block_map *block_map, |
| uint32_t num_blocks) |
| { |
| if (num_blocks <= 1) |
| return SMQ_ENOMEMORY; |
| |
| block_map->map = kcalloc(num_blocks, sizeof(uint8_t), GFP_KERNEL); |
| if (!block_map->map) |
| return SMQ_ENOMEMORY; |
| |
| block_map->num_blocks = num_blocks - 1; |
| smq_blockmap_reset(block_map); |
| |
| return SMQ_SUCCESS; |
| } |
| |
| static void smq_blockmap_dtor(struct smq_block_map *block_map) |
| { |
| kfree(block_map->map); |
| block_map->map = NULL; |
| } |
| |
| static int smq_free(struct smq *smq, void *data) |
| { |
| struct smq_node node; |
| uint32_t index_block; |
| int err = SMQ_SUCCESS; |
| |
| if (smq->lock) |
| mutex_lock(smq->lock); |
| |
| if ((SM_VERSION != smq->hdr->producer_version) && |
| (SMQ_MAGIC_PRODUCER != smq->out->s.init)) { |
| err = SMQ_UNDERFLOW; |
| goto bail; |
| } |
| |
| index_block = ((uint8_t *)data - smq->blocks) / SM_BLOCKSIZE; |
| if (index_block >= smq->num_blocks) { |
| err = SMQ_EBADPARM; |
| goto bail; |
| } |
| |
| node.index_block = (uint16_t)index_block; |
| node.num_blocks = 0; |
| *((struct smq_node *)(smq->in->free + smq->in-> |
| s.index_free_write)) = node; |
| |
| smq->in->s.index_free_write = (smq->in->s.index_free_write + 1) |
| % smq->num_blocks; |
| |
| bail: |
| if (smq->lock) |
| mutex_unlock(smq->lock); |
| return err; |
| } |
| |
| static int smq_receive(struct smq *smq, void **pp, int *pnsize, int *pbmore) |
| { |
| struct smq_node *node; |
| int err = SMQ_SUCCESS; |
| int more = 0; |
| |
| if ((SM_VERSION != smq->hdr->producer_version) && |
| (SMQ_MAGIC_PRODUCER != smq->out->s.init)) |
| return SMQ_UNDERFLOW; |
| |
| if (smq->in->s.index_sent_read == smq->out->s.index_sent_write) { |
| err = SMQ_UNDERFLOW; |
| goto bail; |
| } |
| |
| node = (struct smq_node *)(smq->out->sent + smq->in->s.index_sent_read); |
| if (node->index_block >= smq->num_blocks) { |
| err = SMQ_EBADPARM; |
| goto bail; |
| } |
| |
| smq->in->s.index_sent_read = (smq->in->s.index_sent_read + 1) |
| % smq->num_blocks; |
| |
| *pp = smq->blocks + (node->index_block * SM_BLOCKSIZE); |
| *pnsize = SM_BLOCKSIZE * node->num_blocks; |
| rmb(); |
| if (smq->in->s.index_sent_read != smq->out->s.index_sent_write) |
| more = 1; |
| |
| bail: |
| *pbmore = more; |
| return err; |
| } |
| |
| static int smq_alloc_send(struct smq *smq, const uint8_t *pcb, int nsize) |
| { |
| void *pv = 0; |
| int num_blocks; |
| uint32_t index_block = 0; |
| int err = SMQ_SUCCESS; |
| struct smq_node *node = NULL; |
| |
| mutex_lock(smq->lock); |
| |
| if ((SMQ_MAGIC_CONSUMER == smq->in->s.init) && |
| (SM_VERSION == smq->hdr->consumer_version)) { |
| if (smq->out->s.index_check_queue_for_reset == |
| smq->in->s.index_check_queue_for_reset_ack) { |
| while (smq->out->s.index_free_read != |
| smq->in->s.index_free_write) { |
| node = (struct smq_node *)( |
| smq->in->free + |
| smq->out->s.index_free_read); |
| if (node->index_block >= smq->num_blocks) { |
| err = SMQ_EBADPARM; |
| goto bail; |
| } |
| |
| smq->out->s.index_free_read = |
| (smq->out->s.index_free_read + 1) |
| % smq->num_blocks; |
| |
| smq_blockmap_put(&smq->block_map, |
| node->index_block); |
| rmb(); |
| } |
| } |
| } |
| |
| num_blocks = ALIGN(nsize, SM_BLOCKSIZE)/SM_BLOCKSIZE; |
| err = smq_blockmap_get(&smq->block_map, &index_block, num_blocks); |
| if (SMQ_SUCCESS != err) |
| goto bail; |
| |
| pv = smq->blocks + (SM_BLOCKSIZE * index_block); |
| |
| err = copy_from_user((void *)pv, (void *)pcb, nsize); |
| if (0 != err) |
| goto bail; |
| |
| ((struct smq_node *)(smq->out->sent + |
| smq->out->s.index_sent_write))->index_block |
| = (uint16_t)index_block; |
| ((struct smq_node *)(smq->out->sent + |
| smq->out->s.index_sent_write))->num_blocks |
| = (uint16_t)num_blocks; |
| |
| smq->out->s.index_sent_write = (smq->out->s.index_sent_write + 1) |
| % smq->num_blocks; |
| |
| bail: |
| if (SMQ_SUCCESS != err) { |
| if (pv) |
| smq_blockmap_put(&smq->block_map, index_block); |
| } |
| mutex_unlock(smq->lock); |
| return err; |
| } |
| |
| static int smq_reset_producer_queue_internal(struct smq *smq, |
| uint32_t reset_num) |
| { |
| int retval = 0; |
| uint32_t i; |
| |
| if (PRODUCER != smq->type) |
| goto bail; |
| |
| mutex_lock(smq->lock); |
| if (smq->out->s.index_check_queue_for_reset != reset_num) { |
| smq->out->s.index_check_queue_for_reset = reset_num; |
| for (i = 0; i < smq->num_blocks; i++) |
| (smq->out->sent + i)->index_block = 0xFFFF; |
| |
| smq_blockmap_reset(&smq->block_map); |
| smq->out->s.index_sent_write = 0; |
| smq->out->s.index_free_read = 0; |
| retval = 1; |
| } |
| mutex_unlock(smq->lock); |
| |
| bail: |
| return retval; |
| } |
| |
| static int smq_check_queue_reset(struct smq *p_cons, struct smq *p_prod) |
| { |
| int retval = 0; |
| uint32_t reset_num, i; |
| |
| if ((CONSUMER != p_cons->type) || |
| (SMQ_MAGIC_PRODUCER != p_cons->out->s.init) || |
| (SM_VERSION != p_cons->hdr->producer_version)) |
| goto bail; |
| |
| reset_num = p_cons->out->s.index_check_queue_for_reset; |
| if (p_cons->in->s.index_check_queue_for_reset_ack != reset_num) { |
| p_cons->in->s.index_check_queue_for_reset_ack = reset_num; |
| for (i = 0; i < p_cons->num_blocks; i++) |
| (p_cons->in->free + i)->index_block = 0xFFFF; |
| |
| p_cons->in->s.index_sent_read = 0; |
| p_cons->in->s.index_free_write = 0; |
| |
| retval = smq_reset_producer_queue_internal(p_prod, reset_num); |
| } |
| |
| bail: |
| return retval; |
| } |
| |
| static int check_subsystem_debug_enabled(void *base_addr, int size) |
| { |
| int num_blocks; |
| uint8_t *pb_orig; |
| uint8_t *pb; |
| struct smq smq; |
| int err = 0; |
| |
| pb = pb_orig = (uint8_t *)base_addr; |
| pb += sizeof(struct smq_hdr); |
| pb = PTR_ALIGN(pb, 8); |
| size -= pb - (uint8_t *)pb_orig; |
| num_blocks = (int)((size - sizeof(struct smq_out_state) - |
| sizeof(struct smq_in_state))/(SM_BLOCKSIZE + |
| sizeof(struct smq_node) * 2)); |
| if (0 >= num_blocks) { |
| err = SMQ_EBADPARM; |
| goto bail; |
| } |
| |
| pb += num_blocks * SM_BLOCKSIZE; |
| smq.out = (struct smq_out *)pb; |
| pb += sizeof(struct smq_out_state) + (num_blocks * |
| sizeof(struct smq_node)); |
| smq.in = (struct smq_in *)pb; |
| |
| if (SMQ_MAGIC_CONSUMER != smq.in->s.init) { |
| pr_err("%s, smq in consumer not initialized", __func__); |
| err = -ECOMM; |
| } |
| |
| bail: |
| return err; |
| } |
| |
| static void smq_dtor(struct smq *smq) |
| { |
| if (SMQ_MAGIC_INIT == smq->initialized) { |
| switch (smq->type) { |
| case PRODUCER: |
| smq->out->s.init = 0; |
| smq_blockmap_dtor(&smq->block_map); |
| break; |
| case CONSUMER: |
| smq->in->s.init = 0; |
| break; |
| default: |
| case INVALID: |
| break; |
| } |
| |
| smq->initialized = 0; |
| } |
| } |
| |
| /* |
| * The shared memory is used as a circular ring buffer in each direction. |
| * Thus we have a bi-directional shared memory channel between the AP |
| * and a subsystem. We call this SMQ. Each memory channel contains a header, |
| * data and a control mechanism that is used to synchronize read and write |
| * of data between the AP and the remote subsystem. |
| * |
| * Overall SMQ memory view: |
| * |
| * +------------------------------------------------+ |
| * | SMEM buffer | |
| * |-----------------------+------------------------| |
| * |Producer: LA | Producer: Remote | |
| * |Consumer: Remote | subsystem | |
| * | subsystem | Consumer: LA | |
| * | | | |
| * | Producer| Consumer| |
| * +-----------------------+------------------------+ |
| * | | |
| * | | |
| * | +--------------------------------------+ |
| * | | |
| * | | |
| * v v |
| * +--------------------------------------------------------------+ |
| * | Header | Data | Control | |
| * +-----------+---+---+---+-----+----+--+--+-----+---+--+--+-----+ |
| * | | b | b | b | | S |n |n | | S |n |n | | |
| * | Producer | l | l | l | | M |o |o | | M |o |o | | |
| * | Ver | o | o | o | | Q |d |d | | Q |d |d | | |
| * |-----------| c | c | c | ... | |e |e | ... | |e |e | ... | |
| * | | k | k | k | | O | | | | I | | | | |
| * | Consumer | | | | | u |0 |1 | | n |0 |1 | | |
| * | Ver | 0 | 1 | 2 | | t | | | | | | | | |
| * +-----------+---+---+---+-----+----+--+--+-----+---+--+--+-----+ |
| * | | |
| * + | |
| * | |
| * +------------------------+ |
| * | |
| * v |
| * +----+----+----+----+ |
| * | SMQ Nodes | |
| * |----|----|----|----| |
| * Node # | 0 | 1 | 2 | ...| |
| * |----|----|----|----| |
| * Starting Block Index # | 0 | 3 | 8 | ...| |
| * |----|----|----|----| |
| * # of blocks | 3 | 5 | 1 | ...| |
| * +----+----+----+----+ |
| * |
| * Header: Contains version numbers for software compatibility to ensure |
| * that both producers and consumers on the AP and subsystems know how to |
| * read from and write to the queue. |
| * Both the producer and consumer versions are 1. |
| * +---------+-------------------+ |
| * | Size | Field | |
| * +---------+-------------------+ |
| * | 1 byte | Producer Version | |
| * +---------+-------------------+ |
| * | 1 byte | Consumer Version | |
| * +---------+-------------------+ |
| * |
| * Data: The data portion contains multiple blocks [0..N] of a fixed size. |
| * The block size SM_BLOCKSIZE is fixed to 128 bytes for header version #1. |
| * Payload sent from the debug agent app is split (if necessary) and placed |
| * in these blocks. The first data block is placed at the next 8 byte aligned |
| * address after the header. |
| * |
| * The number of blocks for a given SMEM allocation is derived as follows: |
| * Number of Blocks = ((Total Size - Alignment - Size of Header |
| * - Size of SMQIn - Size of SMQOut)/(SM_BLOCKSIZE)) |
| * |
| * The producer maintains a private block map of each of these blocks to |
| * determine which of these blocks in the queue is available and which are free. |
| * |
| * Control: |
| * The control portion contains a list of nodes [0..N] where N is number |
| * of available data blocks. Each node identifies the data |
| * block indexes that contain a particular debug message to be transfered, |
| * and the number of blocks it took to hold the contents of the message. |
| * |
| * Each node has the following structure: |
| * +---------+-------------------+ |
| * | Size | Field | |
| * +---------+-------------------+ |
| * | 2 bytes |Staring Block Index| |
| * +---------+-------------------+ |
| * | 2 bytes |Number of Blocks | |
| * +---------+-------------------+ |
| * |
| * The producer and the consumer update different parts of the control channel |
| * (SMQOut / SMQIn) respectively. Each of these control data structures contains |
| * information about the last node that was written / read, and the actual nodes |
| * that were written/read. |
| * |
| * SMQOut Structure (R/W by producer, R by consumer): |
| * +---------+-------------------+ |
| * | Size | Field | |
| * +---------+-------------------+ |
| * | 4 bytes | Magic Init Number | |
| * +---------+-------------------+ |
| * | 4 bytes | Reset | |
| * +---------+-------------------+ |
| * | 4 bytes | Last Sent Index | |
| * +---------+-------------------+ |
| * | 4 bytes | Index Free Read | |
| * +---------+-------------------+ |
| * |
| * SMQIn Structure (R/W by consumer, R by producer): |
| * +---------+-------------------+ |
| * | Size | Field | |
| * +---------+-------------------+ |
| * | 4 bytes | Magic Init Number | |
| * +---------+-------------------+ |
| * | 4 bytes | Reset ACK | |
| * +---------+-------------------+ |
| * | 4 bytes | Last Read Index | |
| * +---------+-------------------+ |
| * | 4 bytes | Index Free Write | |
| * +---------+-------------------+ |
| * |
| * Magic Init Number: |
| * Both SMQ Out and SMQ In initialize this field with a predefined magic |
| * number so as to make sure that both the consumer and producer blocks |
| * have fully initialized and have valid data in the shared memory control area. |
| * Producer Magic #: 0xFF00FF01 |
| * Consumer Magic #: 0xFF00FF02 |
| */ |
| static int smq_ctor(struct smq *smq, void *base_addr, int size, |
| enum smq_type type, struct mutex *lock_ptr) |
| { |
| int num_blocks; |
| uint8_t *pb_orig; |
| uint8_t *pb; |
| uint32_t i; |
| int err; |
| |
| if (SMQ_MAGIC_INIT == smq->initialized) { |
| err = SMQ_EBADPARM; |
| goto bail; |
| } |
| |
| if (!base_addr || !size) { |
| err = SMQ_EBADPARM; |
| goto bail; |
| } |
| |
| if (type == PRODUCER) |
| smq->lock = lock_ptr; |
| |
| pb_orig = (uint8_t *)base_addr; |
| smq->hdr = (struct smq_hdr *)pb_orig; |
| pb = pb_orig; |
| pb += sizeof(struct smq_hdr); |
| pb = PTR_ALIGN(pb, 8); |
| size -= pb - (uint8_t *)pb_orig; |
| num_blocks = (int)((size - sizeof(struct smq_out_state) - |
| sizeof(struct smq_in_state))/(SM_BLOCKSIZE + |
| sizeof(struct smq_node) * 2)); |
| if (0 >= num_blocks) { |
| err = SMQ_ENOMEMORY; |
| goto bail; |
| } |
| |
| smq->blocks = pb; |
| smq->num_blocks = num_blocks; |
| pb += num_blocks * SM_BLOCKSIZE; |
| smq->out = (struct smq_out *)pb; |
| pb += sizeof(struct smq_out_state) + (num_blocks * |
| sizeof(struct smq_node)); |
| smq->in = (struct smq_in *)pb; |
| smq->type = type; |
| if (PRODUCER == type) { |
| smq->hdr->producer_version = SM_VERSION; |
| for (i = 0; i < smq->num_blocks; i++) |
| (smq->out->sent + i)->index_block = 0xFFFF; |
| |
| err = smq_blockmap_ctor(&smq->block_map, smq->num_blocks); |
| if (SMQ_SUCCESS != err) |
| goto bail; |
| |
| smq->out->s.index_sent_write = 0; |
| smq->out->s.index_free_read = 0; |
| if (smq->out->s.init == SMQ_MAGIC_PRODUCER) { |
| smq->out->s.index_check_queue_for_reset += 1; |
| } else { |
| smq->out->s.index_check_queue_for_reset = 1; |
| smq->out->s.init = SMQ_MAGIC_PRODUCER; |
| } |
| } else { |
| smq->hdr->consumer_version = SM_VERSION; |
| for (i = 0; i < smq->num_blocks; i++) |
| (smq->in->free + i)->index_block = 0xFFFF; |
| |
| smq->in->s.index_sent_read = 0; |
| smq->in->s.index_free_write = 0; |
| if (smq->out->s.init == SMQ_MAGIC_PRODUCER) { |
| smq->in->s.index_check_queue_for_reset_ack = |
| smq->out->s.index_check_queue_for_reset; |
| } else { |
| smq->in->s.index_check_queue_for_reset_ack = 0; |
| } |
| |
| smq->in->s.init = SMQ_MAGIC_CONSUMER; |
| } |
| smq->initialized = SMQ_MAGIC_INIT; |
| err = SMQ_SUCCESS; |
| |
| bail: |
| return err; |
| } |
| |
| static void send_interrupt_to_subsystem(struct rdbg_data *rdbgdata) |
| { |
| int offset = rdbgdata->gpio_out_offset; |
| int val = 1 ^ gpio_get_value(rdbgdata->out.gpio_base_id + offset); |
| gpio_set_value(rdbgdata->out.gpio_base_id + offset, val); |
| rdbgdata->gpio_out_offset = (offset + 1) % 32; |
| |
| dev_dbg(rdbgdata->device, "%s: sent interrupt %d to subsystem", |
| __func__, val); |
| } |
| |
| static irqreturn_t on_interrupt_from(int irq, void *ptr) |
| { |
| struct rdbg_data *rdbgdata = (struct rdbg_data *) ptr; |
| |
| dev_dbg(rdbgdata->device, "%s: Received interrupt %d from subsystem", |
| __func__, irq); |
| |
| complete(&(rdbgdata->work)); |
| return IRQ_HANDLED; |
| } |
| |
| static int initialize_smq(struct rdbg_data *rdbgdata) |
| { |
| int err = 0; |
| |
| if (smq_ctor(&(rdbgdata->producer_smrb), (void *)(rdbgdata->smem_addr), |
| ((rdbgdata->smem_size)/2), PRODUCER, &rdbgdata->write_mutex)) { |
| dev_err(rdbgdata->device, "%s: smq producer allocation failed", |
| __func__); |
| err = -ENOMEM; |
| goto bail; |
| } |
| |
| if (smq_ctor(&(rdbgdata->consumer_smrb), (void *)((uint32_t) |
| (rdbgdata->smem_addr) + ((rdbgdata->smem_size)/2)), |
| ((rdbgdata->smem_size)/2), CONSUMER, NULL)) { |
| dev_err(rdbgdata->device, "%s: smq conmsumer allocation failed", |
| __func__); |
| err = -ENOMEM; |
| } |
| |
| bail: |
| return err; |
| |
| } |
| |
| static int rdbg_open(struct inode *inode, struct file *filp) |
| { |
| int device_id = -1; |
| struct rdbg_device *device = &g_rdbg_instance; |
| struct rdbg_data *rdbgdata = NULL; |
| int err = 0; |
| |
| if (!inode || !device->rdbg_data) { |
| pr_err("Memory not allocated yet"); |
| err = -ENODEV; |
| goto bail; |
| } |
| |
| device_id = MINOR(inode->i_rdev); |
| rdbgdata = &device->rdbg_data[device_id]; |
| |
| if (rdbgdata->device_opened) { |
| dev_err(rdbgdata->device, "%s: Device already opened", |
| __func__); |
| err = -EEXIST; |
| goto bail; |
| } |
| |
| rdbgdata->smem_size = proc_info[device_id].smem_buffer_size; |
| if (!rdbgdata->smem_size) { |
| dev_err(rdbgdata->device, "%s: smem not initialized", __func__); |
| err = -ENOMEM; |
| goto bail; |
| } |
| |
| rdbgdata->smem_addr = smem_alloc(proc_info[device_id].smem_buffer_addr, |
| rdbgdata->smem_size); |
| if (!rdbgdata->smem_addr) { |
| dev_err(rdbgdata->device, "%s: Could not allocate smem memory", |
| __func__); |
| err = -ENOMEM; |
| goto bail; |
| } |
| dev_dbg(rdbgdata->device, "%s: SMEM address=0x%x smem_size=%d", |
| __func__, (unsigned int)rdbgdata->smem_addr, |
| rdbgdata->smem_size); |
| |
| if (check_subsystem_debug_enabled(rdbgdata->smem_addr, |
| rdbgdata->smem_size/2)) { |
| dev_err(rdbgdata->device, "%s: Subsystem %s is not debug enabled", |
| __func__, proc_info[device_id].name); |
| err = -ECOMM; |
| goto bail; |
| } |
| |
| init_completion(&rdbgdata->work); |
| |
| err = request_irq(rdbgdata->in.irq_base_id, on_interrupt_from, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| proc_info[device_id].name, |
| (void *)&device->rdbg_data[device_id]); |
| if (err) { |
| dev_err(rdbgdata->device, |
| "%s: Failed to register interrupt.Err=%d,irqid=%d.", |
| __func__, err, rdbgdata->in.irq_base_id); |
| goto irq_bail; |
| } |
| |
| err = enable_irq_wake(rdbgdata->in.irq_base_id); |
| if (err < 0) { |
| dev_dbg(rdbgdata->device, "enable_irq_wake() failed with err=%d", |
| err); |
| err = 0; |
| } |
| |
| mutex_init(&rdbgdata->write_mutex); |
| |
| err = initialize_smq(rdbgdata); |
| if (err) { |
| dev_err(rdbgdata->device, "Error initializing smq. Err=%d", |
| err); |
| goto smq_bail; |
| } |
| |
| rdbgdata->device_opened = 1; |
| |
| filp->private_data = (void *)rdbgdata; |
| |
| return 0; |
| |
| smq_bail: |
| smq_dtor(&(rdbgdata->producer_smrb)); |
| smq_dtor(&(rdbgdata->consumer_smrb)); |
| mutex_destroy(&rdbgdata->write_mutex); |
| irq_bail: |
| free_irq(rdbgdata->in.irq_base_id, (void *) |
| &device->rdbg_data[device_id]); |
| bail: |
| return err; |
| } |
| |
| static int rdbg_release(struct inode *inode, struct file *filp) |
| { |
| int device_id = -1; |
| struct rdbg_device *rdbgdevice = &g_rdbg_instance; |
| struct rdbg_data *rdbgdata = NULL; |
| int err = 0; |
| |
| if (!inode || !rdbgdevice->rdbg_data) { |
| pr_err("Memory not allocated yet"); |
| err = -ENODEV; |
| goto bail; |
| } |
| |
| device_id = MINOR(inode->i_rdev); |
| rdbgdata = &rdbgdevice->rdbg_data[device_id]; |
| |
| if (rdbgdata->device_opened == 1) { |
| dev_dbg(rdbgdata->device, "%s: Destroying %s.", __func__, |
| proc_info[device_id].name); |
| rdbgdata->device_opened = 0; |
| complete(&(rdbgdata->work)); |
| free_irq(rdbgdata->in.irq_base_id, (void *) |
| &rdbgdevice->rdbg_data[device_id]); |
| if (rdbgdevice->rdbg_data[device_id].producer_smrb.initialized) |
| smq_dtor(&(rdbgdevice->rdbg_data[device_id]. |
| producer_smrb)); |
| if (rdbgdevice->rdbg_data[device_id].consumer_smrb.initialized) |
| smq_dtor(&(rdbgdevice->rdbg_data[device_id]. |
| consumer_smrb)); |
| mutex_destroy(&rdbgdata->write_mutex); |
| } |
| |
| filp->private_data = NULL; |
| |
| bail: |
| return err; |
| } |
| |
| static ssize_t rdbg_read(struct file *filp, char __user *buf, size_t size, |
| loff_t *offset) |
| { |
| int err = 0; |
| struct rdbg_data *rdbgdata = filp->private_data; |
| void *p_sent_buffer = NULL; |
| int nsize = 0; |
| int more = 0; |
| |
| if (!rdbgdata) { |
| pr_err("Invalid argument"); |
| err = -EINVAL; |
| goto bail; |
| } |
| |
| dev_dbg(rdbgdata->device, "%s: In receive", __func__); |
| err = wait_for_completion_interruptible(&(rdbgdata->work)); |
| if (err) { |
| dev_err(rdbgdata->device, "%s: Error in wait", __func__); |
| goto bail; |
| } |
| |
| smq_check_queue_reset(&(rdbgdata->consumer_smrb), |
| &(rdbgdata->producer_smrb)); |
| if (SMQ_SUCCESS != smq_receive(&(rdbgdata->consumer_smrb), |
| &p_sent_buffer, &nsize, &more)) { |
| dev_err(rdbgdata->device, "%s: Error in smq_recv(). Err code = %d", |
| __func__, err); |
| err = -ENODATA; |
| goto bail; |
| } |
| |
| size = ((size < nsize) ? size : nsize); |
| err = copy_to_user(buf, p_sent_buffer, size); |
| if (err != 0) { |
| dev_err(rdbgdata->device, "%s: Error in copy_to_user(). Err code = %d", |
| __func__, err); |
| err = -ENODATA; |
| goto bail; |
| } |
| |
| smq_free(&(rdbgdata->consumer_smrb), p_sent_buffer); |
| err = size; |
| dev_dbg(rdbgdata->device, "%s: Read data to buffer with address 0x%x", |
| __func__, (unsigned int) buf); |
| |
| bail: |
| dev_dbg(rdbgdata->device, "%s: Returning from receive", __func__); |
| return err; |
| } |
| |
| static ssize_t rdbg_write(struct file *filp, const char __user *buf, |
| size_t size, loff_t *offset) |
| { |
| int err = 0; |
| struct rdbg_data *rdbgdata = filp->private_data; |
| |
| if (!rdbgdata) { |
| pr_err("Invalid argument"); |
| err = -EINVAL; |
| goto bail; |
| } |
| |
| if (smq_alloc_send(&(rdbgdata->producer_smrb), buf, size)) { |
| dev_err(rdbgdata->device, "%s, Error sending", __func__); |
| err = -ECOMM; |
| goto bail; |
| } |
| send_interrupt_to_subsystem(rdbgdata); |
| |
| err = size; |
| |
| bail: |
| return err; |
| } |
| |
| |
| static const struct file_operations rdbg_fops = { |
| .open = rdbg_open, |
| .read = rdbg_read, |
| .write = rdbg_write, |
| .release = rdbg_release, |
| }; |
| |
| static int register_smp2p(char *node_name, struct gpio_info *gpio_info_ptr) |
| { |
| struct device_node *node = NULL; |
| int cnt = 0; |
| int id = 0; |
| |
| node = of_find_compatible_node(NULL, NULL, node_name); |
| if (node) { |
| cnt = of_gpio_count(node); |
| if (cnt && gpio_info_ptr) { |
| id = of_get_gpio(node, 0); |
| gpio_info_ptr->gpio_base_id = id; |
| gpio_info_ptr->irq_base_id = gpio_to_irq(id); |
| return 0; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| static int __init rdbg_init(void) |
| { |
| int err = 0; |
| struct rdbg_device *rdbgdevice = &g_rdbg_instance; |
| int minor = 0; |
| int major = 0; |
| int minor_nodes_created = 0; |
| |
| char *rdbg_compatible_string = "qcom,smp2pgpio_client_rdbg_"; |
| int max_len = strlen(rdbg_compatible_string) + strlen("xx_out"); |
| |
| char *node_name = kcalloc(max_len, sizeof(char), GFP_KERNEL); |
| |
| if (!node_name) { |
| pr_err("Not enough memory"); |
| err = -ENOMEM; |
| goto bail; |
| } |
| |
| if (rdbgdevice->num_devices < 1 || |
| rdbgdevice->num_devices > SMP2P_NUM_PROCS) { |
| pr_err("rgdb: invalid num_devices"); |
| err = -EDOM; |
| goto name_bail; |
| } |
| |
| rdbgdevice->rdbg_data = kcalloc(rdbgdevice->num_devices, |
| sizeof(struct rdbg_data), GFP_KERNEL); |
| if (!rdbgdevice->rdbg_data) { |
| pr_err("Not enough memory for rdbg devices"); |
| err = -ENOMEM; |
| goto name_bail; |
| } |
| |
| err = alloc_chrdev_region(&rdbgdevice->dev_no, 0, |
| rdbgdevice->num_devices, "rdbgctl"); |
| if (err) { |
| pr_err("Error in alloc_chrdev_region."); |
| goto data_bail; |
| } |
| major = MAJOR(rdbgdevice->dev_no); |
| |
| cdev_init(&rdbgdevice->cdev, &rdbg_fops); |
| rdbgdevice->cdev.owner = THIS_MODULE; |
| err = cdev_add(&rdbgdevice->cdev, MKDEV(major, 0), |
| rdbgdevice->num_devices); |
| if (err) { |
| pr_err("Error in cdev_add"); |
| goto chrdev_bail; |
| } |
| |
| rdbgdevice->class = class_create(THIS_MODULE, "rdbg"); |
| if (IS_ERR(rdbgdevice->class)) { |
| err = PTR_ERR(rdbgdevice->class); |
| pr_err("Error in class_create"); |
| goto cdev_bail; |
| } |
| |
| for (minor = 0; minor < rdbgdevice->num_devices; minor++) { |
| if (!proc_info[minor].name) |
| continue; |
| |
| if (snprintf(node_name, max_len, "%s%d_in", |
| rdbg_compatible_string, minor) <= 0) { |
| pr_err("Error in snprintf"); |
| err = -ENOMEM; |
| goto device_bail; |
| } |
| |
| if (register_smp2p(node_name, |
| &rdbgdevice->rdbg_data[minor].in)) { |
| pr_debug("No incoming device tree entry found for %s", |
| proc_info[minor].name); |
| continue; |
| } |
| |
| if (snprintf(node_name, max_len, "%s%d_out", |
| rdbg_compatible_string, minor) <= 0) { |
| pr_err("Error in snprintf"); |
| err = -ENOMEM; |
| goto device_bail; |
| } |
| |
| if (register_smp2p(node_name, |
| &rdbgdevice->rdbg_data[minor].out)) { |
| pr_err("No outgoing device tree entry found for %s", |
| proc_info[minor].name); |
| err = -EINVAL; |
| goto device_bail; |
| } |
| |
| rdbgdevice->rdbg_data[minor].device = device_create( |
| rdbgdevice->class, NULL, MKDEV(major, minor), |
| NULL, "%s", proc_info[minor].name); |
| if (IS_ERR(rdbgdevice->rdbg_data[minor].device)) { |
| err = PTR_ERR(rdbgdevice->rdbg_data[minor].device); |
| pr_err("Error in device_create"); |
| goto device_bail; |
| } |
| rdbgdevice->rdbg_data[minor].device_initialized = 1; |
| minor_nodes_created++; |
| dev_dbg(rdbgdevice->rdbg_data[minor].device, |
| "%s: created /dev/%s c %d %d'", __func__, |
| proc_info[minor].name, major, minor); |
| } |
| |
| if (!minor_nodes_created) { |
| pr_err("No device tree entries found"); |
| err = -EINVAL; |
| goto class_bail; |
| } |
| |
| goto name_bail; |
| |
| device_bail: |
| for (--minor; minor >= 0; minor--) { |
| if (rdbgdevice->rdbg_data[minor].device_initialized) |
| device_destroy(rdbgdevice->class, |
| MKDEV(MAJOR(rdbgdevice->dev_no), minor)); |
| } |
| class_bail: |
| class_destroy(rdbgdevice->class); |
| cdev_bail: |
| cdev_del(&rdbgdevice->cdev); |
| chrdev_bail: |
| unregister_chrdev_region(rdbgdevice->dev_no, rdbgdevice->num_devices); |
| data_bail: |
| kfree(rdbgdevice->rdbg_data); |
| name_bail: |
| kfree(node_name); |
| bail: |
| return err; |
| } |
| |
| static void __exit rdbg_exit(void) |
| { |
| struct rdbg_device *rdbgdevice = &g_rdbg_instance; |
| int minor; |
| |
| for (minor = 0; minor < rdbgdevice->num_devices; minor++) { |
| if (rdbgdevice->rdbg_data[minor].device_initialized) { |
| device_destroy(rdbgdevice->class, |
| MKDEV(MAJOR(rdbgdevice->dev_no), minor)); |
| } |
| } |
| class_destroy(rdbgdevice->class); |
| cdev_del(&rdbgdevice->cdev); |
| unregister_chrdev_region(rdbgdevice->dev_no, 1); |
| kfree(rdbgdevice->rdbg_data); |
| } |
| |
| module_init(rdbg_init); |
| module_exit(rdbg_exit); |
| |
| MODULE_DESCRIPTION("rdbg module"); |
| MODULE_LICENSE("GPL v2"); |