/*----------------------------------------------------------------------------*/ /* Copyright (C) ST Ericsson, 2009. */ /* */ /* This program is free software; you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the Free */ /* Software Foundation; either version 2.1 of the License, or (at your option */ /* any later version. */ /* */ /* 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. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see . */ /*----------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shrm_driver.h" #include "shrm_private.h" #include "shrm_config.h" #include #include #ifdef CONFIG_HIGH_RES_TIMERS #include static struct hrtimer timer; #endif #include #include #include #define NAME "IPC_ISA" #define ISA_DEVICES 4 /**debug functionality*/ #define ISA_DEBUG 0 #define dbg_printk(format, arg...) (ISA_DEBUG & 1) ? \ (printk(KERN_ALERT NAME ": " format , ## arg)) : \ ({do {} while (0); }) #define PHONET_TASKLET #define MAX_RCV_LEN 2048 static unsigned char ph_recv_buf[MAX_RCV_LEN]; void do_phonet_rcv_tasklet(unsigned long unused); struct tasklet_struct phonet_rcv_tasklet; /**global data*/ enum { PHONET_BIND_NOT_DONE, PHONET_BIND_DONE }; static int8_t phonet_bind_flag = PHONET_BIND_NOT_DONE; #define ISI_MESSAGING (0) #define RPC_MESSAGING (1) #define AUDIO_MESSAGING (2) #define SECURITY_MESSAGING (3) #define SIZE_OF_FIFO (512*1024) static unsigned char message_fifo[4][SIZE_OF_FIFO]; static unsigned char wr_isi_msg[10*1024]; static unsigned char wr_rpc_msg[10*1024]; static unsigned char wr_sec_msg[10*1024]; static unsigned char wr_audio_msg[10*1024]; /**global data*/ /* int major:This variable is exported to user as module_param to specify major number at load time */ static int major; module_param(major, int, 0); MODULE_PARM_DESC(major, "Major device number"); /**global fops mutex*/ static DEFINE_MUTEX(isa_lock); /**global driver context pointer */ struct t_isa_driver_context *p_isa_context; rx_cb common_rx; rx_cb audio_rx; struct shrm_dev *pshm_dev; struct net_device *dev; static void isi_receive(void *p_data, u32 n_bytes); static void rpc_receive(void *p_data, u32 n_bytes); static void audio_receive(void *p_data, u32 n_bytes); static void security_receive(void *p_data, u32 n_bytes); void phonet_bind_check(int8_t flag) { phonet_bind_flag = flag; } EXPORT_SYMBOL(phonet_bind_check); int isa_to_skb(/*struct net_device *dev,*/ unsigned char *data, size_t len) { struct sk_buff *skb; if (data == NULL) return -ENOMEM; /** * The packet has been retrieved from the transmission * medium. Build an skb around it, so upper layers can handle it */ skb = dev_alloc_skb(len /*+ 2*/ + sizeof(struct phonethdr)); if (!skb) { if (printk_ratelimit()) printk(KERN_NOTICE "isa rx: low on mem - packet dropped\n"); goto out; } memcpy(skb_put(skb, sizeof(struct phonethdr)), (void *)(data + 1), \ sizeof(struct phonethdr)); memcpy(skb_put(skb, len), data, len); /*Write metadata, and then pass to the receive level*/ skb->dev = dev;/*kmalloc(sizeof(struct net_device), GFP_ATOMIC);*/ skb->protocol = htons(ETH_P_PHONET); skb->priority = 0; skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ netif_rx_ni(skb); out: return 0; } static void rx_common_l2msg_handler(unsigned char l2_header, void *msg, unsigned int length) { #ifdef CONFIG_U8500_SHRM_LOOP_BACK unsigned char *pdata; #endif dbg_printk("rx_common_l2msg_handler++ \n"); switch (l2_header) { case 0: isi_receive(msg, length); break; case 1: rpc_receive(msg, length); break; case 3: security_receive(msg, length); break; #ifdef CONFIG_U8500_SHRM_LOOP_BACK case 0xC0: pdata = (unsigned char *)msg; if ((*pdata == 0x50) || (*pdata == 0xAF)) isi_receive(msg, length); else if ((*pdata == 0x0A) || (*pdata == 0xF5)) rpc_receive(msg, length); else if ((*pdata == 0xFF) || (*pdata == 0x00)) security_receive(msg, length); break; #endif default: break; } dbg_printk("rx_common_l2msg_handler-- \n"); } static void rx_audio_l2msg_handler(unsigned char l2_header, void *msg, unsigned int length) { dbg_printk("rx_audio_l2msg_handler++ \n"); audio_receive(msg, length); dbg_printk("rx_audio_l2msg_handler-- \n"); } static int __init shm_initialise_irq(struct shrm_dev *shm_dev_data) { int err = 0; shm_protocol_init(rx_common_l2msg_handler, rx_audio_l2msg_handler); err = request_irq(shm_dev_data->ca_wake_irq, ca_wake_irq_handler, IRQF_TRIGGER_RISING, "ca_wake-up", shm_dev_data); if (err < 0) { printk(KERN_ALERT "Unable to allocate shm tx interrupt line\n"); err = -EBUSY; free_irq(shm_dev_data->ca_wake_irq, shm_dev_data); } err = request_irq(shm_dev_data->ac_read_notif_0_irq, \ ac_read_notif_0_irq_handler, 0, \ "ac_read_notif_0", shm_dev_data); if (err < 0) { printk(KERN_ALERT "error ac_read_notif_0_irq interrupt line\n"); err = -EBUSY; free_irq(shm_dev_data->ac_read_notif_0_irq, shm_dev_data); } err = request_irq(shm_dev_data->ac_read_notif_1_irq, \ ac_read_notif_1_irq_handler, 0, \ "ac_read_notif_1", shm_dev_data); if (err < 0) { printk(KERN_ALERT "error ac_read_notif_1_irq interrupt line\n"); err = -EBUSY; free_irq(shm_dev_data->ac_read_notif_1_irq, shm_dev_data); } err = request_irq(shm_dev_data->ca_msg_pending_notif_0_irq, \ ca_msg_pending_notif_0_irq_handler, 0, \ "ca_msg_pending_notif_0", shm_dev_data); if (err < 0) { printk(KERN_ALERT "error ca_msg_pending_notif_0_irq line\n"); err = -EBUSY; free_irq(shm_dev_data->ca_msg_pending_notif_0_irq, \ shm_dev_data); } err = request_irq(shm_dev_data->ca_msg_pending_notif_1_irq, \ ca_msg_pending_notif_1_irq_handler, 0, \ "ca_msg_pending_notif_1", shm_dev_data); if (err < 0) { printk(KERN_ALERT "error ca_msg_pending_notif_1_irq interrupt line\n"); err = -EBUSY; free_irq(shm_dev_data->ca_msg_pending_notif_1_irq, \ shm_dev_data); } return err; } static void free_shm_irq(struct shrm_dev *shm_dev_data) { free_irq(shm_dev_data->ca_wake_irq, shm_dev_data); free_irq(shm_dev_data->ac_read_notif_0_irq, shm_dev_data); free_irq(shm_dev_data->ac_read_notif_1_irq, shm_dev_data); free_irq(shm_dev_data->ca_msg_pending_notif_0_irq, shm_dev_data); free_irq(shm_dev_data->ca_msg_pending_notif_1_irq, shm_dev_data); } /** * create_queue() - To create FIFO for Tx and Rx message buffering. * @q: message queue. * @devicetype: device type 0-isi,1-rpc,2-audio,3-security. * * This function creates a FIFO buffer of n_bytes size using * dma_alloc_coherent(). It also initializes all queue handling * locks, queue management pointers. It also initializes message list * which occupies this queue. * * It return -ENOMEM in case of no memory. */ static int create_queue(struct t_message_queue *q, u32 devicetype) { q->p_fifo_base = (unsigned char *)&message_fifo[devicetype]; q->size = SIZE_OF_FIFO; q->readptr = 0; q->writeptr = 0; q->no = 0; spin_lock_init(&q->update_lock); sema_init(&q->tx_mutex, 1); INIT_LIST_HEAD(&q->msg_list); init_waitqueue_head(&q->wq_readable); atomic_set(&q->q_rp, 0); return 0; } /** * delete_queue() - To delete FIFO and assiciated memory. * @q: message queue * * This function deletes FIFO created using create_queue() function. * It resets queue management pointers. */ static void delete_queue(struct t_message_queue *q) { q->size = 0; q->readptr = 0; q->writeptr = 0; } /** * add_msg_to_queue() - Add a message inside inside queue * * @q: message queue * @size: size in bytes * * This function tries to allocate n_bytes of size in FIFO q. * It returns negative number when no memory can be allocated * currently. */ void add_msg_to_queue(struct t_message_queue *q, unsigned int size) { struct t_queue_element *new_msg = NULL; dbg_printk("add_msg_to_queue++ q->writeptr=%d \n", q->writeptr); new_msg = kmalloc(sizeof(struct t_queue_element), /*GFP_KERNEL|*/GFP_ATOMIC); if (new_msg == NULL) { printk(KERN_ALERT ":memory overflow inside while(1) \n"); BUG_ON(new_msg == NULL); } new_msg->offset = q->writeptr; new_msg->size = size; new_msg->no = q->no++; /*check for overflow condition*/ if (q->readptr <= q->writeptr) { if (((q->writeptr-q->readptr) + size) >= q->size) { printk(KERN_ALERT "Buffer overflow**************************\n"); BUG_ON(((q->writeptr-q->readptr) + size) >= q->size); } } else { if ((q->writeptr + size) >= q->readptr) { printk(KERN_ALERT "Buffer overflow**************************\n"); BUG_ON((q->writeptr + size) >= q->readptr); } } q->writeptr = (q->writeptr + size) % q->size; if (list_empty(&q->msg_list)) { list_add_tail(&new_msg->entry, &q->msg_list); /*There can be 2 blocking calls read and another select */ atomic_set(&q->q_rp, 1); wake_up_interruptible(&q->wq_readable); } else list_add_tail(&new_msg->entry, &q->msg_list); dbg_printk("add_msg_to_queue-- \n"); } /** * remove_msg_from_queue() - To remove a message from the msg queue. * * @q: message queue * * This function delets a message from the message list associated with message * queue q and also updates read ptr. * If the message list is empty, then, event is set to block the select and * read calls of the paricular queue. * * The message list is FIFO style and message is always added to tail and * removed from head. */ void remove_msg_from_queue(struct t_message_queue *q) { struct t_queue_element *old_msg = NULL; struct list_head *msg; dbg_printk("remove_msg_from_queue++ q->readptr %d\n", q->readptr); list_for_each(msg, &q->msg_list) { old_msg = list_entry(msg, struct t_queue_element, entry); if (old_msg == NULL) { printk(KERN_ALERT ":no message found\n"); BUG_ON(old_msg == NULL); } break; } list_del(msg); q->readptr = (q->readptr + old_msg->size)%q->size; if (list_empty(&q->msg_list)) { dbg_printk("List is empty setting RP= 0\n"); atomic_set(&q->q_rp, 0); } kfree(old_msg); dbg_printk("remove_msg_from_queue-- \n"); } /** * get_size_of_new_msg() - retrieve new message from message list * * @q: message queue * * This function will retrieve most recent message from the corresponding * queue list. New message is always retrieved from head side. * It returns new message no, offset if FIFO and size. */ static int get_size_of_new_msg(struct t_message_queue *q) { struct t_queue_element *new_msg = NULL; struct list_head *msg_list; dbg_printk("get_size_of_new_msg++\n"); spin_lock_bh(&q->update_lock); list_for_each(msg_list, &q->msg_list) { new_msg = list_entry(msg_list, struct t_queue_element, entry); if (new_msg == NULL) { spin_unlock_bh(&q->update_lock); printk(KERN_ALERT NAME ":no message found\n"); return -1; } break; } spin_unlock_bh(&q->update_lock); dbg_printk("get_size_of_new_msg--\n"); return new_msg->size; } /** * isi_receive() - Rx Completion callback * * @p_data:message pointer * @n_bytes:message size * * This function is a callback to indicate ISI message reception is complete. * It updates Writeptr of the Fifo */ static void isi_receive(void *p_data, u32 n_bytes) { u32 size = 0; unsigned char *psrc; struct t_message_queue *q; struct t_isadev_context *isidev = p_isa_context->p_isadev[0]; dbg_printk("isi_receive++\n"); q = &isidev->dl_queue; spin_lock(&q->update_lock); /*Memcopy RX data first*/ if ((q->writeptr+n_bytes) >= q->size) { dbg_printk("Inside Loop Back\n"); psrc = (unsigned char *)p_data; size = (q->size-q->writeptr); /*Copy First Part of msg*/ memcpy((q->p_fifo_base+q->writeptr), psrc, size); psrc += size; /*Copy Second Part of msg at the top of fifo*/ memcpy(q->p_fifo_base, psrc, (n_bytes-size)); } else { memcpy((q->p_fifo_base+q->writeptr), p_data, n_bytes); } add_msg_to_queue(q, n_bytes); spin_unlock(&q->update_lock); #ifndef PHONET_TASKLET unsigned char *buf, *temp; ssize_t len; buf = kmalloc(n_bytes, GFP_ATOMIC); temp = buf; len = isa_read(NULL, buf, n_bytes, NULL); #if (ISA_DEBUG & 1) int i; dbg_printk(KERN_ALERT NAME "\nSAMPATH: recv buf :len = %d, \ n_bytes = %d\n", len, n_bytes); for (i = 0 ; i < len ; i++) dbg_printk("%02X ", buf[i]); dbg_printk("\n\n"); #endif isa_to_skb(buf, len); kfree(temp); #else /** * This flag is set after a socket is created by user-space * and it is bound to an address */ if (phonet_bind_flag == PHONET_BIND_DONE) { dbg_printk("\nSAMPATH:: scheduling the phonet tasklet \ from %s!!!\n", __func__); tasklet_schedule(&phonet_rcv_tasklet); } dbg_printk("\nSAMPATH:: Out of phonet tasklet %s!!!\n", __func__); #endif dbg_printk("isi_receive--\n"); } /** * rpc_receive() - Rx Completion callback * * @p_data:message pointer * @n_bytes:message size * * This function is a callback to indicate RPC message reception is complete. * It updates Writeptr of the Fifo */ static void rpc_receive(void *p_data, u32 n_bytes) { u32 size = 0; unsigned char *psrc; struct t_message_queue *q; struct t_isadev_context *rpcdev = p_isa_context->p_isadev[1]; dbg_printk("rpc_receive++\n"); q = &rpcdev->dl_queue; spin_lock(&q->update_lock); /*Memcopy RX data first*/ if ((q->writeptr+n_bytes) >= q->size) { psrc = (unsigned char *)p_data; size = (q->size-q->writeptr); /*Copy First Part of msg*/ memcpy((q->p_fifo_base+q->writeptr), psrc, size); psrc += size; /*Copy Second Part of msg at the top of fifo*/ memcpy(q->p_fifo_base, psrc, (n_bytes-size)); } else { memcpy((q->p_fifo_base+q->writeptr), p_data, n_bytes); } add_msg_to_queue(q, n_bytes); spin_unlock(&q->update_lock); dbg_printk("rpc_receive--\n"); } /** * audio_receive() - Rx Completion callback * * @p_data:message pointer * @n_bytes:message size * * This function is a callback to indicate audio message reception is complete. * It updates Writeptr of the Fifo */ static void audio_receive(void *p_data, u32 n_bytes) { u32 size = 0; unsigned char *psrc; struct t_message_queue *q; struct t_isadev_context *audiodev = p_isa_context->p_isadev[2]; dbg_printk("audio_receive++\n"); q = &audiodev->dl_queue; spin_lock(&q->update_lock); /*Memcopy RX data first*/ if ((q->writeptr+n_bytes) >= q->size) { psrc = (unsigned char *)p_data; size = (q->size-q->writeptr); /*Copy First Part of msg*/ memcpy((q->p_fifo_base+q->writeptr), psrc, size); psrc += size; /*Copy Second Part of msg at the top of fifo*/ memcpy(q->p_fifo_base, psrc, (n_bytes-size)); } else { memcpy((q->p_fifo_base+q->writeptr), p_data, n_bytes); } add_msg_to_queue(q, n_bytes); spin_unlock(&q->update_lock); dbg_printk("audio_receive--\n"); } /** * security_receive() - Rx Completion callback * * @p_data:message pointer * @n_bytes: message size * * This function is a callback to indicate security message reception * is complete.It updates Writeptr of the Fifo */ static void security_receive(void *p_data, u32 n_bytes) { u32 size = 0; unsigned char *psrc; struct t_message_queue *q; struct t_isadev_context *secdev = p_isa_context->p_isadev[3]; dbg_printk("security_receive++\n"); q = &secdev->dl_queue; spin_lock(&q->update_lock); /*Memcopy RX data first*/ if ((q->writeptr+n_bytes) >= q->size) { psrc = (unsigned char *)p_data; size = (q->size-q->writeptr); /*Copy First Part of msg*/ memcpy((q->p_fifo_base+q->writeptr), psrc, size); psrc += size; /*Copy Second Part of msg at the top of fifo*/ memcpy(q->p_fifo_base, psrc, (n_bytes-size)); } else { memcpy((q->p_fifo_base+q->writeptr), p_data, n_bytes); } add_msg_to_queue(q, n_bytes); spin_unlock(&q->update_lock); dbg_printk("security_receive--\n"); } /** * isa_select() - Select Interface * * @filp:file descriptor pointer * @wait:poll_table_struct pointer * * This function is used to perform non-blocking read operations. It allows * a process to determine whether it can read from one or more open files * without blocking. These calls can also block a process until any of a * given set of file descriptors becomes available for reading. * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned. * The driver method is called whenever the user-space program performs a select * system call involving a file descriptor associated with the driver. */ static unsigned int isa_select(struct file *filp, \ struct poll_table_struct *wait) { struct t_isadev_context *p_isadev; struct t_message_queue *q; unsigned int mask = 0; u32 m = iminor(filp->f_path.dentry->d_inode); dbg_printk("isa_select++\n"); p_isadev = (struct t_isadev_context *)filp->private_data; if (p_isadev->device_id != m) return -1; q = &p_isadev->dl_queue; poll_wait(filp, &q->wq_readable, wait); if (atomic_read(&q->q_rp) == 1) mask = POLLIN | POLLRDNORM; dbg_printk("isa_select--\n"); return mask; } /** * isa_read() - Read from device * * @filp:file descriptor * @buf:user buffer pointer * @len:size of requested data transfer * @ppos:not used * * This function is called whenever user calls read() system call. * It reads a oldest message from queue and copies it into user buffer and * returns its size. * If there is no message present in queue, then it blocks until new data is * available. */ static ssize_t isa_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) { u32 size = 0; char *psrc; struct t_isadev_context *p_isadev; struct t_message_queue *q; u32 msgsize; u8 m; dbg_printk("isa_read++\n"); if (len <= 0) return -EFAULT; if (filp != NULL) { p_isadev = (struct t_isadev_context *)filp->private_data; q = &p_isadev->dl_queue; spin_lock_bh(&q->update_lock); if (list_empty(&q->msg_list)) { spin_unlock_bh(&q->update_lock); dbg_printk("Waiting for Data\n"); if (wait_event_interruptible(q->wq_readable, \ atomic_read(&q->q_rp) == 1)) { return -ERESTARTSYS; } } else spin_unlock_bh(&q->update_lock); msgsize = get_size_of_new_msg(q); if ((q->readptr+msgsize) >= q->size) { dbg_printk("Inside Loop Back\n"); psrc = (char *)buf; size = (q->size-q->readptr); /*Copy First Part of msg*/ if (copy_to_user(psrc, \ (unsigned char *)(q->p_fifo_base+q->readptr), size)) { printk(KERN_ALERT NAME ":copy_to_user \ failed\n"); return -EFAULT; } psrc += size; /*Copy Second Part of msg at the top of fifo*/ if (copy_to_user(psrc, \ (unsigned char *)(q->p_fifo_base), (msgsize-size))) { printk(KERN_ALERT NAME ":copy_to_user \ failed\n"); return -EFAULT; } } else { if (copy_to_user(buf, \ (unsigned char *)(q->p_fifo_base + q->readptr), \ msgsize)) { printk(KERN_ALERT NAME ":copy_to_user \ failed\n"); return -EFAULT; } } } else { m = ISI_MESSAGING; p_isadev = p_isa_context->p_isadev[m]; q = &p_isadev->dl_queue; spin_lock_bh(&q->update_lock); if (list_empty(&q->msg_list)) { spin_unlock_bh(&q->update_lock); dbg_printk("Waiting for Data\n"); return 0; } else spin_unlock_bh(&q->update_lock); msgsize = get_size_of_new_msg(q); if ((q->readptr+msgsize) >= q->size) { dbg_printk("Inside Loop Back : ISI\n"); size = (q->size-q->readptr); /*Copy First Part of msg*/ memcpy(buf, \ (unsigned char *)(q->p_fifo_base + q->readptr), \ size); /*Copy Second Part of msg at the top of fifo*/ memcpy(buf+size, \ (unsigned char *)(q->p_fifo_base), \ (msgsize - size)); } else { dbg_printk("Inside Loop Back\n"); memcpy(buf, \ (unsigned char *)(q->p_fifo_base+q->readptr), \ msgsize); } len = msgsize; #if (ISA_DEBUG & 1) int i; dbg_printk(KERN_ALERT NAME "\nisa_read buf :len = %d\n", len); for (i = 0 ; i < len ; i++) dbg_printk("%02X ", \ ((unsigned char *)(q->p_fifo_base+q->readptr))[i]); dbg_printk("\n\n"); #endif } spin_lock_bh(&q->update_lock); remove_msg_from_queue(q); spin_unlock_bh(&q->update_lock); dbg_printk("isa_read--\n"); return msgsize; } /** * isa_write() - Write to device * * @filp:file descriptor * @buf:user buffer pointer * @len:size of requested data transfer * @ppos:not used * * This function is called whenever user calls write() system call. * It checks if there is space available in queue, and copies the message * inside queue. If there is no space, it blocks until space becomes available. * It also schedules transfer thread to transmit the newly added message. */ ssize_t isa_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) { struct t_isadev_context *p_isadev; struct t_message_queue *q; void *addr = 0; u8 m; unsigned char l2_header = 0; dbg_printk("isa_write++\n"); if (len <= 0 || buf == NULL) return -EFAULT; if (filp != NULL) { p_isadev = (struct t_isadev_context *)filp->private_data; } else { m = ISI_MESSAGING; p_isadev = p_isa_context->p_isadev[m]; } q = &p_isadev->dl_queue; down(&q->tx_mutex); switch (p_isadev->device_id) { case 0: dbg_printk("ISI \n"); addr = (void *)wr_isi_msg; #ifdef CONFIG_U8500_SHRM_LOOP_BACK printk(KERN_INFO "Loopback \n"); l2_header = 0xC0; #else l2_header = p_isadev->device_id; #endif break; case 1: dbg_printk("RPC \n"); addr = (void *)wr_rpc_msg; #ifdef CONFIG_U8500_SHRM_LOOP_BACK l2_header = 0xC0; #else l2_header = p_isadev->device_id; #endif break; case 2: dbg_printk("Audio\n"); addr = (void *)wr_audio_msg; #ifdef CONFIG_U8500_SHRM_LOOP_BACK l2_header = 0x80; #else l2_header = p_isadev->device_id; #endif break; case 3: dbg_printk("Security \n"); addr = (void *)wr_sec_msg; #ifdef CONFIG_U8500_SHRM_LOOP_BACK l2_header = 0xC0; #else l2_header = p_isadev->device_id; #endif break; default: dbg_printk("Wrong device \n"); break; } #if (ISA_DEBUG & 1) int i; dbg_printk(KERN_ALERT NAME "SAMPATH:len = %d\nbuf:: \n", len); for (i = 0 ; i < len ; i++) dbg_printk("%02X ", buf[i]); #endif if (filp != NULL) { if (copy_from_user(addr, buf, len)) { printk(KERN_ALERT NAME ":copy_from_user failed\n"); return -EFAULT; } } else { if (addr != memcpy(addr, buf, len)) { printk(KERN_ALERT NAME ":memcpy failed\n"); return -EFAULT; } } /*Write msg to Fifo*/ shm_write_msg(l2_header, addr, len); up(&q->tx_mutex); dbg_printk("isa_write--\n"); return 0 /*len*/; } EXPORT_SYMBOL(isa_write); /** * isa_ioctl() - To handle different ioctl commands supported by driver. * * @inode: structure is used by the kernel internally to represent files * @filp:file descriptor pointer * @cmd:ioctl command * @arg:input param * * Following ioctls are supported by this driver. * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message. * This ioctl is called with required message size. It returns offset for * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate * new uplink message available in queuq for transmission. Message is copied * from offset location returned by previous ioctl before calling this ioctl. * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in * queue. It returns offset for new message inside queue. * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for * downlink message once the message is copied. Message is copied from offset * location returned by previous ioctl before calling this ioctl. */ static int isa_ioctl(struct inode *inode, struct file *filp, unsigned cmd, unsigned long arg) { int err = 0; struct t_isadev_context *p_isadev; u32 m = iminor(inode); p_isadev = (struct t_isadev_context *)filp->private_data; if (p_isadev->device_id != m) return -1; switch (cmd) { case DLP_IOC_ALLOCATE_BUFFER: dbg_printk(" DLP_IOC_ALLOCATE_BUFFER++\n"); break; case DLP_IOC_PUT_MESSAGE: dbg_printk(" DLP_IOC_PUT_MESSAGE++\n"); break; case DLP_IOC_GET_MESSAGE: dbg_printk(" DLP_IOC_GET_MESSAGE++\n"); break; case DLP_IOC_DEALLOCATE_BUFFER: dbg_printk(" DLP_IOC_DEALLOCATE_BUFFER++\n"); break; default: dbg_printk("Unknown IOCTL\n"); err = -1; break; } return err; } /** * isa_mmap() - Maps kernel queue memory to user space. * * @filp:file descriptor pointer * @vma:virtual area memory structure. * * This function maps kernel FIFO into user space. This function * shall be called twice to map both uplink and downlink buffers. */ static int isa_mmap(struct file *filp, struct vm_area_struct *vma) { struct t_isadev_context *p_isadev; int err = 0; u32 m = iminor(filp->f_path.dentry->d_inode); dbg_printk(" isa_mmap %d++\n", m); p_isadev = (struct t_isadev_context *)filp->private_data; dbg_printk(" isa_mmap--\n"); return err; } /** * isa_close() - Close device file * * @inode:structure is used by the kernel internally to represent files * @filp:device file descriptor * * This function deletes structues associated with this file, deletes * queues, flushes and destroys workqueus and closes this file. * It also unregisters itself from l2mux driver. */ static int isa_close(struct inode *inode, struct file *filp) { struct t_isadev_context *p_isadev; u8 m; mutex_lock(&isa_lock); dbg_printk("isa_close"); if (inode == NULL) m = ISI_MESSAGING; else m = iminor(filp->f_path.dentry->d_inode); dbg_printk("isa_close %d", m); if (atomic_dec_and_test(&p_isa_context->is_open[m])) { atomic_inc(&p_isa_context->is_open[m]); printk(KERN_ALERT NAME ":Device not opened yet\n"); mutex_unlock(&isa_lock); return -ENODEV; } atomic_set(&p_isa_context->is_open[m], 1); if (filp != NULL) p_isadev = (struct t_isadev_context *)filp->private_data; else p_isadev = p_isa_context->p_isadev[m]; dbg_printk("p_isadev->device_id %d", p_isadev->device_id); dbg_printk(" Closed %d device\n", m); if (m == ISI_MESSAGING) printk(KERN_ALERT "Closed ISI_MESSAGING Device\n"); else if (m == RPC_MESSAGING) printk(KERN_ALERT "Closed RPC_MESSAGING Device\n"); else if (m == AUDIO_MESSAGING) printk(KERN_ALERT "Closed AUDIO_MESSAGING Device\n"); else if (m == SECURITY_MESSAGING) printk(KERN_ALERT "Closed SECURITY_MESSAGING Device\n"); else printk(KERN_ALERT NAME ":No such device present\n"); mutex_unlock(&isa_lock); return 0; } /** * isa_open() - Open device file * * @inode: structure is used by the kernel internally to represent files * @filp: device file descriptor * * This function performs initialization tasks needed to open SHM channel. * Following tasks are performed. * -return if device is already opened * -create uplink FIFO * -create downlink FIFO * -init delayed workqueue thread * -register to l2mux driver */ static int isa_open(struct inode *inode, struct file *filp) { int err = 0; u8 m; struct t_isadev_context *p_isadev; dbg_printk("++\n"); if (get_boot_state() != BOOT_DONE) { printk(KERN_ALERT NAME "Boot is not done \n"); return -EBUSY; } mutex_lock(&isa_lock); if (inode == NULL) m = ISI_MESSAGING; else m = iminor(inode); if ((m != ISI_MESSAGING) && (m != RPC_MESSAGING) && \ (m != AUDIO_MESSAGING) && (m != SECURITY_MESSAGING)) { printk(KERN_ALERT NAME ":No such device present\n"); mutex_unlock(&isa_lock); return -ENODEV; } if (!atomic_dec_and_test(&p_isa_context->is_open[m])) { atomic_inc(&p_isa_context->is_open[m]); printk(KERN_ALERT NAME ":Device already opened\n"); mutex_unlock(&isa_lock); return -EBUSY; } if (m == ISI_MESSAGING) printk(KERN_ALERT "Open ISI_MESSAGING Device\n"); else if (m == RPC_MESSAGING) printk(KERN_ALERT "Open RPC_MESSAGING Device\n"); else if (m == AUDIO_MESSAGING) printk(KERN_ALERT "Open AUDIO_MESSAGING Device\n"); else if (m == SECURITY_MESSAGING) printk(KERN_ALERT "Open SECURITY_MESSAGING Device\n"); else printk(KERN_ALERT NAME ":No such device present\n"); p_isadev = p_isa_context->p_isadev[m]; if (filp != NULL) filp->private_data = p_isadev; mutex_unlock(&isa_lock); dbg_printk("--\n"); return err; } void do_phonet_rcv_tasklet(unsigned long unused) { ssize_t len, ret; // unsigned char *buf /* = (unsigned char *)kmalloc(100, GFP_ATOMIC)*/; dbg_printk(KERN_ALERT NAME "Inside %s\n", __func__); // buf = kmalloc(MAX_RCV_LEN, GFP_ATOMIC); for (;;) { len = isa_read(NULL, ph_recv_buf, MAX_RCV_LEN, NULL); if (len == 0) { dbg_printk(KERN_ALERT NAME "\nSAMPATH: \ len is zero, queue empty\n"); break; } if (len < 0) { dbg_printk(KERN_ALERT NAME "\nSAMPATH \ len < 0 !!! error!!!"); break; } #if (ISA_DEBUG & 1) int i; dbg_printk(KERN_ALERT NAME "\nSAMPATH: recv buf \ :len = %d\n", len); for (i = 0 ; i < len ; i++) dbg_printk("%02X ", ph_recv_buf[i]); dbg_printk("\n\n"); #endif ret = isa_to_skb(ph_recv_buf, len); if (ret < 0) printk(KERN_ALERT NAME "\n NULL pointer \ to isa_to_skb\n"); } // kfree(buf); dbg_printk(KERN_ALERT NAME "Exiting %s\n", __func__); } /* * struct shm_driver: SHRM platform structure * @owner: The probe funtion to be called * @open: The remove funtion to be called * @release: The driver data * @ioctl: The probe funtion to be called * @mmap: The remove funtion to be called * @read: The driver data * @write: The remove funtion to be called * @poll: The driver data */ const struct file_operations isa_fops = { .owner = THIS_MODULE, .open = isa_open, .release = isa_close, .ioctl = isa_ioctl, .mmap = isa_mmap, .read = isa_read, .write = isa_write, .poll = isa_select, }; /** * isa_init() - module insertion function * * This function registers module as a character driver using * register_chrdev_region() or alloc_chrdev_region. It adds this * driver to system using cdev_add() call. Major number is dynamically * allocated using alloc_chrdev_region() by default or left to user to specify * it during load time. For this variable major is used as module_param * Nodes to be created using * mknod /dev/isi c $major 0 * mknod /dev/rpc c $major 1 * mknod /dev/audio c $major 2 * mknod /dev/sec c $major 3 */ static int isa_init(void) { dev_t dev_id; int retval, no_dev; struct t_isadev_context *p_isadev; p_isa_context = kzalloc(sizeof(struct t_isa_driver_context), \ GFP_KERNEL); if (p_isa_context == NULL) { printk(KERN_ALERT NAME ":Failed to alloc memory\n"); return -ENOMEM; } if (major) { dev_id = MKDEV(major, 0); retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME); } else { retval = alloc_chrdev_region(&dev_id, 0, ISA_DEVICES, NAME); major = MAJOR(dev_id); } printk(KERN_ALERT NAME ": major %d\n", major); cdev_init(&p_isa_context->cdev, &isa_fops); p_isa_context->cdev.owner = THIS_MODULE; retval = cdev_add(&p_isa_context->cdev, dev_id, ISA_DEVICES); if (retval) { printk(KERN_ALERT NAME ":Failed to add char device\n"); return retval; } for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) atomic_set(&p_isa_context->is_open[no_dev], 1); for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { p_isadev = kzalloc(sizeof(struct t_isadev_context), GFP_KERNEL); if (p_isadev == NULL) { printk(KERN_ALERT NAME ":Failed to alloc memory\n"); return -ENOMEM; } p_isadev->device_id = no_dev; retval = create_queue(&p_isadev->dl_queue, p_isadev->device_id); if (retval < 0) { printk(KERN_ALERT NAME ":create dl_queue failed\n"); delete_queue(&p_isadev->dl_queue); kfree(p_isadev); return retval; } p_isa_context->p_isadev[p_isadev->device_id] = p_isadev; sema_init(&p_isadev->tx_mutex, 1); } printk(KERN_ALERT NAME ": Driver added\n"); return retval; } /** * isa_exit() - module removal function * * This function removes this driver from system. */ static void isa_exit(void) { int no_dev; struct t_isadev_context *p_isadev; dev_t dev_id = MKDEV(major, 0); for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { p_isadev = p_isa_context->p_isadev[no_dev]; delete_queue(&p_isadev->dl_queue); kfree(p_isadev); } cdev_del(&p_isa_context->cdev); unregister_chrdev_region(dev_id, ISA_DEVICES); kfree(p_isa_context); printk(KERN_ALERT NAME ": Driver removed\n"); } #ifdef CONFIG_HIGH_RES_TIMERS static enum hrtimer_restart callback(struct hrtimer *timer) { return HRTIMER_NORESTART; } #endif static int __init shrm_probe(struct platform_device *pdev) { int err = 0; struct resource *res; struct shrm_dev *shm_dev_data = NULL; if (pdev == NULL) { printk(KERN_ALERT "No device/platform_data found on shm device\n"); return -ENODEV; } shm_dev_data = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL); pshm_dev = shm_dev_data; if (shm_dev_data == NULL) { printk(KERN_ALERT "Could not allocate memory for struct shm_dev\n"); return -ENOMEM; } /** initialise the SHM */ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { printk(KERN_ALERT "Unable to map Ca Wake up interrupt \n"); err = -EBUSY; goto rollback_intr; } shm_dev_data->ca_wake_irq = res->start; res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); if (!res) { printk(KERN_ALERT "Unable to map APE_Read_notif_common IRQ base \n"); err = -EBUSY; goto rollback_intr; } shm_dev_data->ac_read_notif_0_irq = res->start; res = platform_get_resource(pdev, IORESOURCE_IRQ, 2); if (!res) { printk(KERN_ALERT "Unable to map APE_Read_notif_audio IRQ base \n"); err = -EBUSY; goto rollback_intr; } shm_dev_data->ac_read_notif_1_irq = res->start; res = platform_get_resource(pdev, IORESOURCE_IRQ, 3); if (!res) { printk(KERN_ALERT "Unable to map Cmt_msg_pending_notif_common IRQ base \n"); err = -EBUSY; goto rollback_intr; } shm_dev_data->ca_msg_pending_notif_0_irq = res->start; res = platform_get_resource(pdev, IORESOURCE_IRQ, 4); if (!res) { printk(KERN_ALERT "Unable to map Cmt_msg_pending_notif_audio IRQ base \n"); err = -EBUSY; goto rollback_intr; } shm_dev_data->ca_msg_pending_notif_1_irq = res->start; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { printk(KERN_ALERT "Could not get SHM IO memory information\n"); err = -ENODEV; goto rollback_intr; } shm_dev_data->intr_base = (void __iomem *)ioremap_nocache(res->start, \ res->end - res->start + 1); if (!(shm_dev_data->intr_base)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_intr; } shm_dev_data->ape_common_fifo_base_phy = \ (unsigned int *)U8500_SHM_FIFO_APE_COMMON_BASE; shm_dev_data->ape_common_fifo_base = \ (void __iomem *)ioremap_nocache( \ U8500_SHM_FIFO_APE_COMMON_BASE, \ SHM_FIFO_0_SIZE); shm_dev_data->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4; if (!(shm_dev_data->ape_common_fifo_base)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_ape_common_fifo_base; } shm_dev_data->cmt_common_fifo_base_phy = \ (unsigned int *)U8500_SHM_FIFO_CMT_COMMON_BASE; shm_dev_data->cmt_common_fifo_base = \ (void __iomem *)ioremap_nocache( \ U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE); shm_dev_data->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4; if (!(shm_dev_data->cmt_common_fifo_base)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_cmt_common_fifo_base; } shm_dev_data->ape_audio_fifo_base_phy = \ (unsigned int *)U8500_SHM_FIFO_APE_AUDIO_BASE; shm_dev_data->ape_audio_fifo_base = \ (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE, \ SHM_FIFO_1_SIZE); shm_dev_data->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; if (!(shm_dev_data->ape_audio_fifo_base)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_ape_audio_fifo_base; } shm_dev_data->cmt_audio_fifo_base_phy = \ (unsigned int *)U8500_SHM_FIFO_CMT_AUDIO_BASE; shm_dev_data->cmt_audio_fifo_base = \ (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE, \ SHM_FIFO_1_SIZE); shm_dev_data->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; if (!(shm_dev_data->cmt_audio_fifo_base)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_cmt_audio_fifo_base; } shm_dev_data->ac_common_shared_wptr = \ (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ac_common_shared_wptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_ac_common_shared_wptr; } shm_dev_data->ac_common_shared_rptr = \ (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ac_common_shared_rptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } shm_dev_data->ca_common_shared_wptr = \ (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ca_common_shared_wptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } shm_dev_data->ca_common_shared_rptr = \ (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ca_common_shared_rptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } shm_dev_data->ac_audio_shared_wptr = \ (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ac_audio_shared_wptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } shm_dev_data->ac_audio_shared_rptr = \ (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ac_audio_shared_rptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } shm_dev_data->ca_audio_shared_wptr = \ (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ca_audio_shared_wptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } shm_dev_data->ca_audio_shared_rptr = \ (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE); if (!(shm_dev_data->ca_audio_shared_rptr)) { printk(KERN_ALERT "Unable to map register base \n"); err = -EBUSY; goto rollback_map; } if (isa_init() != 0) { printk(KERN_ALERT "Driver Initialization Error \n"); err = -EBUSY; } /* install handlers and tasklets */ if (shm_initialise_irq(shm_dev_data)) { printk(KERN_ALERT "shm error in interrupt registration \n"); goto rollback_irq; } #ifdef CONFIG_HIGH_RES_TIMERS hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); timer.function = callback; hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL); #endif return err; rollback_irq: free_shm_irq(shm_dev_data); rollback_map: iounmap(pshm_dev->ac_common_shared_wptr); iounmap(pshm_dev->ac_common_shared_rptr); iounmap(pshm_dev->ca_common_shared_wptr); iounmap(pshm_dev->ca_common_shared_rptr); iounmap(pshm_dev->ac_audio_shared_wptr); iounmap(pshm_dev->ac_audio_shared_rptr); iounmap(pshm_dev->ca_audio_shared_wptr); iounmap(pshm_dev->ca_audio_shared_rptr); rollback_ac_common_shared_wptr: iounmap(pshm_dev->cmt_audio_fifo_base); rollback_cmt_audio_fifo_base: iounmap(pshm_dev->ape_audio_fifo_base); rollback_ape_audio_fifo_base: iounmap(pshm_dev->cmt_common_fifo_base); rollback_cmt_common_fifo_base: iounmap(pshm_dev->ape_common_fifo_base); rollback_ape_common_fifo_base: iounmap(pshm_dev->intr_base); rollback_intr: kfree(shm_dev_data); return err; } static int __exit shrm_remove(struct platform_device *pdev) { free_shm_irq(pshm_dev); iounmap(pshm_dev->intr_base); iounmap(pshm_dev->ape_common_fifo_base); iounmap(pshm_dev->cmt_common_fifo_base); iounmap(pshm_dev->ape_audio_fifo_base); iounmap(pshm_dev->cmt_audio_fifo_base); iounmap(pshm_dev->ac_common_shared_wptr); iounmap(pshm_dev->ac_common_shared_rptr); iounmap(pshm_dev->ca_common_shared_wptr); iounmap(pshm_dev->ca_common_shared_rptr); iounmap(pshm_dev->ac_audio_shared_wptr); iounmap(pshm_dev->ac_audio_shared_rptr); iounmap(pshm_dev->ca_audio_shared_wptr); iounmap(pshm_dev->ca_audio_shared_rptr); kfree(pshm_dev); isa_exit(); return 0; } #ifdef CONFIG_PM /** * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state. * @pdev: platform device. * * This routine checks the current ongoing communication with Modem by * examining the ca_wake state and prevents suspend if modem communication * is on-going. * If ca_wake = 1 (high), modem comm. is on-going; don't suspend * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend */ int u8500_shrm_suspend(struct platform_device *pdev, pm_message_t state) { dbgprintk("\n u8500_shrm_suspend: called...\n"); dbgprintk("\n ca_wake_req_state = %x\n", get_ca_wake_req_state()); /* if ca_wake_req is high, prevent system suspend */ if (get_ca_wake_req_state()) return -EBUSY; else return 0; } /** * u8500_shrm_resume() - This routine resumes the SHRM from sustend state. * @pdev: platform device. * * This routine restore back the current state of the SHRM */ int u8500_shrm_resume(struct platform_device *pdev) { dbgprintk("\n u8500_shrm_resume: called...\n"); /* TODO: * As of now, no state save takes place in suspend. * So, no restore required in resume. * Simply return as of now. * State saved in suspend should be restored here. */ return 0; } #else #define u8500_shrm_suspend NULL #define u8500_shrm_resume NULL #endif /* * struct shrm_driver: SHRM platform structure * @probe: The probe funtion to be called * @remove: The remove funtion to be called * @driver: The driver data */ static struct platform_driver shrm_driver = { .probe = shrm_probe, .remove = __exit_p(shrm_remove), .driver = { .name = "u8500_shrm", .owner = THIS_MODULE, }, #ifdef CONFIG_PM .suspend = u8500_shrm_suspend, .resume = u8500_shrm_resume, #endif }; static int __init shrm_driver_init(void) { int err = 0; err = platform_driver_probe(&shrm_driver, shrm_probe); if (err < 0) { printk(KERN_ALERT "SHM Platform register FAILED: %d\n", err); return err; } err = isa_open(NULL, NULL); dbg_printk(KERN_ALERT NAME "\n isa_open Done\n"); tasklet_init(&phonet_rcv_tasklet, do_phonet_rcv_tasklet, 0); dev = kmalloc(sizeof(struct net_device), GFP_ATOMIC); return 0; } static void __exit shrm_driver_exit(void) { kfree(dev); platform_driver_unregister(&shrm_driver); printk(KERN_ALERT "SHM Platform DRIVER removed\n"); } module_init(shrm_driver_init); module_exit(shrm_driver_exit); MODULE_AUTHOR("Biju Das"); MODULE_DESCRIPTION("Shared Memory Modem Driver Interface"); MODULE_LICENSE("GPL");