diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/sipc | |
Diffstat (limited to 'drivers/sipc')
| -rw-r--r-- | drivers/sipc/Kconfig | 69 | ||||
| -rw-r--r-- | drivers/sipc/Makefile | 16 | ||||
| -rw-r--r-- | drivers/sipc/dual_sim_plug.c | 543 | ||||
| -rw-r--r-- | drivers/sipc/sblock.c | 880 | ||||
| -rw-r--r-- | drivers/sipc/sblock.h | 103 | ||||
| -rw-r--r-- | drivers/sipc/sbuf.c | 651 | ||||
| -rw-r--r-- | drivers/sipc/sbuf.h | 82 | ||||
| -rw-r--r-- | drivers/sipc/seth.c | 1153 | ||||
| -rw-r--r-- | drivers/sipc/simple_events.h | 89 | ||||
| -rw-r--r-- | drivers/sipc/sipc.c | 488 | ||||
| -rw-r--r-- | drivers/sipc/sipc_debugfs.c | 45 | ||||
| -rw-r--r-- | drivers/sipc/smem.c | 180 | ||||
| -rw-r--r-- | drivers/sipc/smsg.c | 526 | ||||
| -rw-r--r-- | drivers/sipc/spipe.c | 336 | ||||
| -rw-r--r-- | drivers/sipc/spool.c | 260 | ||||
| -rw-r--r-- | drivers/sipc/stty.c | 412 |
16 files changed, 5833 insertions, 0 deletions
diff --git a/drivers/sipc/Kconfig b/drivers/sipc/Kconfig new file mode 100644 index 00000000..0a47fb72 --- /dev/null +++ b/drivers/sipc/Kconfig @@ -0,0 +1,69 @@ + +menu "SIPC modules" + +config SIPC + bool "Sprd IPC" + default n + help + SIPC is a module for spreadtrum AP/CP communicaiton system. + +config SIPC_SPIPE + bool "sprd pipe driver based on SBUF" + default n + depends on SIPC + help + This driver is a pipe driver base on SBUF, which create + general pipes between AP & CP. + +config SIPC_SPOOL + bool "sprd pool driver based on SBLOCK" + default n + depends on SIPC + help + This driver is a pool driver base on SBLOCK, which create + general pools between AP & CP. + +config SIPC_SETH + bool "Sprd Ethernet driver" + default n + depends on SIPC + help + This driver supports the Spreadtrum Ethernet based on share + memory. Say Y here if you want to use it. + +config SETH_OPT + bool "Enable performance optimization for SETH (2 Bytes reserved & US gather)" + default n + depends on SIPC_SETH + help + Starting from SharkL, the Seth driver is optimized for data thransfer + performance, but that is not compatible with former products + like TShark. Enable this configuration, IF the modem end surports + these optimizations: 2 Bytes reserved in sblock field and packets gathering + in upstreaming direction. + +config SETH_GRO_DISABLE + bool "Disable the gro function in seth" + default n + depends on SIPC_SETH + help + GRO function is incompatible with the samsung's GrandPrime, this will make + it happen kernel panic and IPV6 download too low. Enable this configuration + will let us to bypass these problems temporarily. + +config SIPC_STTY + bool "Sprd Stty driver" + default n + depends on SIPC + help + This driver supports the Spreadtrum Ethernet based on share + memory. Say Y here if you want to use it. + +config SIPC_DUAL_SIM_PLUG + bool "Sprd Dual sim plug driver" + default n + depends on SIPC + help + This driver supports the Spreadtrum dual sim plug. Say Y here if you want to use it. + +endmenu diff --git a/drivers/sipc/Makefile b/drivers/sipc/Makefile new file mode 100644 index 00000000..c92ad54b --- /dev/null +++ b/drivers/sipc/Makefile @@ -0,0 +1,16 @@ + +ifeq ($(CONFIG_OF), y) +obj-$(CONFIG_SIPC) += sipc.o smsg.o smem.o sbuf.o sblock.o sipc_debugfs.o +else +obj-$(CONFIG_SIPC) += smsg.o smem.o sbuf.o sblock.o sipc_debugfs.o +endif + +obj-$(CONFIG_SIPC_SPIPE) += spipe.o + +obj-$(CONFIG_SIPC_SETH) += seth.o + +obj-$(CONFIG_SIPC_SPOOL) += spool.o + +obj-$(CONFIG_SIPC_STTY) += stty.o + +obj-$(CONFIG_SIPC_DUAL_SIM_PLUG) += dual_sim_plug.o diff --git a/drivers/sipc/dual_sim_plug.c b/drivers/sipc/dual_sim_plug.c new file mode 100644 index 00000000..f7629ff9 --- /dev/null +++ b/drivers/sipc/dual_sim_plug.c @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/err.h> +#ifdef CONFIG_OF +#include <linux/of_device.h> +#endif + +#include <linux/sipc.h> + +#include <linux/dual_sim_plug.h> + +#define DELAY_COUNT 1000 + +#define VALUE_SIM1_PLUGIN 0x11 +#define VALUE_SIM1_PLUGOUT 0x10 +#define VALUE_SIM2_PLUGIN 0x21 +#define VALUE_SIM2_PLUGOUT 0x20 + + +#define SMSG_SIM_PLUG 0xA5A5 + +int flag_low_count_sim1 = 0; +int flag_high_count_sim1 = 0; + +int flag_low_count_sim2 = 0; +int flag_high_count_sim2 = 0; + +struct dual_sim_plug_device{ + struct dual_sim_plug_init_data *init; + struct task_struct *th1; + int irq_cd1; + int irq_cd2; + unsigned int sim1_detect_gpio; + unsigned int sim2_detect_gpio; +}; + +static irqreturn_t sim1_irq_cd(int irq, void *dev_id) +{ + struct dual_sim_plug_device *dual_sim_plug = (struct dual_sim_plug_device *)dev_id; + + struct smsg msend; + int flag = 0; + + //printk(KERN_INFO "Enter sim1_irq_cd\n"); + + flag = gpio_get_value(dual_sim_plug->sim1_detect_gpio); + + //printk(KERN_INFO "Enter sim1_irq_cd,flag = %d\n",flag); + + if(flag == 0) + { + flag_low_count_sim1++; + flag_high_count_sim1 = 0; + } + else + { + flag_high_count_sim1++; + flag_low_count_sim1= 0; + } + + if(flag_low_count_sim1 >= DELAY_COUNT) + { + flag_low_count_sim1 = 0; + irq_set_irq_type(irq,IRQF_TRIGGER_HIGH); + printk(KERN_INFO "Enter sim1_irq_cd,flag = low\n"); + smsg_set(&msend,dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM1_PLUGIN);//plug in + smsg_send(dual_sim_plug->init->dst,&msend,-1); + printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value); + + } + else if(flag_high_count_sim1 >= DELAY_COUNT) + { + flag_high_count_sim1 = 0; + irq_set_irq_type(irq,IRQF_TRIGGER_LOW); + printk(KERN_INFO "Enter sim1_irq_cd,flag = high\n"); + smsg_set(&msend,dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM1_PLUGOUT);//plug out + smsg_send(dual_sim_plug->init->dst,&msend, -1); + printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value); + + } + else + { + } + + return IRQ_HANDLED; + +} + +static irqreturn_t sim2_irq_cd (int irq, void *dev_id) +{ + struct dual_sim_plug_device *dual_sim_plug = (struct dual_sim_plug_device *)dev_id; + + struct smsg msend; + int flag = 0; + //printk(KERN_INFO "Enter sim2_irq_cd\n"); + flag = gpio_get_value(dual_sim_plug->sim2_detect_gpio); + //printk(KERN_INFO "Enter sim2_irq_cd,flag = %d\n",flag); + if(flag == 0) + { + flag_low_count_sim2++; + flag_high_count_sim2 = 0; + } + else + { + flag_high_count_sim2++; + flag_low_count_sim2 = 0; + } + + if(flag_low_count_sim2 >= DELAY_COUNT) + { + flag_low_count_sim2 = 0; + irq_set_irq_type(irq,IRQF_TRIGGER_HIGH); + printk(KERN_INFO "Enter sim2_irq_cd,flag = low\n"); + smsg_set(&msend, dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM2_PLUGIN);//plug in + smsg_send(dual_sim_plug->init->dst, &msend, -1); + printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value); + + } + else if(flag_high_count_sim2 >= DELAY_COUNT) + { + flag_high_count_sim2 = 0; + irq_set_irq_type(irq,IRQF_TRIGGER_LOW); + printk(KERN_INFO "Enter sim2_irq_cd,flag = high\n"); + smsg_set(&msend, dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM2_PLUGOUT);//plug out + smsg_send(dual_sim_plug->init->dst,&msend, -1); + printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value); + + } + else + { + } + + return IRQ_HANDLED; +} +static int dual_sim_plug_thread(void *data) +{ + struct dual_sim_plug_device *dual_sim_plug = (struct dual_sim_plug_device *)data; + struct smsg mrecv; + int rval; + struct sched_param param = {.sched_priority = 91}; + + sched_setscheduler(current,SCHED_RR,¶m); + printk(KERN_INFO "Enter dual_sim_plug_thread\n"); + + while (!kthread_should_stop()) + { + smsg_set(&mrecv, dual_sim_plug->init->channel,0,0,0); + rval = smsg_recv(dual_sim_plug->init->dst,&mrecv,-1); + printk(KERN_INFO "Enter dual_sim_plug_thread rval = %d,mrecv.channel = %d,mrecv.type =%d,mrecv.flag =%d,mrecv.value =%d\n",rval,mrecv.channel,mrecv.type,mrecv.flag,mrecv.value); + if (rval == -EIO) + { + msleep(5); + continue; + } + switch(mrecv.type) + { + case SMSG_TYPE_DONE: + if(mrecv.flag == SMSG_SIM_PLUG) + { + if(mrecv.value == VALUE_SIM1_PLUGOUT)//sim1 plug out + { + printk(KERN_INFO "Sim1 plug out done\n"); + } + else if(mrecv.value == VALUE_SIM1_PLUGIN)//sim1 plug in + { + printk(KERN_INFO "Sim1 plug in done\n"); + } + else if(mrecv.value == VALUE_SIM2_PLUGOUT)//sim2 plug out + { + printk(KERN_INFO "Sim2 plug out done\n"); + } + else if(mrecv.value == VALUE_SIM2_PLUGIN)//sim2 plug in + { + printk(KERN_INFO "Sim2 plug in done\n"); + } + else + { + + } + } + break; + case SMSG_TYPE_OPEN: + printk(KERN_INFO "SMSG_TYPE_OPEN!!!\n"); + smsg_open_ack(dual_sim_plug->init->dst, dual_sim_plug->init->channel); + break; + default: + break; + } + } + + return 0; + +} + +static inline void dual_sim_plug_destroy_pdata(struct dual_sim_plug_init_data **init) +{ +#ifdef CONFIG_OF + struct dual_sim_plug_init_data *pdata = *init; + + if (pdata) { + kfree(pdata); + } + + *init = NULL; +#else + return; +#endif +} + +static int dual_sim_plug_parse_dt(struct dual_sim_plug_init_data **init, struct device *dev) +{ +#ifdef CONFIG_OF + struct device_node *np = dev->of_node; + struct dual_sim_plug_init_data *pdata = NULL; + int ret; + uint32_t data; + + pdata = kzalloc(sizeof(struct dual_sim_plug_init_data),GFP_KERNEL); + if (!pdata) { + printk(KERN_ERR "Failed to allocate pdata memory\n"); + return -ENOMEM; + } + + ret = of_property_read_string(np,"sprd,name",(const char**)&pdata->name); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np,"sprd,dst",(uint32_t *)&data); + if (ret) { + goto error; + } + pdata->dst = (uint8_t)data; + + ret = of_property_read_u32(np,"sprd,channel",(uint32_t *)&data); + if (ret) { + goto error; + } + pdata->channel = (uint8_t)data; + + ret = of_property_read_u32(np,"sprd,sim1_gpio",(uint32_t *)&data); + if (ret) { + goto error; + } + pdata->sim1_gpio = (uint32_t)data; + + ret = of_property_read_u32(np,"sprd,sim2_gpio",(uint32_t *)&data); + if (ret) { + goto error; + } + pdata->sim2_gpio = (uint32_t)data; + + *init = pdata; + return ret; +error: + kfree(pdata); + *init = NULL; + return ret; +#else + return -ENODEV; +#endif +} + +static int dual_sim_plug_probe(struct platform_device *pdev)//add by xiangru.bi +{ + struct dual_sim_plug_init_data *init = pdev->dev.platform_data; + struct dual_sim_plug_device *dual_sim_plug; + int rval = 0; + int ret1,ret2; + int result; + int ret; + + //struct smsg msend; + + + printk(KERN_INFO "Dual_sim_plug_probe start\n"); + + if (pdev->dev.of_node && !init) { + rval = dual_sim_plug_parse_dt(&init, &pdev->dev); + if (rval) { + printk(KERN_ERR "Failed to parse spipe device tree, ret=%d\n", rval); + return rval; + } + } + + printk(KERN_INFO "Succed to parse spipe device tree, ret=%d,init->name=%s,init->dst=%d,init->channel=%d,init->sim1_gpio=%d,init->sim2_gpio=%d\n", rval,init->name,init->dst,init->channel,init->sim1_gpio,init->sim2_gpio); + + dual_sim_plug = kzalloc(sizeof(struct dual_sim_plug_device),GFP_KERNEL); + if (!dual_sim_plug) { + printk(KERN_ERR "Failed to allocate dual_sim_plug_device\n"); + ret = -ENOMEM; + goto probe_out; + } + + printk(KERN_INFO "Succed to allocate dual_sim_plug_device\n"); + + dual_sim_plug->sim1_detect_gpio = init->sim1_gpio; + dual_sim_plug->sim2_detect_gpio = init->sim2_gpio; + + dual_sim_plug->init = init; + + ret1 = gpio_request(dual_sim_plug->sim1_detect_gpio,"sim1_detect"); + if (ret1) + { + printk(KERN_ERR "failed to get sim1 detect gpio\n"); + ret = ret1; + goto probe_free_dual_sim_plug; + } + + gpio_direction_input(dual_sim_plug->sim1_detect_gpio); + + ret1 = gpio_get_value(dual_sim_plug->sim1_detect_gpio); + if(ret1 == 0) + { + printk(KERN_INFO "Sim1 detect gpio = low.\n"); + } + else + { + printk(KERN_INFO "Sim1 detect gpio = high.\n"); + } + + + printk(KERN_INFO "Succed to get sim1 detect gpio\n"); + + dual_sim_plug ->irq_cd1 = gpio_to_irq(dual_sim_plug->sim1_detect_gpio); + + printk(KERN_INFO "Succed to get sim1 irq dual_sim_plug ->irq_cd1 =%d\n",dual_sim_plug ->irq_cd1); + if (dual_sim_plug ->irq_cd1 >= 0) + { + if(ret1 == 0) + { + if (request_irq(dual_sim_plug ->irq_cd1,sim1_irq_cd,IRQF_TRIGGER_HIGH,"sim1_detect_hander",dual_sim_plug)) + { + printk(KERN_ERR "can't get sim1 detect irq.\n"); + ret = -ENOENT; + goto probe_free_sim1_gpio; + } + printk(KERN_INFO "Get sim1 detect irq.\n"); + } + else + { + if (request_irq(dual_sim_plug ->irq_cd1,sim1_irq_cd,IRQF_TRIGGER_LOW,"sim1_detect_hander",dual_sim_plug)) + { + printk(KERN_ERR "can't get sim1 detect irq.\n"); + ret = -ENOENT; + goto probe_free_sim1_gpio; + } + printk(KERN_INFO "Get sim1 detect irq.\n"); + } + } + else + { + printk(KERN_INFO "host detect has no irq available\n"); + } + + ret2 = gpio_request(dual_sim_plug->sim2_detect_gpio, "sim2_detect"); + if (ret2) + { + printk(KERN_ERR "failed to get sim2 detect gpio\n"); + ret = ret2; + goto probe_free_sim1_irq; + + } + + gpio_direction_input(dual_sim_plug->sim2_detect_gpio); + + ret2 = gpio_get_value(dual_sim_plug->sim2_detect_gpio); + if(ret2 == 0) + { + printk(KERN_INFO "Sim2 detect gpio = low.\n"); + } + else + { + printk(KERN_INFO "Sim2 detect gpio = high.\n"); + } + + + printk(KERN_INFO "Succed to get sim2 detect gpio\n"); + + dual_sim_plug ->irq_cd2 = gpio_to_irq(dual_sim_plug->sim2_detect_gpio); + printk(KERN_INFO "Succed to get sim2 irq dual_sim_plug ->irq_cd2 =%d\n",dual_sim_plug ->irq_cd2); + if (dual_sim_plug ->irq_cd2 >= 0) + { + if(ret2 == 0) + { + if (request_irq(dual_sim_plug ->irq_cd2,sim2_irq_cd,IRQF_TRIGGER_HIGH,"sim2_detect_hander", dual_sim_plug)) + { + printk(KERN_ERR "can't get sim2 detect irq.\n"); + ret = -ENOENT; + goto probe_free_sim2_gpio; + } + printk(KERN_INFO "Get sim2 detect irq.\n"); + } + else + { + if (request_irq(dual_sim_plug ->irq_cd2,sim2_irq_cd,IRQF_TRIGGER_LOW,"sim2_detect_hander", dual_sim_plug)) + { + printk(KERN_ERR "can't get sim2 detect irq.\n"); + ret = -ENOENT; + goto probe_free_sim2_gpio; + } + printk(KERN_INFO "Get sim2 detect irq.\n"); + } + } + else + { + printk(KERN_INFO "host detect has no irq available\n"); + } + + rval = smsg_ch_open(init->dst,init->channel,-1); + + if (rval != 0) { + printk(KERN_ERR "Failed to open channel: %d\n",init->channel); + ret = rval; + goto probe_free_sim2_irq; + } + + printk(KERN_INFO "Succed to open channel: %d\n",init->channel); + + dual_sim_plug->th1 = kthread_create(dual_sim_plug_thread,dual_sim_plug,"dual_sim_plug-%d-%d",init->dst,init->channel); + + if (IS_ERR(dual_sim_plug->th1)) + { + printk(KERN_ERR "Failed to create kthread: dual_sim_plug-%d-%d\n",init->dst,init->channel); + result = PTR_ERR(dual_sim_plug->th1); + ret = result; + goto probe_close_ch; + } + + printk(KERN_INFO "Succed to create kthread: dual_sim_plug-%d-%d\n",init->dst,init->channel); + + wake_up_process(dual_sim_plug->th1); + + platform_set_drvdata(pdev, dual_sim_plug); + + return 0; + +probe_close_ch: + smsg_ch_close(init->dst,init->channel,-1); +probe_free_sim2_irq: + free_irq(dual_sim_plug ->irq_cd2,dual_sim_plug); +probe_free_sim2_gpio: + gpio_free(dual_sim_plug->sim2_detect_gpio); + probe_free_sim1_irq: + free_irq(dual_sim_plug ->irq_cd1,dual_sim_plug); + probe_free_sim1_gpio: + gpio_free(dual_sim_plug->sim1_detect_gpio); +probe_free_dual_sim_plug: + kfree(dual_sim_plug); +probe_out: + dual_sim_plug_destroy_pdata(&init); + return ret; +} + +static int dual_sim_plug_remove(struct platform_device *pdev) +{ + + printk(KERN_INFO "Dual_sim_plug_remove start\n"); + struct dual_sim_plug_device *dual_sim_plug = platform_get_drvdata(pdev); + + printk(KERN_INFO "dual_sim_plug->init->dst = %d,dual_sim_plug->init->channel = %d,dual_sim_plug->init->sim1_gpio=%d,dual_sim_plug->init->sim2_gpio=%d\n",dual_sim_plug->init->dst,dual_sim_plug->init->channel,dual_sim_plug->init->sim1_gpio,dual_sim_plug->init->sim2_gpio); + + if (!IS_ERR_OR_NULL(dual_sim_plug->th1)) { + kthread_stop(dual_sim_plug->th1); + } + + smsg_ch_close(dual_sim_plug->init->dst,dual_sim_plug->init->channel,-1); + + free_irq(dual_sim_plug ->irq_cd2,dual_sim_plug); + + gpio_free(dual_sim_plug->sim2_detect_gpio); + + free_irq(dual_sim_plug ->irq_cd1,dual_sim_plug); + + gpio_free(dual_sim_plug->sim1_detect_gpio); + + dual_sim_plug_destroy_pdata(&dual_sim_plug->init); + + kfree(dual_sim_plug); + + platform_set_drvdata(pdev,NULL); + + return 0; +} + +static const struct of_device_id dual_sim_plug_match_table[] = { + {.compatible = "sprd,dual_sim_plug", }, + { }, +}; + +static struct platform_driver dual_sim_plug_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "dual_sim_plug", + .of_match_table = dual_sim_plug_match_table, + }, + .probe = dual_sim_plug_probe, + .remove = dual_sim_plug_remove, +}; + +static int __init dual_sim_plug_init(void) +{ + printk(KERN_INFO "Dual_sim_plug_init start\n"); + return platform_driver_register(&dual_sim_plug_driver); +} + +static void __exit dual_sim_plug_exit(void) +{ + printk(KERN_INFO "Dual_sim_plug_exit start\n"); + platform_driver_unregister(&dual_sim_plug_driver); +} + +module_init(dual_sim_plug_init); +module_exit(dual_sim_plug_exit); + +MODULE_AUTHOR("Bi Xiangru"); +MODULE_DESCRIPTION("SIPC/DUAL_SIM_PLUG driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/sblock.c b/drivers/sipc/sblock.c new file mode 100644 index 00000000..7f73180f --- /dev/null +++ b/drivers/sipc/sblock.c @@ -0,0 +1,880 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <asm/uaccess.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/log2.h> + +#include <linux/sipc.h> +#include "sblock.h" + +static struct sblock_mgr *sblocks[SIPC_ID_NR][SMSG_CH_NR]; + +static inline uint32_t sblock_get_index(uint32_t x, uint32_t y) +{ + return (x / y); +} + +static inline uint32_t sblock_get_ringpos(uint32_t x, uint32_t y) +{ + return is_power_of_2(y) ? (x & (y - 1)) : (x % y); +} + +void sblock_put(uint8_t dst, uint8_t channel, struct sblock *blk) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + unsigned long flags; + int txpos; + int index; + + if (!sblock) { + return; + } + + ring = sblock->ring; + poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool); + + spin_lock_irqsave(&ring->p_txlock, flags); + txpos = sblock_get_ringpos(poolhd->txblk_rdptr - 1, poolhd->txblk_count); + ring->r_txblks[txpos].addr = blk->addr - sblock->smem_virt + sblock->smem_addr; + ring->r_txblks[txpos].length = poolhd->txblk_size; + poolhd->txblk_rdptr = poolhd->txblk_rdptr - 1; + if ((int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr) == 1) { + wake_up_interruptible_all(&(ring->getwait)); + } + index = sblock_get_index((blk->addr - ring->txblk_virt), sblock->txblksz); + ring->txrecord[index] = SBLOCK_BLK_STATE_DONE; + + spin_unlock_irqrestore(&ring->p_txlock, flags); +} + +static int sblock_recover(uint8_t dst, uint8_t channel) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *ringhd = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + unsigned long pflags, qflags; + int i, j; + + if (!sblock) { + return -ENODEV; + } + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool); + + sblock->state = SBLOCK_STATE_IDLE; + wake_up_interruptible_all(&ring->getwait); + wake_up_interruptible_all(&ring->recvwait); + + spin_lock_irqsave(&ring->r_txlock, pflags); + /* clean txblks ring */ + ringhd->txblk_wrptr = ringhd->txblk_rdptr; + + spin_lock_irqsave(&ring->p_txlock, qflags); + /* recover txblks pool */ + poolhd->txblk_rdptr = poolhd->txblk_wrptr; + for (i = 0, j = 0; i < poolhd->txblk_count; i++) { + if (ring->txrecord[i] == SBLOCK_BLK_STATE_DONE) { + ring->p_txblks[j].addr = i * sblock->txblksz + poolhd->txblk_addr; + ring->p_txblks[j].length = sblock->txblksz; + poolhd->txblk_wrptr = poolhd->txblk_wrptr + 1; + j++; + } + } + spin_unlock_irqrestore(&ring->p_txlock, qflags); + spin_unlock_irqrestore(&ring->r_txlock, pflags); + + + spin_lock_irqsave(&ring->r_rxlock, pflags); + /* clean rxblks ring */ + ringhd->rxblk_rdptr = ringhd->rxblk_wrptr; + + spin_lock_irqsave(&ring->p_rxlock, qflags); + /* recover rxblks pool */ + poolhd->rxblk_wrptr = poolhd->rxblk_rdptr; + for (i = 0, j = 0; i < poolhd->rxblk_count; i++) { + if (ring->rxrecord[i] == SBLOCK_BLK_STATE_DONE) { + ring->p_rxblks[j].addr = i * sblock->rxblksz + poolhd->rxblk_addr; + ring->p_rxblks[j].length = sblock->rxblksz; + poolhd->rxblk_wrptr = poolhd->rxblk_wrptr + 1; + j++; + } + } + spin_unlock_irqrestore(&ring->p_rxlock, qflags); + spin_unlock_irqrestore(&ring->r_rxlock, pflags); + + return 0; +} + +static int sblock_thread(void *data) +{ + struct sblock_mgr *sblock = data; + struct smsg mcmd, mrecv; + int rval; + int recovery = 0; + struct sched_param param = {.sched_priority = 90}; + + /*set the thread as a real time thread, and its priority is 90*/ + sched_setscheduler(current, SCHED_RR, ¶m); + + /* since the channel open may hang, we call it in the sblock thread */ + rval = smsg_ch_open(sblock->dst, sblock->channel, -1); + if (rval != 0) { + printk(KERN_ERR "Failed to open channel %d\n", sblock->channel); + /* assign NULL to thread poniter as failed to open channel */ + sblock->thread = NULL; + return rval; + } + + /* handle the sblock events */ + while (!kthread_should_stop()) { + + /* monitor sblock recv smsg */ + smsg_set(&mrecv, sblock->channel, 0, 0, 0); + rval = smsg_recv(sblock->dst, &mrecv, -1); + if (rval == -EIO || rval == -ENODEV) { + /* channel state is FREE */ + msleep(5); + continue; + } + + pr_debug("sblock thread recv msg: dst=%d, channel=%d, " + "type=%d, flag=0x%04x, value=0x%08x\n", + sblock->dst, sblock->channel, + mrecv.type, mrecv.flag, mrecv.value); + + switch (mrecv.type) { + case SMSG_TYPE_OPEN: + /* handle channel recovery */ + if (recovery) { + if (sblock->handler) { + sblock->handler(SBLOCK_NOTIFY_CLOSE, sblock->data); + } + sblock_recover(sblock->dst, sblock->channel); + } + smsg_open_ack(sblock->dst, sblock->channel); + break; + case SMSG_TYPE_CLOSE: + /* handle channel recovery */ + smsg_close_ack(sblock->dst, sblock->channel); + if (sblock->handler) { + sblock->handler(SBLOCK_NOTIFY_CLOSE, sblock->data); + } + sblock->state = SBLOCK_STATE_IDLE; + break; + case SMSG_TYPE_CMD: + /* respond cmd done for sblock init */ + WARN_ON(mrecv.flag != SMSG_CMD_SBLOCK_INIT); + smsg_set(&mcmd, sblock->channel, SMSG_TYPE_DONE, + SMSG_DONE_SBLOCK_INIT, sblock->smem_addr); + smsg_send(sblock->dst, &mcmd, -1); + if (sblock->handler) { + sblock->handler(SBLOCK_NOTIFY_OPEN, sblock->data); + } + sblock->state = SBLOCK_STATE_READY; + recovery = 1; + break; + case SMSG_TYPE_EVENT: + /* handle sblock send/release events */ + switch (mrecv.flag) { + case SMSG_EVENT_SBLOCK_SEND: + wake_up_interruptible_all(&sblock->ring->recvwait); + if (sblock->handler) { + sblock->handler(SBLOCK_NOTIFY_RECV, sblock->data); + } + break; + case SMSG_EVENT_SBLOCK_RELEASE: + wake_up_interruptible_all(&(sblock->ring->getwait)); + if (sblock->handler) { + sblock->handler(SBLOCK_NOTIFY_GET, sblock->data); + } + break; + default: + rval = 1; + break; + } + break; + default: + rval = 1; + break; + }; + if (rval) { + printk(KERN_WARNING "non-handled sblock msg: %d-%d, %d, %d, %d\n", + sblock->dst, sblock->channel, + mrecv.type, mrecv.flag, mrecv.value); + rval = 0; + } + } + + printk(KERN_WARNING "sblock %d-%d thread stop", sblock->dst, sblock->channel); + return rval; +} + +int sblock_create(uint8_t dst, uint8_t channel, + uint32_t txblocknum, uint32_t txblocksize, + uint32_t rxblocknum, uint32_t rxblocksize) +{ + struct sblock_mgr *sblock = NULL; + volatile struct sblock_ring_header *ringhd = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + uint32_t hsize; + int i, result; + + sblock = kzalloc(sizeof(struct sblock_mgr) , GFP_KERNEL); + if (!sblock) { + return -ENOMEM; + } + + sblock->state = SBLOCK_STATE_IDLE; + sblock->dst = dst; + sblock->channel = channel; + sblock->txblksz = txblocksize; + sblock->rxblksz = rxblocksize; + sblock->txblknum = txblocknum; + sblock->rxblknum = rxblocknum; + + + /* allocate smem */ + hsize = sizeof(struct sblock_header); + sblock->smem_size = hsize + /* for header*/ + txblocknum * txblocksize + rxblocknum * rxblocksize + /* for blks */ + (txblocknum + rxblocknum) * sizeof(struct sblock_blks) + /* for ring*/ + (txblocknum + rxblocknum) * sizeof(struct sblock_blks); /* for pool*/ + + sblock->smem_addr = smem_alloc(sblock->smem_size); + if (!sblock->smem_addr) { + printk(KERN_ERR "Failed to allocate smem for sblock\n"); + kfree(sblock); + return -ENOMEM; + } + sblock->smem_virt = ioremap_nocache(sblock->smem_addr, sblock->smem_size); + if (!sblock->smem_virt) { + printk(KERN_ERR "Failed to map smem for sblock\n"); + smem_free(sblock->smem_addr, sblock->smem_size); + kfree(sblock); + return -EFAULT; + } + + /* initialize ring and header */ + sblock->ring = kzalloc(sizeof(struct sblock_ring), GFP_KERNEL); + if (!sblock->ring) { + printk(KERN_ERR "Failed to allocate ring for sblock\n"); + iounmap(sblock->smem_virt); + smem_free(sblock->smem_addr, sblock->smem_size); + kfree(sblock); + return -ENOMEM; + } + ringhd = (volatile struct sblock_ring_header *)(sblock->smem_virt); + ringhd->txblk_addr = sblock->smem_addr + hsize; + ringhd->txblk_count = txblocknum; + ringhd->txblk_size = txblocksize; + ringhd->txblk_rdptr = 0; + ringhd->txblk_wrptr = 0; + ringhd->txblk_blks = sblock->smem_addr + hsize + + txblocknum * txblocksize + rxblocknum * rxblocksize; + ringhd->rxblk_addr = ringhd->txblk_addr + txblocknum * txblocksize; + ringhd->rxblk_count = rxblocknum; + ringhd->rxblk_size = rxblocksize; + ringhd->rxblk_rdptr = 0; + ringhd->rxblk_wrptr = 0; + ringhd->rxblk_blks = ringhd->txblk_blks + txblocknum * sizeof(struct sblock_blks); + + poolhd = (volatile struct sblock_ring_header *)(sblock->smem_virt + sizeof(struct sblock_ring_header)); + poolhd->txblk_addr = sblock->smem_addr + hsize; + poolhd->txblk_count = txblocknum; + poolhd->txblk_size = txblocksize; + poolhd->txblk_rdptr = 0; + poolhd->txblk_wrptr = 0; + poolhd->txblk_blks = ringhd->rxblk_blks + rxblocknum * sizeof(struct sblock_blks); + poolhd->rxblk_addr = ringhd->txblk_addr + txblocknum * txblocksize; + poolhd->rxblk_count = rxblocknum; + poolhd->rxblk_size = rxblocksize; + poolhd->rxblk_rdptr = 0; + poolhd->rxblk_wrptr = 0; + poolhd->rxblk_blks = poolhd->txblk_blks + txblocknum * sizeof(struct sblock_blks); + + sblock->ring->txrecord = kzalloc(sizeof(int) * txblocknum, GFP_KERNEL); + if (!sblock->ring->txrecord) { + printk(KERN_ERR "Failed to allocate memory for txrecord\n"); + iounmap(sblock->smem_virt); + smem_free(sblock->smem_addr, sblock->smem_size); + kfree(sblock->ring); + kfree(sblock); + return -ENOMEM; + } + + sblock->ring->rxrecord = kzalloc(sizeof(int) * rxblocknum, GFP_KERNEL); + if (!sblock->ring->rxrecord) { + printk(KERN_ERR "Failed to allocate memory for rxrecord\n"); + iounmap(sblock->smem_virt); + smem_free(sblock->smem_addr, sblock->smem_size); + kfree(sblock->ring->txrecord); + kfree(sblock->ring); + kfree(sblock); + return -ENOMEM; + } + + sblock->ring->header = sblock->smem_virt; + sblock->ring->txblk_virt = sblock->smem_virt + + (ringhd->txblk_addr - sblock->smem_addr); + sblock->ring->r_txblks = sblock->smem_virt + + (ringhd->txblk_blks - sblock->smem_addr); + sblock->ring->rxblk_virt = sblock->smem_virt + + (ringhd->rxblk_addr - sblock->smem_addr); + sblock->ring->r_rxblks = sblock->smem_virt + + (ringhd->rxblk_blks - sblock->smem_addr); + sblock->ring->p_txblks = sblock->smem_virt + + (poolhd->txblk_blks - sblock->smem_addr); + sblock->ring->p_rxblks = sblock->smem_virt + + (poolhd->rxblk_blks - sblock->smem_addr); + + + for (i = 0; i < txblocknum; i++) { + sblock->ring->p_txblks[i].addr = poolhd->txblk_addr + i * txblocksize; + sblock->ring->p_txblks[i].length = txblocksize; + sblock->ring->txrecord[i] = SBLOCK_BLK_STATE_DONE; + poolhd->txblk_wrptr++; + } + for (i = 0; i < rxblocknum; i++) { + sblock->ring->p_rxblks[i].addr = poolhd->rxblk_addr + i * rxblocksize; + sblock->ring->p_rxblks[i].length = rxblocksize; + sblock->ring->rxrecord[i] = SBLOCK_BLK_STATE_DONE; + poolhd->rxblk_wrptr++; + } + + init_waitqueue_head(&sblock->ring->getwait); + init_waitqueue_head(&sblock->ring->recvwait); + spin_lock_init(&sblock->ring->r_txlock); + spin_lock_init(&sblock->ring->r_rxlock); + spin_lock_init(&sblock->ring->p_txlock); + spin_lock_init(&sblock->ring->p_rxlock); + + sblock->thread = kthread_create(sblock_thread, sblock, + "sblock-%d-%d", dst, channel); + if (IS_ERR(sblock->thread)) { + printk(KERN_ERR "Failed to create kthread: sblock-%d-%d\n", dst, channel); + iounmap(sblock->smem_virt); + smem_free(sblock->smem_addr, sblock->smem_size); + kfree(sblock->ring->txrecord); + kfree(sblock->ring->rxrecord); + kfree(sblock->ring); + result = PTR_ERR(sblock->thread); + kfree(sblock); + return result; + } + + sblocks[dst][channel]=sblock; + wake_up_process(sblock->thread); + + return 0; +} + +void sblock_destroy(uint8_t dst, uint8_t channel) +{ + struct sblock_mgr *sblock = sblocks[dst][channel]; + + if (sblock == NULL) { + return; + } + + sblock->state = SBLOCK_STATE_IDLE; + smsg_ch_close(dst, channel, -1); + + /* stop sblock thread if it's created successfully and still alive */ + if (!IS_ERR_OR_NULL(sblock->thread)) { + kthread_stop(sblock->thread); + } + + if (sblock->ring) { + wake_up_interruptible_all(&sblock->ring->recvwait); + wake_up_interruptible_all(&sblock->ring->getwait); + if (sblock->ring->txrecord) { + kfree(sblock->ring->txrecord); + } + if (sblock->ring->rxrecord) { + kfree(sblock->ring->rxrecord); + } + kfree(sblock->ring); + } + if (sblock->smem_virt) { + iounmap(sblock->smem_virt); + } + smem_free(sblock->smem_addr, sblock->smem_size); + kfree(sblock); + + sblocks[dst][channel]=NULL; +} + +int sblock_register_notifier(uint8_t dst, uint8_t channel, + void (*handler)(int event, void *data), void *data) +{ + struct sblock_mgr *sblock = sblocks[dst][channel]; + + if (!sblock) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return -ENODEV; + } +#ifndef CONFIG_SIPC_WCN + if (sblock->handler) { + printk(KERN_ERR "sblock handler already registered\n"); + return -EBUSY; + } +#endif + sblock->handler = handler; + sblock->data = data; + + return 0; +} + +int sblock_get(uint8_t dst, uint8_t channel, struct sblock *blk, int timeout) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *ringhd = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + int txpos, index; + int rval = 0; + unsigned long flags; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return sblock ? -EIO : -ENODEV; + } + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool); + + if (poolhd->txblk_rdptr == poolhd->txblk_wrptr) { + if (timeout == 0) { + /* no wait */ + printk(KERN_WARNING "sblock_get %d-%d is empty!\n", + dst, channel); + rval = -ENODATA; + } else if (timeout < 0) { + /* wait forever */ + rval = wait_event_interruptible(ring->getwait, + poolhd->txblk_rdptr != poolhd->txblk_wrptr || + sblock->state == SBLOCK_STATE_IDLE); + if (rval < 0) { + printk(KERN_WARNING "sblock_get wait interrupted!\n"); + } + + if (sblock->state == SBLOCK_STATE_IDLE) { + printk(KERN_ERR "sblock_get sblock state is idle!\n"); + rval = -EIO; + } + } else { + /* wait timeout */ + rval = wait_event_interruptible_timeout(ring->getwait, + poolhd->txblk_rdptr != poolhd->txblk_wrptr || + sblock == SBLOCK_STATE_IDLE, + timeout); + if (rval < 0) { + printk(KERN_WARNING "sblock_get wait interrupted!\n"); + } else if (rval == 0) { + printk(KERN_WARNING "sblock_get wait timeout!\n"); + rval = -ETIME; + } + + if(sblock->state == SBLOCK_STATE_IDLE) { + printk(KERN_ERR "sblock_get sblock state is idle!\n"); + rval = -EIO; + } + } + } + + if (rval < 0) { + return rval; + } + + /* multi-gotter may cause got failure */ + spin_lock_irqsave(&ring->p_txlock, flags); + if (poolhd->txblk_rdptr != poolhd->txblk_wrptr && + sblock->state == SBLOCK_STATE_READY) { + txpos = sblock_get_ringpos(poolhd->txblk_rdptr, poolhd->txblk_count); + blk->addr = sblock->smem_virt + (ring->p_txblks[txpos].addr - sblock->smem_addr); + blk->length = poolhd->txblk_size; + poolhd->txblk_rdptr = poolhd->txblk_rdptr + 1; + index = sblock_get_index((blk->addr - ring->txblk_virt), sblock->txblksz); + ring->txrecord[index] = SBLOCK_BLK_STATE_PENDING; + } else { + rval = sblock->state == SBLOCK_STATE_READY ? -EAGAIN : -EIO; + } + spin_unlock_irqrestore(&ring->p_txlock, flags); + + return rval; +} + +static int sblock_send_ex(uint8_t dst, uint8_t channel, struct sblock *blk, bool yell) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring; + volatile struct sblock_ring_header *ringhd; + struct smsg mevt; + int txpos, index; + int rval = 0; + unsigned long flags; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return sblock ? -EIO : -ENODEV; + } + + pr_debug("sblock_send: dst=%d, channel=%d, addr=%p, len=%d\n", + dst, channel, blk->addr, blk->length); + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + + spin_lock_irqsave(&ring->r_txlock, flags); + + txpos = sblock_get_ringpos(ringhd->txblk_wrptr, ringhd->txblk_count); + ring->r_txblks[txpos].addr = blk->addr - sblock->smem_virt + sblock->smem_addr; + ring->r_txblks[txpos].length = blk->length; + pr_debug("sblock_send: channel=%d, wrptr=%d, txpos=%d, addr=%x\n", + channel, ringhd->txblk_wrptr, txpos, ring->r_txblks[txpos].addr); + ringhd->txblk_wrptr = ringhd->txblk_wrptr + 1; + if (sblock->state == SBLOCK_STATE_READY) { + if(yell) { + smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBLOCK_SEND, 0); + rval = smsg_send(dst, &mevt, 0); + } + else if(!ring->yell) { + if(((int)(ringhd->txblk_wrptr - ringhd->txblk_rdptr) == 1) /*&& + ((int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr) == (sblock->txblknum - 1))*/) { + ring->yell = 1; + } + } + } + index = sblock_get_index((blk->addr - ring->txblk_virt), sblock->txblksz); + ring->txrecord[index] = SBLOCK_BLK_STATE_DONE; + + spin_unlock_irqrestore(&ring->r_txlock, flags); + + return rval ; +} + +int sblock_send(uint8_t dst, uint8_t channel, struct sblock *blk) +{ + return sblock_send_ex(dst, channel, blk, true); +} + +int sblock_send_prepare(uint8_t dst, uint8_t channel, struct sblock *blk) +{ + return sblock_send_ex(dst, channel, blk, false); +} + +int sblock_send_finish(uint8_t dst, uint8_t channel) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring; + volatile struct sblock_ring_header *ringhd; + struct smsg mevt; + int rval=0; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return sblock ? -EIO : -ENODEV; + } + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + + if (ringhd->txblk_wrptr != ringhd->txblk_rdptr) { + smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBLOCK_SEND, 0); + rval = smsg_send(dst, &mevt, 0); + } + + return rval; +} + +int sblock_receive(uint8_t dst, uint8_t channel, struct sblock *blk, int timeout) +{ + struct sblock_mgr *sblock = sblocks[dst][channel]; + struct sblock_ring *ring; + volatile struct sblock_ring_header *ringhd; + int rxpos, index, rval = 0; + unsigned long flags; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return sblock ? -EIO : -ENODEV; + } + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + + pr_debug("sblock_receive: dst=%d, channel=%d, timeout=%d\n", + dst, channel, timeout); + pr_debug("sblock_receive: channel=%d, wrptr=%d, rdptr=%d", + channel, ringhd->rxblk_wrptr, ringhd->rxblk_rdptr); + + if (ringhd->rxblk_wrptr == ringhd->rxblk_rdptr) { + if (timeout == 0) { + /* no wait */ + pr_debug("sblock_receive %d-%d is empty!\n", + dst, channel); + rval = -ENODATA; + } else if (timeout < 0) { + /* wait forever */ + rval = wait_event_interruptible(ring->recvwait, + ringhd->rxblk_wrptr != ringhd->rxblk_rdptr); + if (rval < 0) { + printk(KERN_WARNING "sblock_receive wait interrupted!\n"); + } + + if (sblock->state == SBLOCK_STATE_IDLE) { + printk(KERN_ERR "sblock_receive sblock state is idle!\n"); + rval = -EIO; + } + + } else { + /* wait timeout */ + rval = wait_event_interruptible_timeout(ring->recvwait, + ringhd->rxblk_wrptr != ringhd->rxblk_rdptr, timeout); + if (rval < 0) { + printk(KERN_WARNING "sblock_receive wait interrupted!\n"); + } else if (rval == 0) { + printk(KERN_WARNING "sblock_receive wait timeout!\n"); + rval = -ETIME; + } + + if (sblock->state == SBLOCK_STATE_IDLE) { + printk(KERN_ERR "sblock_receive sblock state is idle!\n"); + rval = -EIO; + } + } + } + + if (rval < 0) { + return rval; + } + + /* multi-receiver may cause recv failure */ + spin_lock_irqsave(&ring->r_rxlock, flags); + + if (ringhd->rxblk_wrptr != ringhd->rxblk_rdptr && + sblock->state == SBLOCK_STATE_READY) { + rxpos = sblock_get_ringpos(ringhd->rxblk_rdptr, ringhd->rxblk_count); + blk->addr = ring->r_rxblks[rxpos].addr - sblock->smem_addr + sblock->smem_virt; + blk->length = ring->r_rxblks[rxpos].length; + ringhd->rxblk_rdptr = ringhd->rxblk_rdptr + 1; + pr_debug("sblock_receive: channel=%d, rxpos=%d, addr=%p, len=%d\n", + channel, rxpos, blk->addr, blk->length); + index = sblock_get_index((blk->addr - ring->rxblk_virt), sblock->rxblksz); + ring->rxrecord[index] = SBLOCK_BLK_STATE_PENDING; + } else { + rval = sblock->state == SBLOCK_STATE_READY ? -EAGAIN : -EIO; + } + spin_unlock_irqrestore(&ring->r_rxlock, flags); + + return rval; +} + +int sblock_get_arrived_count(uint8_t dst, uint8_t channel) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *ringhd = NULL; + int blk_count = 0; + unsigned long flags; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return -ENODEV; + } + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + + spin_lock_irqsave(&ring->r_rxlock, flags); + blk_count = (int)(ringhd->rxblk_wrptr - ringhd->rxblk_rdptr); + spin_unlock_irqrestore(&ring->r_rxlock, flags); + + return blk_count; + +} + +int sblock_get_free_count(uint8_t dst, uint8_t channel) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + int blk_count = 0; + unsigned long flags; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return -ENODEV; + } + + ring = sblock->ring; + poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool); + + spin_lock_irqsave(&ring->p_txlock, flags); + blk_count = (int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr); + spin_unlock_irqrestore(&ring->p_txlock, flags); + + return blk_count; +} + +int sblock_release(uint8_t dst, uint8_t channel, struct sblock *blk) +{ + struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel]; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *ringhd = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + struct smsg mevt; + unsigned long flags; + int rxpos; + int index; + + if (!sblock || sblock->state != SBLOCK_STATE_READY) { + printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel); + return -ENODEV; + } + + pr_debug("sblock_release: dst=%d, channel=%d, addr=%p, len=%d\n", + dst, channel, blk->addr, blk->length); + + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring); + poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool); + + spin_lock_irqsave(&ring->p_rxlock, flags); + rxpos = sblock_get_ringpos(poolhd->rxblk_wrptr, poolhd->rxblk_count); + ring->p_rxblks[rxpos].addr = blk->addr - sblock->smem_virt + sblock->smem_addr; + ring->p_rxblks[rxpos].length = poolhd->rxblk_size; + poolhd->rxblk_wrptr = poolhd->rxblk_wrptr + 1; + pr_debug("sblock_release: addr=%x\n", ring->p_rxblks[rxpos].addr); + + if((int)(poolhd->rxblk_wrptr - poolhd->rxblk_rdptr) == 1 && + sblock->state == SBLOCK_STATE_READY) { + /* send smsg to notify the peer side */ + smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBLOCK_RELEASE, 0); + smsg_send(dst, &mevt, -1); + } + + index = sblock_get_index((blk->addr - ring->rxblk_virt), sblock->rxblksz); + ring->rxrecord[index] = SBLOCK_BLK_STATE_DONE; + + spin_unlock_irqrestore(&ring->p_rxlock, flags); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +static int sblock_debug_show(struct seq_file *m, void *private) +{ + struct sblock_mgr *sblock = NULL; + struct sblock_ring *ring = NULL; + volatile struct sblock_ring_header *ringhd = NULL; + volatile struct sblock_ring_header *poolhd = NULL; + int i, j; + + for (i = 0; i < SIPC_ID_NR; i++) { + for (j=0; j < SMSG_CH_NR; j++) { + sblock = sblocks[i][j]; + if (!sblock) { + continue; + } + ring = sblock->ring; + ringhd = (volatile struct sblock_ring_header *)(&sblock->ring->header->ring); + poolhd = (volatile struct sblock_ring_header *)(&sblock->ring->header->pool); + + seq_printf(m, "sblock dst 0x%0x, channel: 0x%0x, state: %d, smem_virt: 0x%lx, smem_addr: 0x%0x, smem_size: 0x%0x, txblksz: %d, rxblksz: %d \n", + sblock->dst, sblock->channel, sblock->state, + (size_t)sblock->smem_virt, sblock->smem_addr, + sblock->smem_size, sblock->txblksz, sblock->rxblksz ); + seq_printf(m, "sblock ring: txblk_virt :0x%lx, rxblk_virt :0x%lx \n", + (size_t)ring->txblk_virt, (size_t)ring->rxblk_virt); + seq_printf(m, "sblock ring header: rxblk_addr :0x%0x, rxblk_rdptr :0x%0x, rxblk_wrptr :0x%0x, rxblk_size :%d, rxblk_count :%d, rxblk_blks: 0x%0x \n", + ringhd->rxblk_addr, ringhd->rxblk_rdptr, + ringhd->rxblk_wrptr, ringhd->rxblk_size, + ringhd->rxblk_count, ringhd->rxblk_blks); + seq_printf(m, "sblock ring header: txblk_addr :0x%0x, txblk_rdptr :0x%0x, txblk_wrptr :0x%0x, txblk_size :%d, txblk_count :%d, txblk_blks: 0x%0x \n", + ringhd->txblk_addr, ringhd->txblk_rdptr, + ringhd->txblk_wrptr, ringhd->txblk_size, + ringhd->txblk_count, ringhd->txblk_blks ); + seq_printf(m, "sblock pool header: rxblk_addr :0x%0x, rxblk_rdptr :0x%0x, rxblk_wrptr :0x%0x, rxblk_size :%d, rxpool_count :%d, rxblk_blks: 0x%0x \n", + poolhd->rxblk_addr, poolhd->rxblk_rdptr, + poolhd->rxblk_wrptr, poolhd->rxblk_size, + (int)(poolhd->rxblk_wrptr - poolhd->rxblk_rdptr), + poolhd->rxblk_blks); + seq_printf(m, "sblock pool header: txblk_addr :0x%0x, txblk_rdptr :0x%0x, txblk_wrptr :0x%0x, txblk_size :%d, txpool_count :%d, txblk_blks: 0x%0x \n", + poolhd->txblk_addr, poolhd->txblk_rdptr, + poolhd->txblk_wrptr, poolhd->txblk_size, + (int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr), + poolhd->txblk_blks ); + } + } + return 0; + +} + +static int sblock_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, sblock_debug_show, inode->i_private); +} + +static const struct file_operations sblock_debug_fops = { + .open = sblock_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int sblock_init_debugfs(void *root ) +{ + if (!root) + return -ENXIO; + debugfs_create_file("sblock", S_IRUGO, (struct dentry *)root, NULL, &sblock_debug_fops); + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +EXPORT_SYMBOL(sblock_put); +EXPORT_SYMBOL(sblock_create); +EXPORT_SYMBOL(sblock_destroy); +EXPORT_SYMBOL(sblock_register_notifier); +EXPORT_SYMBOL(sblock_get); +EXPORT_SYMBOL(sblock_send); +EXPORT_SYMBOL(sblock_send_prepare); +EXPORT_SYMBOL(sblock_send_finish); +EXPORT_SYMBOL(sblock_receive); +EXPORT_SYMBOL(sblock_get_arrived_count); +EXPORT_SYMBOL(sblock_get_free_count); +EXPORT_SYMBOL(sblock_release); + +MODULE_AUTHOR("Chen Gaopeng"); +MODULE_DESCRIPTION("SIPC/SBLOCK driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/sblock.h b/drivers/sipc/sblock.h new file mode 100644 index 00000000..4d8770b0 --- /dev/null +++ b/drivers/sipc/sblock.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __SBLOCK_H +#define __SBLOCK_H + +/* flag for CMD/DONE msg type */ +#define SMSG_CMD_SBLOCK_INIT 0x0001 +#define SMSG_DONE_SBLOCK_INIT 0x0002 + +/* flag for EVENT msg type */ +#define SMSG_EVENT_SBLOCK_SEND 0x0001 +#define SMSG_EVENT_SBLOCK_RELEASE 0x0002 + +#define SBLOCK_STATE_IDLE 0 +#define SBLOCK_STATE_READY 1 + +#define SBLOCK_BLK_STATE_DONE 0 +#define SBLOCK_BLK_STATE_PENDING 1 + +struct sblock_blks { + uint32_t addr; /*phy address*/ + uint32_t length; +}; + +/* ring block header */ +struct sblock_ring_header { + /* get|send-block info */ + uint32_t txblk_addr; + uint32_t txblk_count; + uint32_t txblk_size; + uint32_t txblk_blks; + uint32_t txblk_rdptr; + uint32_t txblk_wrptr; + + /* release|recv-block info */ + uint32_t rxblk_addr; + uint32_t rxblk_count; + uint32_t rxblk_size; + uint32_t rxblk_blks; + uint32_t rxblk_rdptr; + uint32_t rxblk_wrptr; +}; + +struct sblock_header { + struct sblock_ring_header ring; + struct sblock_ring_header pool; +}; + +struct sblock_ring { + struct sblock_header *header; + void *txblk_virt; /* virt of header->txblk_addr */ + void *rxblk_virt; /* virt of header->rxblk_addr */ + + struct sblock_blks *r_txblks; /* virt of header->ring->txblk_blks */ + struct sblock_blks *r_rxblks; /* virt of header->ring->rxblk_blks */ + struct sblock_blks *p_txblks; /* virt of header->pool->txblk_blks */ + struct sblock_blks *p_rxblks; /* virt of header->pool->rxblk_blks */ + + int *txrecord; /* record the state of every txblk */ + int *rxrecord; /* record the state of every rxblk */ + int yell; /* need to notify cp */ + spinlock_t r_txlock; /* send */ + spinlock_t r_rxlock; /* recv */ + spinlock_t p_txlock; /* get */ + spinlock_t p_rxlock; /* release */ + + wait_queue_head_t getwait; + wait_queue_head_t recvwait; +}; + +struct sblock_mgr { + uint8_t dst; + uint8_t channel; + uint32_t state; + + void *smem_virt; + uint32_t smem_addr; + uint32_t smem_size; + + uint32_t txblksz; + uint32_t rxblksz; + uint32_t txblknum; + uint32_t rxblknum; + + struct sblock_ring *ring; + struct task_struct *thread; + + void (*handler)(int event, void *data); + void *data; +}; + +#endif diff --git a/drivers/sipc/sbuf.c b/drivers/sipc/sbuf.c new file mode 100644 index 00000000..aade2816 --- /dev/null +++ b/drivers/sipc/sbuf.c @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <asm/uaccess.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + + +#include <linux/sipc.h> +#include "sbuf.h" + + +static struct sbuf_mgr *sbufs[SIPC_ID_NR][SMSG_CH_NR]; + +static int sbuf_thread(void *data) +{ + struct sbuf_mgr *sbuf = data; + struct smsg mcmd, mrecv; + int rval, bufid; + struct sched_param param = {.sched_priority = 90}; + + /*set the thread as a real time thread, and its priority is 90*/ + sched_setscheduler(current, SCHED_RR, ¶m); + + /* since the channel open may hang, we call it in the sbuf thread */ + rval = smsg_ch_open(sbuf->dst, sbuf->channel, -1); + if (rval != 0) { + printk(KERN_ERR "Failed to open channel %d\n", sbuf->channel); + /* assign NULL to thread poniter as failed to open channel */ + sbuf->thread = NULL; + return rval; + } + + /* sbuf init done, handle the ring rx events */ + while (!kthread_should_stop()) { + /* monitor sbuf rdptr/wrptr update smsg */ + smsg_set(&mrecv, sbuf->channel, 0, 0, 0); + rval = smsg_recv(sbuf->dst, &mrecv, -1); + + if (rval == -EIO) { + /* channel state is free */ + msleep(5); + continue; + } + + pr_debug("sbuf thread recv msg: dst=%d, channel=%d, " + "type=%d, flag=0x%04x, value=0x%08x\n", + sbuf->dst, sbuf->channel, + mrecv.type, mrecv.flag, mrecv.value); + + switch (mrecv.type) { + case SMSG_TYPE_OPEN: + /* handle channel recovery */ + smsg_open_ack(sbuf->dst, sbuf->channel); + break; + case SMSG_TYPE_CLOSE: + /* handle channel recovery */ + smsg_close_ack(sbuf->dst, sbuf->channel); + sbuf->state = SBUF_STATE_IDLE; + break; + case SMSG_TYPE_CMD: + /* respond cmd done for sbuf init */ + WARN_ON(mrecv.flag != SMSG_CMD_SBUF_INIT); + smsg_set(&mcmd, sbuf->channel, SMSG_TYPE_DONE, + SMSG_DONE_SBUF_INIT, sbuf->smem_addr); + smsg_send(sbuf->dst, &mcmd, -1); + sbuf->state = SBUF_STATE_READY; + break; + case SMSG_TYPE_EVENT: + bufid = mrecv.value; + WARN_ON(bufid >= sbuf->ringnr); + switch (mrecv.flag) { + case SMSG_EVENT_SBUF_RDPTR: + wake_up_interruptible_all(&(sbuf->rings[bufid].txwait)); + if (sbuf->rings[bufid].handler) { + sbuf->rings[bufid].handler(SBUF_NOTIFY_WRITE, sbuf->rings[bufid].data); + } + break; + case SMSG_EVENT_SBUF_WRPTR: + wake_up_interruptible_all(&(sbuf->rings[bufid].rxwait)); + if (sbuf->rings[bufid].handler) { + sbuf->rings[bufid].handler(SBUF_NOTIFY_READ, sbuf->rings[bufid].data); + } + break; + default: + rval = 1; + break; + } + break; + default: + rval = 1; + break; + }; + if (rval) { + printk(KERN_WARNING "non-handled sbuf msg: %d-%d, %d, %d, %d\n", + sbuf->dst, sbuf->channel, + mrecv.type, mrecv.flag, mrecv.value); + rval = 0; + } + } + + return 0; +} + +int sbuf_create(uint8_t dst, uint8_t channel, uint32_t bufnum, + uint32_t txbufsize, uint32_t rxbufsize) +{ + struct sbuf_mgr *sbuf; + volatile struct sbuf_smem_header *smem; + volatile struct sbuf_ring_header *ringhd; + int hsize, i, result; + + sbuf = kzalloc(sizeof(struct sbuf_mgr), GFP_KERNEL); + if (!sbuf) { + printk(KERN_ERR "Failed to allocate mgr for sbuf\n"); + return -ENOMEM; + } + + sbuf->state = SBUF_STATE_IDLE; + sbuf->dst = dst; + sbuf->channel = channel; + sbuf->ringnr = bufnum; + + /* allocate smem */ + hsize = sizeof(struct sbuf_smem_header) + sizeof(struct sbuf_ring_header) * bufnum; + sbuf->smem_size = hsize + (txbufsize + rxbufsize) * bufnum; + sbuf->smem_addr = smem_alloc(sbuf->smem_size); + if (!sbuf->smem_addr) { + printk(KERN_ERR "Failed to allocate smem for sbuf\n"); + kfree(sbuf); + return -ENOMEM; + } + sbuf->smem_virt = ioremap_nocache(sbuf->smem_addr, sbuf->smem_size); + if (!sbuf->smem_virt) { + printk(KERN_ERR "Failed to map smem for sbuf\n"); + smem_free(sbuf->smem_addr, sbuf->smem_size); + kfree(sbuf); + return -EFAULT; + } + + /* allocate rings description */ + sbuf->rings = kzalloc(sizeof(struct sbuf_ring) * bufnum, GFP_KERNEL); + if (!sbuf->rings) { + printk(KERN_ERR "Failed to allocate rings for sbuf\n"); + iounmap(sbuf->smem_virt); + smem_free(sbuf->smem_addr, sbuf->smem_size); + kfree(sbuf); + return -ENOMEM; + } + + /* initialize all ring bufs */ + smem = (volatile struct sbuf_smem_header *)sbuf->smem_virt; + smem->ringnr = bufnum; + for (i = 0; i < bufnum; i++) { + ringhd = (volatile struct sbuf_ring_header *)&(smem->headers[i]); + ringhd->txbuf_addr = sbuf->smem_addr + hsize + + (txbufsize + rxbufsize) * i; + ringhd->txbuf_size = txbufsize; + ringhd->txbuf_rdptr = 0; + ringhd->txbuf_wrptr = 0; + ringhd->rxbuf_addr = smem->headers[i].txbuf_addr + txbufsize; + ringhd->rxbuf_size = rxbufsize; + ringhd->rxbuf_rdptr = 0; + ringhd->rxbuf_wrptr = 0; + + sbuf->rings[i].header = ringhd; + sbuf->rings[i].txbuf_virt = sbuf->smem_virt + hsize + + (txbufsize + rxbufsize) * i; + sbuf->rings[i].rxbuf_virt = sbuf->rings[i].txbuf_virt + txbufsize; + init_waitqueue_head(&(sbuf->rings[i].txwait)); + init_waitqueue_head(&(sbuf->rings[i].rxwait)); + mutex_init(&(sbuf->rings[i].txlock)); + mutex_init(&(sbuf->rings[i].rxlock)); + } + + sbuf->thread = kthread_create(sbuf_thread, sbuf, + "sbuf-%d-%d", dst, channel); + if (IS_ERR(sbuf->thread)) { + printk(KERN_ERR "Failed to create kthread: sbuf-%d-%d\n", dst, channel); + kfree(sbuf->rings); + iounmap(sbuf->smem_virt); + smem_free(sbuf->smem_addr, sbuf->smem_size); + result = PTR_ERR(sbuf->thread); + kfree(sbuf); + + return result; + } + + sbufs[dst][channel] = sbuf; + wake_up_process(sbuf->thread); + + return 0; +} + +void sbuf_destroy(uint8_t dst, uint8_t channel) +{ + struct sbuf_mgr *sbuf = sbufs[dst][channel]; + int i; + + if (sbuf == NULL) { + return; + } + + sbuf->state = SBUF_STATE_IDLE; + smsg_ch_close(dst, channel, -1); + + /* stop sbuf thread if it's created successfully and still alive */ + if (!IS_ERR_OR_NULL(sbuf->thread)) { + kthread_stop(sbuf->thread); + } + + if (sbuf->rings) { + for (i = 0; i < sbuf->ringnr; i++) { + wake_up_interruptible_all(&sbuf->rings[i].txwait); + wake_up_interruptible_all(&sbuf->rings[i].rxwait); + } + kfree(sbuf->rings); + } + + if (sbuf->smem_virt) { + iounmap(sbuf->smem_virt); + } + smem_free(sbuf->smem_addr, sbuf->smem_size); + kfree(sbuf); + + sbufs[dst][channel] = NULL; +} + +int sbuf_write(uint8_t dst, uint8_t channel, uint32_t bufid, + void *buf, uint32_t len, int timeout) +{ + struct sbuf_mgr *sbuf = sbufs[dst][channel]; + struct sbuf_ring *ring = NULL; + volatile struct sbuf_ring_header *ringhd = NULL; + struct smsg mevt; + void *txpos; + int rval, left, tail, txsize; + + if (!sbuf) { + return -ENODEV; + } + ring = &(sbuf->rings[bufid]); + ringhd = ring->header; + if (sbuf->state != SBUF_STATE_READY) { + printk(KERN_ERR "sbuf-%d-%d not ready to write!\n", dst, channel); + return -ENODEV; + } + + pr_debug("sbuf_write: dst=%d, channel=%d, bufid=%d, len=%d, timeout=%d\n", + dst, channel, bufid, len, timeout); + pr_debug("sbuf_write: channel=%d, wrptr=%d, rdptr=%d", + channel, ringhd->txbuf_wrptr, ringhd->txbuf_rdptr); + + rval = 0; + left = len; + + if (timeout) { + mutex_lock(&ring->txlock); + } else { + if (!mutex_trylock(&(ring->txlock))) { + printk(KERN_INFO "sbuf_write busy!\n"); + return -EBUSY; + } + } + + if (timeout == 0) { + /* no wait */ + if ((int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) >= + ringhd->txbuf_size) { + printk(KERN_WARNING "sbuf %d-%d ring %d txbuf is full!\n", + dst, channel, bufid); + rval = -EBUSY; + } + } else if (timeout < 0) { + /* wait forever */ + rval = wait_event_interruptible(ring->txwait, + (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) < + ringhd->txbuf_size || sbuf->state == SBUF_STATE_IDLE); + if (rval < 0) { + printk(KERN_WARNING "sbuf_write wait interrupted!\n"); + } + + if (sbuf->state == SBUF_STATE_IDLE) { + printk(KERN_ERR "sbuf_write sbuf state is idle!\n"); + rval = -EIO; + } + } else { + /* wait timeout */ + rval = wait_event_interruptible_timeout(ring->txwait, + (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) < + ringhd->txbuf_size || sbuf->state == SBUF_STATE_IDLE, + timeout); + if (rval < 0) { + printk(KERN_WARNING "sbuf_write wait interrupted!\n"); + } else if (rval == 0) { + printk(KERN_WARNING "sbuf_write wait timeout!\n"); + rval = -ETIME; + } + + if (sbuf->state == SBUF_STATE_IDLE) { + printk(KERN_ERR "sbuf_write sbuf state is idle!\n"); + rval = -EIO; + } + + } + + while (left && (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) < ringhd->txbuf_size && + sbuf->state == SBUF_STATE_READY) { + /* calc txpos & txsize */ + txpos = ring->txbuf_virt + ringhd->txbuf_wrptr % ringhd->txbuf_size; + txsize = ringhd->txbuf_size - (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr); + txsize = min(txsize, left); + + tail = txpos + txsize - (ring->txbuf_virt + ringhd->txbuf_size); + if (tail > 0) { + /* ring buffer is rounded */ + if ((uintptr_t)buf > TASK_SIZE) { + unalign_memcpy(txpos, buf, txsize - tail); + unalign_memcpy(ring->txbuf_virt, buf + txsize - tail, tail); + } else { + if(unalign_copy_from_user(txpos, (void __user *)buf, txsize - tail) || + unalign_copy_from_user(ring->txbuf_virt, + (void __user *)(buf + txsize - tail), tail)) { + printk(KERN_ERR "sbuf_write: failed to copy from user!\n"); + rval = -EFAULT; + break; + } + } + } else { + if ((uintptr_t)buf > TASK_SIZE) { + unalign_memcpy(txpos, buf, txsize); + } else { + /* handle the user space address */ + if(unalign_copy_from_user(txpos, (void __user *)buf, txsize)) { + printk(KERN_ERR "sbuf_write: failed to copy from user!\n"); + rval = -EFAULT; + break; + } + } + } + + + pr_debug("sbuf_write: channel=%d, txpos=%p, txsize=%d\n", channel, txpos, txsize); + + /* update tx wrptr */ + ringhd->txbuf_wrptr = ringhd->txbuf_wrptr + txsize; + /* tx ringbuf is empty, so need to notify peer side */ + if(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr == txsize) { + smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBUF_WRPTR, bufid); + smsg_send(dst, &mevt, -1); + } + + left -= txsize; + buf += txsize; + } + + mutex_unlock(&ring->txlock); + + pr_debug("sbuf_write done: channel=%d, len=%d\n", channel, len - left); + + if (len == left) { + return rval; + } else { + return (len - left); + } +} + +int sbuf_read(uint8_t dst, uint8_t channel, uint32_t bufid, + void *buf, uint32_t len, int timeout) +{ + struct sbuf_mgr *sbuf = sbufs[dst][channel]; + struct sbuf_ring *ring = NULL; + volatile struct sbuf_ring_header *ringhd = NULL; + struct smsg mevt; + void *rxpos; + int rval, left, tail, rxsize; + + if (!sbuf) { + return -ENODEV; + } + ring = &(sbuf->rings[bufid]); + ringhd = ring->header; + + if (sbuf->state != SBUF_STATE_READY) { + printk(KERN_ERR "sbuf-%d-%d not ready to read!\n", dst, channel); + return -ENODEV; + } + + pr_debug("sbuf_read: dst=%d, channel=%d, bufid=%d, len=%d, timeout=%d\n", + dst, channel, bufid, len, timeout); + pr_debug("sbuf_read: channel=%d, wrptr=%d, rdptr=%d", + channel, ringhd->rxbuf_wrptr, ringhd->rxbuf_rdptr); + + rval = 0; + left = len; + + if (timeout) { + mutex_lock(&ring->rxlock); + } else { + if (!mutex_trylock(&(ring->rxlock))) { + printk(KERN_INFO "sbuf_read busy!\n"); + return -EBUSY; + } + } + + if (ringhd->rxbuf_wrptr == ringhd->rxbuf_rdptr) { + if (timeout == 0) { + /* no wait */ + printk(KERN_WARNING "sbuf %d-%d ring %d rxbuf is empty!\n", + dst, channel, bufid); + rval = -ENODATA; + } else if (timeout < 0) { + /* wait forever */ + rval = wait_event_interruptible(ring->rxwait, + ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr || + sbuf->state == SBUF_STATE_IDLE); + if (rval < 0) { + printk(KERN_WARNING "sbuf_read wait interrupted!\n"); + } + + if (sbuf->state == SBUF_STATE_IDLE) { + printk(KERN_ERR "sbuf_read sbuf state is idle!\n"); + rval = -EIO; + } + } else { + /* wait timeout */ + rval = wait_event_interruptible_timeout(ring->rxwait, + ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr || + sbuf->state == SBUF_STATE_IDLE, timeout); + if (rval < 0) { + printk(KERN_WARNING "sbuf_read wait interrupted!\n"); + } else if (rval == 0) { + printk(KERN_WARNING "sbuf_read wait timeout!\n"); + rval = -ETIME; + } + + if (sbuf->state == SBUF_STATE_IDLE) { + printk(KERN_ERR "sbuf_read sbuf state is idle!\n"); + rval = -EIO; + } + } + } + + while (left && (ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr) && + sbuf->state == SBUF_STATE_READY) { + /* calc rxpos & rxsize */ + rxpos = ring->rxbuf_virt + ringhd->rxbuf_rdptr % ringhd->rxbuf_size; + rxsize = (int)(ringhd->rxbuf_wrptr - ringhd->rxbuf_rdptr); + /* check overrun */ + WARN_ON(rxsize > ringhd->rxbuf_size); + rxsize = min(rxsize, left); + + pr_debug("sbuf_read: channel=%d, buf=%p, rxpos=%p, rxsize=%d\n", channel, buf, rxpos, rxsize); + + tail = rxpos + rxsize - (ring->rxbuf_virt + ringhd->rxbuf_size); + + if (tail > 0) { + /* ring buffer is rounded */ + if ((uintptr_t)buf > TASK_SIZE) { + unalign_memcpy(buf, rxpos, rxsize - tail); + unalign_memcpy(buf + rxsize - tail, ring->rxbuf_virt, tail); + } else { + /* handle the user space address */ + if(unalign_copy_to_user((void __user *)buf, rxpos, rxsize - tail) || + unalign_copy_to_user((void __user *)(buf + rxsize - tail), + ring->rxbuf_virt, tail)) { + printk(KERN_ERR "sbuf_read: failed to copy to user!\n"); + rval = -EFAULT; + break; + } + } + } else { + if ((uintptr_t)buf > TASK_SIZE) { + unalign_memcpy(buf, rxpos, rxsize); + } else { + /* handle the user space address */ + if (unalign_copy_to_user((void __user *)buf, rxpos, rxsize)) { + printk(KERN_ERR "sbuf_read: failed to copy to user!\n"); + rval = -EFAULT; + break; + } + } + } + + /* update rx rdptr */ + ringhd->rxbuf_rdptr = ringhd->rxbuf_rdptr + rxsize; + /* rx ringbuf is full ,so need to notify peer side */ + if(ringhd->rxbuf_wrptr - ringhd->rxbuf_rdptr == ringhd->rxbuf_size - rxsize) { + smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBUF_RDPTR, bufid); + smsg_send(dst, &mevt, -1); + } + + left -= rxsize; + buf += rxsize; + } + + mutex_unlock(&ring->rxlock); + + pr_debug("sbuf_read done: channel=%d, len=%d", channel, len - left); + + if (len == left) { + return rval; + } else { + return (len - left); + } +} + +int sbuf_poll_wait(uint8_t dst, uint8_t channel, uint32_t bufid, + struct file *filp, poll_table *wait) +{ + struct sbuf_mgr *sbuf = sbufs[dst][channel]; + struct sbuf_ring *ring = NULL; + volatile struct sbuf_ring_header *ringhd = NULL; + unsigned int mask = 0; + + if (!sbuf) { + return -ENODEV; + } + ring = &(sbuf->rings[bufid]); + ringhd = ring->header; + if (sbuf->state != SBUF_STATE_READY) { + printk(KERN_ERR "sbuf-%d-%d not ready to poll !\n", dst, channel); + return -ENODEV; + } + + poll_wait(filp, &ring->txwait, wait); + poll_wait(filp, &ring->rxwait, wait); + + if (ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr) { + mask |= POLLIN | POLLRDNORM; + } + + if (ringhd->txbuf_wrptr - ringhd->txbuf_rdptr < ringhd->txbuf_size) { + mask |= POLLOUT | POLLWRNORM; + } + + return mask; +} + +int sbuf_status(uint8_t dst, uint8_t channel) +{ + struct sbuf_mgr *sbuf = sbufs[dst][channel]; + + if (!sbuf) { + return -ENODEV; + } + if (sbuf->state != SBUF_STATE_READY) { + return -ENODEV; + } + + return 0; +} + +int sbuf_register_notifier(uint8_t dst, uint8_t channel, uint32_t bufid, + void (*handler)(int event, void *data), void *data) +{ + struct sbuf_mgr *sbuf = sbufs[dst][channel]; + struct sbuf_ring *ring = NULL; + + if (!sbuf) { + return -ENODEV; + } + ring = &(sbuf->rings[bufid]); + ring->handler = handler; + ring->data = data; + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) + +static int sbuf_debug_show(struct seq_file *m, void *private) +{ + struct sbuf_mgr *sbuf = NULL; + struct sbuf_ring *rings = NULL; + volatile struct sbuf_ring_header *ring = NULL; + int i, j, n; + + for (i = 0; i < SIPC_ID_NR; i++) { + for (j=0; j< SMSG_CH_NR; j++) { + sbuf = sbufs[i][j]; + if (!sbuf) { + continue; + } + seq_printf(m, "sbuf dst 0x%0x, channel: 0x%0x, state: %d, smem_virt: 0x%lx, smem_addr: 0x%0x, smem_size: 0x%0x, ringnr: %d \n", + sbuf->dst, sbuf->channel, sbuf->state, (size_t)sbuf->smem_virt, sbuf->smem_addr, sbuf->smem_size, sbuf->ringnr); + + for (n=0; n < sbuf->ringnr; n++) { + rings = &(sbuf->rings[n]); + ring = rings->header; + seq_printf(m, "sbuf ring[%d]: rxbuf_addr :0x%0x, rxbuf_rdptr :0x%0x, rxbuf_wrptr :0x%0x, rxbuf_size :0x%0x \n", n, ring->rxbuf_addr, ring->rxbuf_rdptr, ring->rxbuf_wrptr, ring->rxbuf_size); + seq_printf(m, "sbuf ring[%d]: txbuf_addr :0x%0x, txbuf_rdptr :0x%0x, txbuf_wrptr :0x%0x, txbuf_size :0x%0x \n", n, ring->txbuf_addr, ring->txbuf_rdptr, ring->txbuf_wrptr, ring->txbuf_size); + } + } + } + return 0; +} + +static int sbuf_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, sbuf_debug_show, inode->i_private); +} + +static const struct file_operations sbuf_debug_fops = { + .open = sbuf_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int sbuf_init_debugfs( void *root ) +{ + if (!root) + return -ENXIO; + debugfs_create_file("sbuf", S_IRUGO, (struct dentry *)root, NULL, &sbuf_debug_fops); + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + + +EXPORT_SYMBOL(sbuf_create); +EXPORT_SYMBOL(sbuf_destroy); +EXPORT_SYMBOL(sbuf_write); +EXPORT_SYMBOL(sbuf_read); +EXPORT_SYMBOL(sbuf_poll_wait); +EXPORT_SYMBOL(sbuf_status); +EXPORT_SYMBOL(sbuf_register_notifier); + +MODULE_AUTHOR("Chen Gaopeng"); +MODULE_DESCRIPTION("SIPC/SBUF driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/sbuf.h b/drivers/sipc/sbuf.h new file mode 100644 index 00000000..cb14a783 --- /dev/null +++ b/drivers/sipc/sbuf.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __SBUF_H +#define __SBUF_H + +/* flag for CMD/DONE msg type */ +#define SMSG_CMD_SBUF_INIT 0x0001 +#define SMSG_DONE_SBUF_INIT 0x0002 + +/* flag for EVENT msg type */ +#define SMSG_EVENT_SBUF_WRPTR 0x0001 +#define SMSG_EVENT_SBUF_RDPTR 0x0002 + +/* ring buf header */ +struct sbuf_ring_header { + /* send-buffer info */ + uint32_t txbuf_addr; + uint32_t txbuf_size; + uint32_t txbuf_rdptr; + uint32_t txbuf_wrptr; + + /* recv-buffer info */ + uint32_t rxbuf_addr; + uint32_t rxbuf_size; + uint32_t rxbuf_rdptr; + uint32_t rxbuf_wrptr; +}; + +/* sbuf_mem is the structure of smem for rings */ +struct sbuf_smem_header { + uint32_t ringnr; + + struct sbuf_ring_header headers[0]; +}; + +struct sbuf_ring { + /* tx/rx buffer info */ + volatile struct sbuf_ring_header *header; + + void *txbuf_virt; + void *rxbuf_virt; + + /* send/recv wait queue */ + wait_queue_head_t txwait; + wait_queue_head_t rxwait; + + /* send/recv mutex */ + struct mutex txlock; + struct mutex rxlock; + + void (*handler)(int event, void *data); + void *data; +}; + +#define SBUF_STATE_IDLE 0 +#define SBUF_STATE_READY 1 + +struct sbuf_mgr { + uint8_t dst; + uint8_t channel; + uint32_t state; + + void *smem_virt; + uint32_t smem_addr; + uint32_t smem_size; + uint32_t ringnr; + struct sbuf_ring *rings; + struct task_struct *thread; +}; + +#endif diff --git a/drivers/sipc/seth.c b/drivers/sipc/seth.c new file mode 100644 index 00000000..e8e3d406 --- /dev/null +++ b/drivers/sipc/seth.c @@ -0,0 +1,1153 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/ipv6.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/icmp.h> +#include <linux/icmpv6.h> +#include <asm/byteorder.h> +#include <linux/tty.h> +#include <linux/platform_device.h> +#include <linux/atomic.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#endif + +#ifdef CONFIG_OF +#include <linux/of_device.h> +#endif + +#include <linux/sipc.h> +#include <linux/seth.h> + +#define seth_max(a,b) ((a) > (b) ? (a) : (b)) +#define seth_min(a,b) ((a) < (b) ? (a) : (b)) + +/* debugging macros */ +#define SETH_INFO(x...) pr_info("SETH: " x) +#define SETH_DEBUG(x...) pr_debug("SETH: " x) +#define SETH_ERR(x...) pr_err("SETH: Error: " x) + +/* defination of sblock length */ +#define SETH_BLOCK_SIZE (ETH_HLEN + ETH_DATA_LEN + NET_IP_ALIGN) + +/* device status */ +#define DEV_ON 1 +#define DEV_OFF 0 + +/* enable NAPI */ +#define SETH_NAPI + +/* tx pkt return value */ +#define SETH_TX_SUCCESS 0 +#define SETH_TX_NO_BLK -1 +#define SETH_TX_INVALID_BLK -2 + +/* Macro of Packet Field */ +#define SETH_IPV4_HLEN 20 +#define SETH_IPV6_HLEN 40 +#define SETH_ETH_HLEN ETH_HLEN +#define SETH_ETH_FRAME_LEN ETH_FRAME_LEN +#define SETH_IPV4_VERSION 0x04 +#define SETH_IPV6_VERSION 0x06 + +/* Macro of protocol type */ +#define SETH_PRO_TCP 6 +#define SETH_PRO_ICMP 1 + +/* Macro of ICMP type */ +#define SETH_ICMP_PING ICMP_ECHO +#define SETH_ICMP6_PING ICMPV6_ECHO_REQUEST + +#ifdef SETH_NAPI +#define SETH_NAPI_WEIGHT 64 +#define SETH_TX_WEIGHT 16 + +/* struct of data transfer statistics */ +struct seth_dtrans_stats { + uint32_t rx_pkt_max; + uint32_t rx_pkt_min; + uint32_t rx_sum; + uint32_t rx_cnt; + uint32_t rx_alloc_fails; + + uint32_t tx_pkt_max; + uint32_t tx_pkt_min; + uint32_t tx_sum; + uint32_t tx_cnt; + uint32_t tx_ping_cnt; + uint32_t tx_ack_cnt; +}; + +#endif /* end of SETH_NAPI*/ + +/* + * Device instance data. + */ +typedef struct SEth { + struct net_device_stats stats; /* net statistics */ + struct net_device* netdev; /* Linux net device */ + struct seth_init_data* pdata; /* platform data */ + int state; /* device state */ + int txstate; /* device txstate */ + +#ifdef SETH_NAPI + atomic_t rx_busy; + struct timer_list rx_timer; + + atomic_t txpending; /* seth tx resend count*/ + struct timer_list tx_timer; + struct napi_struct napi; /* napi instance */ + struct seth_dtrans_stats dt_stats; /* record data_transfer statistics*/ +#endif /* SETH_NAPI */ +} SEth; + +#ifdef CONFIG_DEBUG_FS +struct dentry *root = NULL; +uint32_t seth_print_seq = 0; +static int seth_debugfs_mknod(void *root, void * data); +#endif + +#ifdef SETH_NAPI +static void seth_rx_timer_handler(unsigned long data); + +static inline void seth_dt_stats_init(struct seth_dtrans_stats * stats) +{ + memset(stats, 0, sizeof(struct seth_dtrans_stats)); + stats->rx_pkt_min = 0xff; + stats->tx_pkt_min = 0xff; +} + +static inline void seth_rx_stats_update(struct seth_dtrans_stats *stats, uint32_t cnt) +{ + stats->rx_pkt_max = seth_max(stats->rx_pkt_max, cnt); + stats->rx_pkt_min = seth_min(stats->rx_pkt_min, cnt); + stats->rx_sum += cnt; + stats->rx_cnt++; +} + +static inline void seth_tx_stats_update(struct seth_dtrans_stats *stats, uint32_t cnt) +{ + stats->tx_pkt_max = seth_max(stats->tx_pkt_max, cnt); + stats->tx_pkt_min = seth_min(stats->tx_pkt_min, cnt); + stats->tx_sum += cnt; + stats->tx_cnt++; +} + +static inline int pkt_is_ping(void *data) +{ + uint8_t *idata = (uint8_t *)data; + uint8_t ver, protocol; + uint32_t ip_hlen; + int ret = 0; + + /* parse the version of IP protocol */ + ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4; + if (ver == SETH_IPV4_VERSION) { + struct iphdr * ip_hdr; + struct icmphdr *icmp_hdr; + + ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]); + /* get length of ip header */ + ip_hlen = ip_hdr->ihl * 4; + /* get the protocol */ + protocol = ip_hdr->protocol; + icmp_hdr = (struct icmphdr *)(&idata[SETH_ETH_HLEN + ip_hlen]); + if (protocol == SETH_PRO_ICMP && icmp_hdr->type == SETH_ICMP_PING) { + ret = 1; + } + } else if (ver == SETH_IPV6_VERSION) { + struct ipv6hdr *ipv6_hdr; + struct icmp6hdr *icmp6_hdr; + + ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]); + ip_hlen = SETH_ETH_HLEN; + /* get the protocol */ + protocol = ipv6_hdr->nexthdr; + icmp6_hdr = (struct icmp6hdr *)(&idata[SETH_ETH_HLEN + ip_hlen]); + if (protocol == SETH_PRO_ICMP && icmp6_hdr->icmp6_type == SETH_ICMP6_PING) { + ret = 1; + } + } + + return ret; +} + +static inline int pkt_is_ack(void *data) +{ + uint8_t *idata = (uint8_t *)data; + uint8_t ver, protocol; + uint32_t ip_hlen; + struct tcphdr *tcp_hdr; + int ret = 0; + + /* parse the version of IP protocol */ + ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4; + if (ver == SETH_IPV4_VERSION) { + struct iphdr *ip_hdr; + + ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]); + /*get length of ip header */ + ip_hlen = ip_hdr->ihl * 4; + /* get the protocol*/ + protocol = ip_hdr->protocol; + } else if (ver == SETH_IPV6_VERSION){ + struct ipv6hdr *ipv6_hdr; + + ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]); + ip_hlen = SETH_IPV6_HLEN; + /* get the protocol */ + protocol = ipv6_hdr->nexthdr; + } else { + return 0; + } + + tcp_hdr = (struct tcphdr *)(&idata[SETH_ETH_HLEN + ip_hlen]); + if (protocol == SETH_PRO_TCP && (tcp_hdr->ack || tcp_hdr->syn)) { + ret = 1; + } + + return ret; +} + +static inline void pkt_seq_print(void *data) +{ + uint8_t *idata = (uint8_t *)data; + uint8_t ver, proto; + uint32_t seq, ip_hlen; + static uint32_t nr = 0; + struct tcphdr *tcp_hdr; + + /* parse the version of IP protocol */ + ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4; + + if (ver == SETH_IPV4_VERSION) { + struct iphdr *ip_hdr; + + ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]); + /* get length of ip header */ + ip_hlen = ip_hdr->ihl * 4; + /* get the protocol */ + proto = ip_hdr->protocol; + } else if (ver == SETH_IPV6_VERSION) { + struct ipv6hdr *ipv6_hdr; + + ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]); + ip_hlen = SETH_IPV6_HLEN; + /* get protocol */ + proto = ipv6_hdr->nexthdr; + } else { + return; + } + + /* print tcp seq */ + if (proto == SETH_PRO_TCP) { + tcp_hdr = (struct tcphdr*)(&idata[SETH_ETH_HLEN + ip_hlen]); + seq = ntohl(tcp_hdr->seq); + pr_err("SETH: rx pkt: no.%u, ipv%u, nseq = 0x%08x, hseq = 0x%08x\n", + nr++, proto, tcp_hdr->seq, seq); + } +} + +static int seth_rx_poll_handler(struct napi_struct * napi, int budget) +{ + struct SEth *seth = container_of(napi, struct SEth, napi); + struct sk_buff * skb; + struct seth_init_data *pdata; + struct sblock blk; + struct seth_dtrans_stats *dt_stats; + int skb_cnt, blk_ret, ret; + + if (!seth) { + SETH_ERR("seth_rx_poll_handler no seth device\n"); + return 0; + } + + pdata = seth->pdata; + dt_stats = &seth->dt_stats; + + blk_ret = 0; + skb_cnt = 0; + /* keep polling, until the sblock rx ring is empty */ + while ((budget - skb_cnt) && !blk_ret) { + blk_ret = sblock_receive(pdata->dst, pdata->channel, &blk, 0); + if (blk_ret) { + SETH_DEBUG("receive sblock error %d\n", blk_ret); + continue; + } + +#ifdef CONFIG_DEBUG_FS + /* print the sequence field of tcp packet for debug purpose*/ + if (seth_print_seq) { + pkt_seq_print((void *)(blk.addr + NET_IP_ALIGN)); + } +#endif + +#ifdef CONFIG_SETH_OPT + /* + * start from SHARKL, the first 2 bytes of sblock are reserved for optimization, + * so IP field has been aligned to 4 Bytes already + */ + skb = dev_alloc_skb (blk.length); + if (!skb) { + SETH_ERR ("failed to alloc skb!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + dt_stats->rx_alloc_fails++; + continue; + } + + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + + /* update skb poiter */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put (skb, blk.length - NET_IP_ALIGN); +#else + skb = dev_alloc_skb (blk.length + NET_IP_ALIGN); + if (!skb) { + SETH_ERR ("alloc skbuff failed!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + continue; + } + + skb_reserve(skb, NET_IP_ALIGN); + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + skb_put (skb, blk.length); +#endif + + skb->protocol = eth_type_trans(skb, seth->netdev); + skb->ip_summed = CHECKSUM_NONE; + + /* update fifo rd_ptr */ + seth->stats.rx_bytes += skb->len; + seth->stats.rx_packets++; + +#ifdef CONFIG_SETH_GRO_DISABLE + netif_receive_skb(skb); +#else + napi_gro_receive(napi, skb); +#endif + /* update skb counter*/ + skb_cnt++; + } + + SETH_DEBUG("napi polling done, budget %d, skb cnt %d, cpu %d\n", + budget, skb_cnt, smp_processor_id()); + + /* update rx statistics */ + seth_rx_stats_update(dt_stats, skb_cnt); + + if (skb_cnt >= 0 && budget > skb_cnt) { + napi_complete(napi); + atomic_dec(&seth->rx_busy); + + /* to guarantee that any arrived sblock(s) can be processed even if there are no events issued by CP */ + if (sblock_get_arrived_count(pdata->dst, pdata->channel)) { + /* start a timer with 2 jiffies expries (20 ms) */ + seth->rx_timer.function = seth_rx_timer_handler; + seth->rx_timer.expires = jiffies + HZ/50; + seth->rx_timer.data = (unsigned long)seth; + mod_timer(&seth->rx_timer, seth->rx_timer.expires); + SETH_INFO("start rx_timer, jiffies %lu.\n", jiffies); + } + } + + return skb_cnt; +} + +#endif + +/* + * Tx_ready handler. + */ +static void +seth_tx_ready_handler (void* data) +{ + SEth* seth = (SEth*) data; + + SETH_DEBUG("seth_tx_ready_handler state %0x\n", seth->state); + if (seth->state != DEV_ON) { + seth->state = DEV_ON; + seth->txstate = DEV_ON; + if (!netif_carrier_ok (seth->netdev)) { + netif_carrier_on (seth->netdev); + } + } + else { + seth->state = DEV_OFF; + seth->txstate = DEV_OFF; + if (netif_carrier_ok (seth->netdev)) { + netif_carrier_off (seth->netdev); + } + } +} + +/* + * Tx_open handler. + */ +static void +seth_tx_open_handler (void* data) +{ + SEth* seth = (SEth*) data; + + SETH_DEBUG("seth_tx_open_handler state %0x\n", seth->state); + if (seth->state != DEV_ON) { + seth->state = DEV_ON; + seth->txstate = DEV_ON; + if (!netif_carrier_ok (seth->netdev)) { + netif_carrier_on (seth->netdev); + } + } +} + +/* + * Tx_close handler. + */ +static void +seth_tx_close_handler (void* data) +{ + SEth* seth = (SEth*) data; + + SETH_INFO("seth_tx_close_handler state %0x\n", seth->state); + if (seth->state != DEV_OFF) { + seth->state = DEV_OFF; + seth->txstate = DEV_OFF; + if (netif_carrier_ok (seth->netdev)) { + netif_carrier_off (seth->netdev); + } + } +} + +#ifdef SETH_NAPI +static void seth_rx_handler (void * data) +{ + SEth* seth = (SEth *)data; + + if (!seth) { + SETH_ERR("seth_rx_handler data is NULL.\n"); + return; + } + + /* if the poll handler has been done, trigger to schedule*/ + if (!atomic_cmpxchg(&seth->rx_busy, 0, 1)) { + /* update rx stats*/ + napi_schedule(&seth->napi); + /* trigger a NET_RX_SOFTIRQ softirq directly */ + raise_softirq(NET_RX_SOFTIRQ); + } +} + +static void seth_rx_timer_handler(unsigned long data) +{ + SETH_INFO("rx_timer expried, jiffies %lu.\n", jiffies); + seth_rx_handler((void *)data); +} + +#else +static void seth_rx_handler (void* data) +{ + SEth* seth = (SEth *)data; + struct seth_init_data *pdata = seth->pdata; + struct sblock blk; + struct sk_buff* skb; + uint32_t cnt; + int ret, sblkret; + + sblkret = 0; + cnt = 0; + while (!sblkret){ + sblkret = sblock_receive(pdata->dst, pdata->channel, &blk, 0); + if (sblkret) { + SETH_DEBUG("receive sblock error %d\n", sblkret); + break; + } + + /* if the seth state is off, drop the comming packet */ + if (seth->state != DEV_ON) { + SETH_ERR ("rx_handler the state of %s is off!\n", seth->netdev->name); + sblock_release(pdata->dst, pdata->channel, &blk); + continue; + } + +#ifdef CONFIG_SETH_OPT + /* + * start from SHARKL, the first 2 bytes of sblock are reserved for optimization, + * so IP field has been aligned to 4 Bytes already + */ + skb = dev_alloc_skb (blk.length); + if (!skb) { + SETH_ERR ("failed to alloc skb!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + dt_stats->rx_alloc_fails++; + continue; + } + + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + + /* update skb poiter */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put (skb, blk.length - NET_IP_ALIGN); +#else + skb = dev_alloc_skb (blk.length + NET_IP_ALIGN); + if (!skb) { + SETH_ERR ("alloc skbuff failed!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + continue; + } + + skb_reserve(skb, NET_IP_ALIGN); + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + skb_put (skb, blk.length); +#endif + + skb->dev = seth->netdev; + skb->protocol = eth_type_trans (skb, seth->netdev); + skb->ip_summed = CHECKSUM_NONE; + + seth->stats.rx_packets++; + seth->stats.rx_bytes += skb->len; + + netif_rx (skb); + + seth->netdev->last_rx = jiffies; + } + + return; +} +#endif + + +/* + * Tx_close handler. + */ +static void +seth_tx_pre_handler (void* data) +{ + SEth* seth = (SEth*) data; + + if (seth->txstate != DEV_ON) { + seth->txstate = DEV_ON; + SETH_INFO ("seth_tx_ready_handler txstate %0x\n", seth->txstate ); + seth->netdev->trans_start = jiffies; + if (netif_queue_stopped(seth->netdev)) { + netif_wake_queue(seth->netdev); + } + } +} + +static void +seth_handler (int event, void* data) +{ + SEth *seth = (SEth *)data; + + switch(event) { + case SBLOCK_NOTIFY_GET: + SETH_DEBUG ("SBLOCK_NOTIFY_GET is received\n"); + seth_tx_pre_handler(seth); + break; + case SBLOCK_NOTIFY_RECV: + SETH_DEBUG ("SBLOCK_NOTIFY_RECV is received\n"); + del_timer(&seth->rx_timer); + seth_rx_handler(seth); + break; + case SBLOCK_NOTIFY_STATUS: + SETH_DEBUG ("SBLOCK_NOTIFY_STATUS is received\n"); + seth_tx_ready_handler(seth); + break; + case SBLOCK_NOTIFY_OPEN: + SETH_DEBUG ("SBLOCK_NOTIFY_OPEN is received\n"); + seth_tx_open_handler(seth); + break; + case SBLOCK_NOTIFY_CLOSE: + SETH_DEBUG ("SBLOCK_NOTIFY_CLOSE is received\n"); + seth_tx_close_handler(seth); + break; + default: + SETH_ERR ("Received event is invalid(event=%d)\n", event); + } +} + +static int +seth_tx_pkt(void* data, struct sk_buff* skb) +{ + struct sblock blk; + SEth* seth = netdev_priv (data); + struct seth_init_data *pdata = seth->pdata; + int ret; + + /* + * Get a free sblock. + */ + ret = sblock_get(pdata->dst, pdata->channel, &blk, 0); + if(ret) { + SETH_INFO("Get free sblock failed(%d), drop data!\n", ret); + seth->stats.tx_fifo_errors++; + return SETH_TX_NO_BLK; + } + + if(blk.length < (skb->len + NET_IP_ALIGN)) { + SETH_ERR("The size of sblock is so tiny!\n"); + /* recycle the unused sblock */ + sblock_put(pdata->dst, pdata->channel, &blk); + seth->stats.tx_fifo_errors++; + netif_wake_queue(seth->netdev); + return SETH_TX_INVALID_BLK; + } + +#ifdef CONFIG_SETH_OPT + /* padding 2 Bytes in the head */ + blk.length = skb->len + NET_IP_ALIGN; + unalign_memcpy(blk.addr + NET_IP_ALIGN, skb->data, skb->len); + /* copy the content into smem in mute */ + sblock_send_prepare(pdata->dst, pdata->channel, &blk); +#else + blk.length = skb->len; + unalign_memcpy(blk.addr, skb->data,skb->len); + /* copy the content into smem and trigger a smsg to the peer side */ + sblock_send(pdata->dst, pdata->channel, &blk); +#endif + /* update the statistics */ + seth->stats.tx_bytes += skb->len; + seth->stats.tx_packets++; + + dev_kfree_skb_any(skb); + +#ifdef CONFIG_SETH_OPT + atomic_inc(&seth->txpending); +#endif + + return SETH_TX_SUCCESS; +} + +static void seth_tx_flush(unsigned long data) +{ + int ret; + uint32_t cnt; + SEth* seth = netdev_priv((void *)data); + struct seth_init_data *pdata = seth->pdata; + + cnt = (uint32_t)atomic_read(&seth->txpending); + ret = sblock_send_finish(pdata->dst, pdata->channel); + seth_tx_stats_update(&seth->dt_stats, cnt); + if(ret) { + SETH_INFO("seth tx failed(%d)!\n", ret); + } else { + atomic_set(&seth->txpending, 0); + } +} + +/* + * Transmit interface + */ +static int +seth_start_xmit (struct sk_buff* skb, struct net_device* dev) +{ + SEth* seth = netdev_priv(dev); + struct seth_dtrans_stats *dt_stats; + int flag = 0, ret = 0; + int blk_cnt = 0; + + if (seth->state != DEV_ON) { + SETH_ERR ("xmit the state of %s is off\n", dev->name); + netif_carrier_off (dev); + seth->stats.tx_carrier_errors++; + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* update tx statistics */ + dt_stats = &seth->dt_stats; + if (pkt_is_ping((void *)skb->data)) { + dt_stats->tx_ping_cnt++; + flag = 1; + } else if (pkt_is_ack((void *)skb->data)) { + dt_stats->tx_ack_cnt++; + flag = 1; + } + + ret = seth_tx_pkt(dev, skb); + if (SETH_TX_NO_BLK == ret) { + /* if there are no available sblks, enter flow control */ + seth->txstate = DEV_OFF; + netif_stop_queue(dev); + /* anyway, flush the stored sblks */ + seth_tx_flush((unsigned long)dev); + return NETDEV_TX_BUSY; + } else if (SETH_TX_INVALID_BLK == ret) { + return NETDEV_TX_OK; + } + + /* if there are no available sblock for subsequent skb, start flow control*/ + blk_cnt = sblock_get_free_count(seth->pdata->dst, seth->pdata->channel); + if (!blk_cnt) { + SETH_DEBUG("start flow control\n"); + seth->txstate = DEV_OFF; + netif_stop_queue(dev); + } + +#ifdef CONFIG_SETH_OPT + if ((atomic_read(&seth->txpending) >= SETH_TX_WEIGHT) || flag) { + del_timer(&seth->tx_timer); + seth_tx_flush((unsigned long)dev); + SETH_DEBUG ("seth_start_xmit:at once\n"); + } else if (atomic_read(&seth->txpending) == 1){ + /* start a timer with 1 jiffy expries (10 ms) */ + seth->tx_timer.function = seth_tx_flush; + seth->tx_timer.expires = jiffies + HZ/100; + seth->tx_timer.data = (unsigned long)dev; + mod_timer(&seth->tx_timer, seth->tx_timer.expires); + SETH_DEBUG ("seth_start_xmit:Timer\n"); + } +#endif + dev->trans_start = jiffies; + return NETDEV_TX_OK; +} + +/* + * Open interface + */ +static int seth_open (struct net_device *dev) +{ + SEth* seth = netdev_priv(dev); + struct seth_init_data *pdata; + struct sblock blk; + int ret = 0, num = 0; + + SETH_INFO("open seth!\n"); + + if (!seth) { + return -ENODEV; + } + + pdata = seth->pdata; + + /* clean the resident sblocks */ + while(!ret) { + ret = sblock_receive(pdata->dst, pdata->channel, &blk, 0); + if (!ret) { + sblock_release(pdata->dst, pdata->channel, &blk); + num++; + } + } + SETH_INFO("seth_open clean %d resident sblocks\n", num); + + /* Reset stats */ + memset(&seth->stats, 0, sizeof(seth->stats)); + + if (seth->state == DEV_ON && !netif_carrier_ok(seth->netdev)) { + SETH_INFO("seth_open netif_carrier_on\n"); + netif_carrier_on(seth->netdev); + } + +#ifdef SETH_NAPI + atomic_set(&seth->rx_busy, 0); + napi_enable(&seth->napi); + seth_dt_stats_init(&seth->dt_stats); +#endif + + seth->txstate = DEV_ON; + seth->state = DEV_ON; + + netif_start_queue(dev); + + return 0; +} + +/* + * Close interface + */ +static int seth_close (struct net_device *dev) +{ + SEth* seth = netdev_priv(dev); + + SETH_INFO("close seth!\n"); + + seth->txstate = DEV_OFF; + seth->state = DEV_OFF; + +#ifdef SETH_NAPI + napi_disable(&seth->napi); +#endif + + netif_stop_queue(dev); + + return 0; +} + +static struct net_device_stats * seth_get_stats(struct net_device *dev) +{ + SEth * seth = netdev_priv(dev); + return &(seth->stats); +} + +static void seth_tx_timeout(struct net_device *dev) +{ + SEth * seth = netdev_priv(dev); + + SETH_INFO ("seth_tx_timeout()\n"); + if (seth->txstate != DEV_ON) { + seth->txstate = DEV_ON; + dev->trans_start = jiffies; + netif_wake_queue(dev); + } +} + +static struct net_device_ops seth_ops = { + .ndo_open = seth_open, + .ndo_stop = seth_close, + .ndo_start_xmit = seth_start_xmit, + .ndo_get_stats = seth_get_stats, + .ndo_tx_timeout = seth_tx_timeout, +}; + +static int seth_parse_dt(struct seth_init_data **init, struct device *dev) +{ +#ifdef CONFIG_OF + struct seth_init_data *pdata = NULL; + struct device_node *np = dev->of_node; + int ret; + uint32_t data; + + pdata = kzalloc(sizeof(struct seth_init_data), GFP_KERNEL); + if (!pdata) { + return -ENOMEM; + } + + ret = of_property_read_string(np, "sprd,name", (const char**)&pdata->name); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->dst = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->channel = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,blknum", (uint32_t *)&pdata->blocknum); + if (ret) { + goto error; + } + + *init = pdata; + return 0; +error: + kfree(pdata); + *init = NULL; + return ret; +#else + return -ENODEV; +#endif +} + +static inline void seth_destroy_pdata(struct seth_init_data **init) +{ +#ifdef CONFIG_OF + struct seth_init_data *pdata = *init; + + if (pdata) { + kfree(pdata); + } + *init = NULL; +#else + return; +#endif +} + +static int seth_probe(struct platform_device *pdev) +{ + struct seth_init_data *pdata = pdev->dev.platform_data; + struct net_device* netdev; + SEth* seth; + char ifname[IFNAMSIZ]; + int ret; + + if (pdev->dev.of_node && !pdata) { + ret = seth_parse_dt(&pdata, &pdev->dev); + if (ret) { + SETH_ERR( "failed to parse seth device tree, ret=%d\n", ret); + return ret; + } + } + SETH_INFO("after parse device tree, name=%s, dst=%u, channel=%u, blocknum=%u\n", + pdata->name, pdata->dst, pdata->channel, pdata->blocknum); + + if(pdata->name[0]) + strlcpy(ifname, pdata->name, IFNAMSIZ); + else + strcpy(ifname, "veth%d"); + + netdev = alloc_netdev (sizeof (SEth), ifname, ether_setup); + if (!netdev) { + seth_destroy_pdata(&pdata); + SETH_ERR ("alloc_netdev() failed.\n"); + return -ENOMEM; + } + seth = netdev_priv (netdev); + seth->pdata = pdata; + seth->netdev = netdev; + seth->state = DEV_OFF; + +#ifdef SETH_NAPI + atomic_set(&seth->rx_busy, 0); + atomic_set(&seth->txpending, 0); + + init_timer(&seth->rx_timer); + init_timer(&seth->tx_timer); + seth_dt_stats_init(&seth->dt_stats); +#endif + + netdev->netdev_ops = &seth_ops; + netdev->watchdog_timeo = 1*HZ; + netdev->irq = 0; + netdev->dma = 0; + + random_ether_addr(netdev->dev_addr); + +#ifdef SETH_NAPI + netif_napi_add(netdev, &seth->napi, seth_rx_poll_handler, SETH_NAPI_WEIGHT); +#endif + + ret = sblock_create(pdata->dst, pdata->channel, + pdata->blocknum, SETH_BLOCK_SIZE, + pdata->blocknum, SETH_BLOCK_SIZE); + if (ret) { + SETH_ERR ("create sblock failed (%d)\n", ret); +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); +#endif + free_netdev(netdev); + seth_destroy_pdata(&pdata); + return ret; + } + + ret = sblock_register_notifier(pdata->dst, pdata->channel, seth_handler, seth); + if (ret) { + SETH_ERR ("regitster notifier failed (%d)\n", ret); +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); +#endif + free_netdev(netdev); + sblock_destroy(pdata->dst, pdata->channel); + seth_destroy_pdata(&pdata); + return ret; + } + + /* register new Ethernet interface */ + if ((ret = register_netdev (netdev))) { + SETH_ERR ("register_netdev() failed (%d)\n", ret); +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); +#endif + free_netdev(netdev); + sblock_destroy(pdata->dst, pdata->channel); + seth_destroy_pdata(&pdata); + return ret; + } + + /* set link as disconnected */ + netif_carrier_off (netdev); + + platform_set_drvdata(pdev, seth); +#ifdef CONFIG_DEBUG_FS + seth_debugfs_mknod(root, (void *)seth); +#endif + return 0; +} + +/* + * Cleanup Ethernet device driver. + */ +static int seth_remove (struct platform_device *pdev) +{ + struct SEth* seth = platform_get_drvdata(pdev); + struct seth_init_data *pdata = seth->pdata; + +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); + + del_timer_sync(&seth->rx_timer); + del_timer_sync(&seth->tx_timer); +#endif + + sblock_destroy(pdata->dst, pdata->channel); + seth_destroy_pdata(&pdata); + unregister_netdev(seth->netdev); + free_netdev(seth->netdev); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id seth_match_table[] = { + { .compatible = "sprd,seth", }, + { }, +}; + +static struct platform_driver seth_driver = { + .probe = seth_probe, + .remove = seth_remove, + .driver = { + .owner = THIS_MODULE, + .name = "seth", + .of_match_table = seth_match_table, + }, +}; + +static int __init seth_init(void) +{ + return platform_driver_register(&seth_driver); +} + +static void __exit seth_exit(void) +{ + platform_driver_unregister(&seth_driver); +} + +module_init (seth_init); +module_exit (seth_exit); + +#ifdef CONFIG_DEBUG_FS +static int seth_debug_show(struct seq_file *m, void *v) +{ + struct SEth *seth = (struct SEth*)(m->private); + struct seth_dtrans_stats *stats; + struct seth_init_data *pdata; + + if(!seth) { + SETH_ERR("invalid data, seth is NULL\n"); + return -EINVAL; + } + + pdata = seth->pdata; + stats = &seth->dt_stats; + + seq_printf(m, "******************************************************************\n"); + seq_printf(m, "DEVICE: %s, state %d, NET_SKB_PAD %u\n", pdata->name, seth->state, NET_SKB_PAD); + seq_printf(m, "\nRX statistics:\n"); + seq_printf(m, "rx_pkt_max=%u, rx_pkt_min=%u, rx_sum=%u, rx_cnt=%u\n", + stats->rx_pkt_max, stats->rx_pkt_min, stats->rx_sum, stats->rx_cnt); + seq_printf(m, "rx_alloc_fails=%u, rx_busy=%d\n", stats->rx_alloc_fails, atomic_read(&seth->rx_busy)); + + seq_printf(m, "\nTX statistics:\n"); + seq_printf(m, "tx_pkt_max=%u, tx_pkt_min=%u, tx_sum=%u, tx_cnt=%u\n", + stats->tx_pkt_max, stats->tx_pkt_min, stats->tx_sum, stats->tx_cnt); + seq_printf(m, "tx_ping_cnt=%u, tx_ack_cnt=%u\n", + stats->tx_ping_cnt, stats->tx_ack_cnt); + seq_printf(m, "******************************************************************\n"); + return 0; +} + +static int seth_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, seth_debug_show, inode->i_private); +} + +static const struct file_operations seth_debug_fops = { + .open = seth_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int seth_debugfs_mknod(void *root, void * data) +{ + struct SEth *seth = (struct SEth*) data; + struct seth_init_data *pdata; + + if (!seth) { + return -ENODEV; + } + pdata = seth->pdata; + + if (!root) + return -ENXIO; + + debugfs_create_file(pdata->name, S_IRUGO, (struct dentry *)root, data, &seth_debug_fops); + return 0; +} + +static int __init seth_debugfs_init(void) +{ + root = debugfs_create_dir("seth", NULL); + if (!root) { + SETH_ERR("failed to create seth debugfs dir\n"); + return -ENODEV; + } + + debugfs_create_u32("print_seq", S_IRUGO | S_IWUGO, root, &seth_print_seq); + + return 0; +} + +fs_initcall(seth_debugfs_init); +#endif + +MODULE_AUTHOR("Qiu Yi"); +MODULE_DESCRIPTION ("Spreadtrum Ethernet device driver"); +MODULE_LICENSE ("GPL"); + diff --git a/drivers/sipc/simple_events.h b/drivers/sipc/simple_events.h new file mode 100644 index 00000000..03851b32 --- /dev/null +++ b/drivers/sipc/simple_events.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef _SIMPLE_EVENTS_H_ +#define _SIMPLE_EVENTS_H_ + +#include <linux/wait.h> + + + +struct __simple_event { + spinlock_t lock; + wait_queue_head_t wq; + unsigned int flags; + +}; + +typedef struct __simple_event simple_event_t; + + +static inline void simple_evnet_init(simple_event_t* evt) +{ + spin_lock_init(&evt->lock); + init_waitqueue_head(&evt->wq); + evt->flags = 0; +} + +static inline int simple_evnet_get(simple_event_t* evt, unsigned int req_flags, + unsigned int *actual_flags) +{ + unsigned long flags; + + if(!evt) return -1; + + *actual_flags = 0; + + while(!(*actual_flags)) { + wait_event(evt->wq, (evt->flags & req_flags)); + + spin_lock_irqsave(&evt->lock, flags); + *actual_flags = evt->flags & req_flags; + evt->flags &= ~req_flags; + spin_unlock_irqrestore(&evt->lock, flags); + } + + return 0; +} + + +static inline int simple_evnet_set(simple_event_t* evt, unsigned int flags, + int opt_and) +{ + unsigned long irq_flags; + int need_wake = 0; + + if(!evt) return -1; + + spin_lock_irqsave(&evt->lock, irq_flags); + if(!opt_and) { + evt->flags |= flags; + } else { + evt->flags &= flags; + } + + if(evt->flags) { + need_wake = 1; + } + spin_unlock_irqrestore(&evt->lock, irq_flags); + + if(need_wake) { + wake_up(&evt->wq); + } + + return 0; +} + + +#endif /* !_SIMPLE_EVENTS_H_ */ + diff --git a/drivers/sipc/sipc.c b/drivers/sipc/sipc.c new file mode 100644 index 00000000..963d0a25 --- /dev/null +++ b/drivers/sipc/sipc.c @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <soc/sprd/sci_glb_regs.h> + +#ifdef CONFIG_SPRD_MAILBOX +#include <soc/sprd/mailbox.h> +#endif + +#include <linux/sipc.h> +#include <linux/sipc_priv.h> + +#define SMSG_TXBUF_ADDR (0) +#define SMSG_TXBUF_SIZE (SZ_1K) +#define SMSG_RXBUF_ADDR (SMSG_TXBUF_SIZE) +#define SMSG_RXBUF_SIZE (SZ_1K) + +#define SMSG_RINGHDR (SMSG_TXBUF_SIZE + SMSG_RXBUF_SIZE) +#define SMSG_TXBUF_RDPTR (SMSG_RINGHDR + 0) +#define SMSG_TXBUF_WRPTR (SMSG_RINGHDR + 4) +#define SMSG_RXBUF_RDPTR (SMSG_RINGHDR + 8) +#define SMSG_RXBUF_WRPTR (SMSG_RINGHDR + 12) + +/* if it's upon mailbox arch, overwrite the implementation*/ +#ifdef CONFIG_SPRD_MAILBOX + +#define DEFINE_SIPC_RXIRQ_STATUS_FN(id) \ +static int sipc_rxirq_status##id(void) \ +{ \ + return 1;\ +} + +#define DEFINE_SIPC_RXIRQ_CLEAR_FN(id) \ +static void sipc_rxirq_clear##id(void) \ +{ \ + return;\ +} + +#define DEFINE_SIPC_TXIRQ_TRIGGER_FN(id) \ +static void sipc_txirq_trigger##id(void) \ +{ \ + struct sipc_child_node_info *info = &sipc_dev->pdata->info_table[id];\ + mbox_raw_sent(info->core_id, 0);\ +} + +#else + +#define DEFINE_SIPC_RXIRQ_STATUS_FN(id) \ +static int sipc_rxirq_status##id(void) \ +{ \ + return 1;\ +} + +#define DEFINE_SIPC_RXIRQ_CLEAR_FN(id) \ +static void sipc_rxirq_clear##id(void) \ +{ \ + struct sipc_child_node_info *info = &sipc_dev->pdata->info_table[id];\ + __raw_writel(info->ap2cp_bit_clr, (volatile void *)info->cp2ap_int_ctrl);\ +} + +#define DEFINE_SIPC_TXIRQ_TRIGGER_FN(id) \ +static void sipc_txirq_trigger##id(void) \ +{ \ + struct sipc_child_node_info *info = &sipc_dev->pdata->info_table[id];\ + __raw_writel(info->ap2cp_bit_trig, (volatile void *)info->ap2cp_int_ctrl);\ +} + +#endif + +#define __GET_SIPC_FN_NAME(fn, id) fn##id + +#define __GET_SIPC_FN(fn, id) \ +({ \ + void* ret; \ + switch(id) { \ + case 0: \ + ret = __GET_SIPC_FN_NAME(fn, 0);\ + break; \ + case 1: \ + ret = __GET_SIPC_FN_NAME(fn, 1);\ + break; \ + case 2: \ + ret = __GET_SIPC_FN_NAME(fn, 2);\ + break; \ + default: \ + break; \ + } \ + ret; \ +}) \ + +#define GET_SIPC_RXIRQ_STATUS_FN(id) __GET_SIPC_FN(sipc_rxirq_status, id) +#define GET_SIPC_RXIRQ_CLEAR_FN(id) __GET_SIPC_FN(sipc_rxirq_clear, id) +#define GET_SIPC_TXIRQ_TRIGGER_FN(id) __GET_SIPC_FN(sipc_txirq_trigger, id) + +struct sipc_child_node_info { + uint8_t dst; + char * name; + + uint32_t cp_base; + uint32_t cp_size; + + uint32_t ring_base; + uint32_t ring_size; + + uint32_t smem_base; + uint32_t smem_size; + +#ifdef CONFIG_SPRD_MAILBOX + uint32_t core_id; +#else + uint32_t ap2cp_int_ctrl; + uint32_t cp2ap_int_ctrl; + uint32_t ap2cp_bit_trig; + uint32_t ap2cp_bit_clr; + + uint32_t irq; +#endif +}; + +struct sipc_init_data { + int is_alloc; + uint32_t chd_nr; + uint32_t smem_base; + uint32_t smem_size; + struct sipc_child_node_info info_table[0]; +}; + +struct sipc_device { + int status; + uint32_t inst_nr; + struct sipc_init_data *pdata; + struct smsg_ipc *smsg_inst; +}; + +struct sipc_device *sipc_dev; + +DEFINE_SIPC_TXIRQ_TRIGGER_FN(0) +DEFINE_SIPC_TXIRQ_TRIGGER_FN(1) +DEFINE_SIPC_TXIRQ_TRIGGER_FN(2) + +DEFINE_SIPC_RXIRQ_CLEAR_FN(0) +DEFINE_SIPC_RXIRQ_CLEAR_FN(1) +DEFINE_SIPC_RXIRQ_CLEAR_FN(2) + +DEFINE_SIPC_RXIRQ_STATUS_FN(0) +DEFINE_SIPC_RXIRQ_STATUS_FN(1) +DEFINE_SIPC_RXIRQ_STATUS_FN(2) + + +static int sipc_create(struct sipc_device *sipc) +{ + struct sipc_init_data *pdata = sipc->pdata; + struct smsg_ipc *inst; + struct sipc_child_node_info *info; + size_t base; + uint32_t num; + int ret, i; + + if (!pdata) { + return -ENODEV; + } + + num = pdata->chd_nr; + inst = sipc->smsg_inst; + info = pdata->info_table; + + for (i = 0; i < num; i++) { + base = (size_t)ioremap_nocache((uint32_t)info[i].ring_base, info[i].ring_size); + if(base == NULL){ + pr_info("sipc:[%d] ioremap return NULL\n"); + return ENOMEM; + } + pr_info("sipc:[%d] after ioremap vbase=0x%lx, pbase=0x%x, size=0x%x\n", + i, base, info[i].ring_base, info[i].ring_size); + inst[i].txbuf_size = SMSG_TXBUF_SIZE / sizeof(struct smsg); + inst[i].txbuf_addr = base + SMSG_TXBUF_ADDR; + inst[i].txbuf_rdptr = base + SMSG_TXBUF_RDPTR; + inst[i].txbuf_wrptr = base + SMSG_TXBUF_WRPTR; + + inst[i].rxbuf_size = SMSG_RXBUF_SIZE / sizeof(struct smsg); + inst[i].rxbuf_addr = base + SMSG_RXBUF_ADDR; + inst[i].rxbuf_rdptr = base + SMSG_RXBUF_RDPTR; + inst[i].rxbuf_wrptr = base + SMSG_RXBUF_WRPTR; + + ret = smsg_ipc_create(inst[i].dst, &inst[i]); + + pr_info("sipc:[%d] created\n", i); + if (ret) { + break; + } + } + return ret; +} + + +static int sipc_parse_dt(struct sipc_init_data **init, struct device *dev) +{ + struct device_node *np = dev->of_node, *nchd; + struct sipc_init_data *pdata; + struct sipc_child_node_info *info; + struct resource res; + uint32_t data, nr, int_ctrl_phy[2]; + int ret, i; + + nr = of_get_child_count(np); + if (nr == 0 || nr >= SIPC_ID_NR) { + return (!nr ? -ENODEV : -EINVAL); + } + pdata = kzalloc(sizeof(struct sipc_init_data) + nr*sizeof(struct sipc_child_node_info), GFP_KERNEL); + if (!pdata) { + pr_err("sipc: failed to alloc mem for pdata\n"); + return -ENOMEM; + } + + pdata->is_alloc = 1; + ret = of_address_to_resource(np, 0, &res); + if (ret) { + goto error; + } + pdata->chd_nr = nr; + pdata->smem_base = (uint32_t)res.start; + pdata->smem_size = (uint32_t)(res.end - res.start + 1); + pr_info("sipc: chd_nr=%d smem_base=0x%x, smem_size=0x%x\n", pdata->chd_nr, pdata->smem_base, pdata->smem_size); + + i = 0; + info = pdata->info_table; + + for_each_child_of_node(np, nchd) { + ret = of_property_read_string(nchd, "sprd,name", (const char **)&info[i].name); + if (ret) { + goto error; + } + pr_info("sipc:[%d] name=%s\n", i, info[i].name); + + ret = of_property_read_u32(nchd, "sprd,dst", &data); + if (ret) { + goto error; + } + info[i].dst = (uint8_t)data; + pr_info("sipc:[%d] dst=%u\n", i, info[i].dst); + +#ifndef CONFIG_SPRD_MAILBOX + /* get ipi base addr */ + ret = of_property_read_u32(nchd, "sprd,ap2cp", &int_ctrl_phy[0]); + if (ret) { + goto error; + } + info[i].ap2cp_int_ctrl = SPRD_DEV_P2V(int_ctrl_phy[0]); + + ret = of_property_read_u32(nchd, "sprd,cp2ap", &int_ctrl_phy[1]); + if (ret) { + goto error; + } + info[i].cp2ap_int_ctrl = SPRD_DEV_P2V(int_ctrl_phy[1]); + + pr_info("sipc:[%d] ap2cp_int_ctrl=0x%lx, ap2cp_ctrl_phy=0x%x, cp2ap_int_ctrl=0x%lx, cp2ap_ctrl_phy=0x%x\n", + i, info[i].ap2cp_int_ctrl, int_ctrl_phy[0], info[i].cp2ap_int_ctrl, int_ctrl_phy[1]); + + ret = of_property_read_u32(nchd, "sprd,trig", &info[i].ap2cp_bit_trig); + if (ret) { + goto error; + } + + ret = of_property_read_u32(nchd, "sprd,clr", &info[i].ap2cp_bit_clr); + if (ret) { + goto error; + } + pr_info("sipc:[%d] ap2cp_bit_trig=0x%x, ap2cp_bit_clr=0x%x\n", + i, info[i].ap2cp_bit_trig, info[i].ap2cp_bit_clr); +#endif + if (strcmp(info[i].name, "sipc-pmsys") != 0) { + /* get cp base addr */ + ret = of_address_to_resource(nchd, 0, &res); + if (ret) { + goto error; + } + info[i].cp_base = (uint32_t)res.start; + info[i].cp_size = (uint32_t)(res.end - res.start + 1); + pr_info("sipc:[%d] cp_base=0x%x, cp_size=0x%x\n", + i, info[i].cp_base, info[i].cp_size); + + /* get smem base addr */ + ret = of_address_to_resource(nchd, 1, &res); + if (ret) { + goto error; + } + info[i].smem_base = (uint32_t)res.start; + info[i].smem_size = (uint32_t)(res.end - res.start + 1); + pr_info("sipc:[%d] smem_base=0x%x, smem_size=0x%x\n", i, info[i].smem_base, info[i].smem_size); + + /* get ring base addr */ + ret = of_address_to_resource(nchd, 2, &res); + if (ret) { + goto error; + } + info[i].ring_base = (uint32_t)res.start; + info[i].ring_size = (uint32_t)(res.end - res.start + 1); + pr_info("sipc:[%d] ring_base=0x%x, ring_size=0x%x\n", i, info[i].ring_base, info[i].ring_size); + } else { + /* get ring base addr */ + ret = of_address_to_resource(nchd, 0, &res); + if (ret) { + goto error; + } + info[i].ring_base = (uint32_t)res.start; + info[i].ring_size = (uint32_t)(res.end - res.start + 1); + pr_info("sipc:[%d] pmsys ring_base=0x%x, ring_size=0x%x\n", i, info[i].ring_base, info[i].ring_size); + } + +#ifdef CONFIG_SPRD_MAILBOX + ret = of_property_read_u32(nchd, "mailbox,core", &info[i].core_id); + if (ret) { + goto error; + } + pr_info("sipc:[%d] core_id=%u\n", i, info[i].core_id); +#else + /* get irq */ + info[i].irq = irq_of_parse_and_map(nchd, 0); + if (!info[i].irq) { + ret = -EINVAL; + goto error; + } + pr_info("sipc:[%d] irq=%d\n", i, info[i].irq); +#endif + i++; + } + *init = pdata; + return 0; +error: + kfree(pdata); + return ret; +} + +static void sipc_destroy_pdata(struct sipc_init_data **ppdata) +{ + struct sipc_init_data *pdata = ppdata; + if (pdata && pdata->is_alloc) { + kfree(pdata); + } + return; +} + +static int sipc_probe(struct platform_device *pdev) +{ + struct sipc_init_data *pdata = pdev->dev.platform_data; + struct sipc_device *sipc; + struct sipc_child_node_info *info; + struct smsg_ipc *smsg; + uint32_t num; + int ret, i; + + pr_info("sipc:[0] define status=0x%x, clear=0x%x, trigger=0x%x\n", + (uint32_t)sipc_rxirq_status0, (uint32_t)sipc_rxirq_clear0, (uint32_t)sipc_txirq_trigger0); + + pr_info("sipc:[1] define status=0x%x, clear=0x%x, trigger=0x%x\n", + (uint32_t)sipc_rxirq_status1, (uint32_t)sipc_rxirq_clear1, (uint32_t)sipc_txirq_trigger1); + + pr_info("sipc:[2] define status=0x%x, clear=0x%x, trigger=0x%x\n", + (uint32_t)sipc_rxirq_status2, (uint32_t)sipc_rxirq_clear2, (uint32_t)sipc_txirq_trigger2); + + if (!pdata && pdev->dev.of_node) { + ret = sipc_parse_dt(&pdata, &pdev->dev); + if (ret) { + pr_err("sipc: failed to parse dt, ret=%d\n", ret); + return -ENODEV; + } + } + + sipc = kzalloc(sizeof(struct sipc_device), GFP_KERNEL); + if (!sipc) { + sipc_destroy_pdata(&pdata); + return -ENOMEM; + } + + num = pdata->chd_nr; + smsg = kzalloc(num * sizeof(struct smsg_ipc), GFP_KERNEL); + if (!smsg) + { + sipc_destroy_pdata(&pdata); + kfree(sipc); + return -ENOMEM; + } + pr_info("sipc: num=%d\n", num); + info = pdata->info_table; + for (i = 0; i < num; i++) { + smsg[i].name = info[i].name; + smsg[i].dst = info[i].dst; +#ifdef CONFIG_SPRD_MAILBOX + smsg[i].core_id = info[i].core_id; +#else + smsg[i].irq = info[i].irq; +#endif + smsg[i].rxirq_status = GET_SIPC_RXIRQ_STATUS_FN(i); + smsg[i].rxirq_clear = GET_SIPC_RXIRQ_CLEAR_FN(i); + smsg[i].txirq_trigger = GET_SIPC_TXIRQ_TRIGGER_FN(i); + + pr_info("sipc: [%d] fn status=0x%x, clear=0x%x, trigger=0x%x\n", + i, (uint32_t)smsg[i].rxirq_status, (uint32_t)smsg[i].rxirq_clear, + (uint32_t)smsg[i].txirq_trigger); +#ifdef CONFIG_SPRD_MAILBOX + pr_info("sipc:[%d] smsg name=%s, dst=%u, core_id=%d\n", + i, smsg[i].name, smsg[i].dst, smsg[i].core_id); +#else + pr_info("sipc:[%d] smsg name=%s, dst=%u, irq=%d\n", + i, smsg[i].name, smsg[i].dst, smsg[i].irq); +#endif + } + + sipc->pdata = pdata; + sipc->smsg_inst = smsg; + sipc_dev = sipc; + + smsg_suspend_init(); + + sipc_create(sipc); + + pr_info("sipc: smem_base=0x%x, smem_size=0x%x\n", pdata->smem_base, pdata->smem_size); + smem_init(pdata->smem_base, pdata->smem_size); + + platform_set_drvdata(pdev, sipc); + return 0; +} + +static int sipc_remove(struct platform_device *pdev) +{ + struct sipc_device *sipc = platform_get_drvdata(pdev); + + sipc_destroy_pdata(&sipc->pdata); + kfree(sipc->smsg_inst); + kfree(sipc); + return 0; +} + + +static const struct of_device_id sipc_match_table[] = { + {.compatible = "sprd,sipc", }, + { }, +}; + +static struct platform_driver sipc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sipc", + .of_match_table = sipc_match_table, + }, + .probe = sipc_probe, + .remove = sipc_remove, +}; + +static int __init sipc_init(void) +{ + return platform_driver_register(&sipc_driver); +} + +static void __exit sipc_exit(void) +{ + platform_driver_unregister(&sipc_driver); +} + +arch_initcall(sipc_init); +module_exit(sipc_exit); + +MODULE_AUTHOR("Qiu Yi"); +MODULE_DESCRIPTION("SIPC module driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/sipc_debugfs.c b/drivers/sipc/sipc_debugfs.c new file mode 100644 index 00000000..fae779f6 --- /dev/null +++ b/drivers/sipc/sipc_debugfs.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <asm/uaccess.h> + +#include <linux/sipc.h> + +#if defined(CONFIG_DEBUG_FS) + +extern int sbuf_init_debugfs( void *root ); +extern int smsg_init_debugfs( void *root ); +extern int sblock_init_debugfs( void *root ); +extern int smem_init_debugfs( void *root ); + +static int __init sipc_init_debugfs( void) +{ + struct dentry *root = debugfs_create_dir("sipc", NULL); + if (!root) + return -ENXIO; + + smsg_init_debugfs(root); + sbuf_init_debugfs(root); + sblock_init_debugfs(root); + smem_init_debugfs(root); + return 0; +} + +__initcall(sipc_init_debugfs); + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/sipc/smem.c b/drivers/sipc/smem.c new file mode 100644 index 00000000..550f4411 --- /dev/null +++ b/drivers/sipc/smem.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/genalloc.h> +#include <linux/mm.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/sched.h> + +#include <linux/sipc.h> + +struct smem_pool { + struct list_head smem_head; + spinlock_t lock; + + uint32_t addr; + uint32_t size; + atomic_t used; + + struct gen_pool *gen; +}; + +struct smem_record { + struct list_head smem_list; + struct task_struct *task; + uint32_t size; + uint32_t addr; +}; + +static struct smem_pool mem_pool; + +int smem_init(uint32_t addr, uint32_t size) +{ + struct smem_pool *spool = &mem_pool; + + spool->addr = addr; + spool->size = PAGE_ALIGN(size); + atomic_set(&spool->used, 0); + spin_lock_init(&spool->lock); + INIT_LIST_HEAD(&spool->smem_head); + + /* allocator block size is times of pages */ + spool->gen = gen_pool_create(PAGE_SHIFT, -1); + if (!spool->gen) { + printk(KERN_ERR "Failed to create smem gen pool!\n"); + return -1; + } + + if (gen_pool_add(spool->gen, spool->addr, spool->size, -1) != 0) { + printk(KERN_ERR "Failed to add smem gen pool!\n"); + return -1; + } + + return 0; +} + +/* ****************************************************************** */ + +uint32_t smem_alloc(uint32_t size) +{ + struct smem_pool *spool = &mem_pool; + struct smem_record *recd; + unsigned long flags; + uint32_t addr; + + recd = kzalloc(sizeof(struct smem_record), GFP_KERNEL); + if (!recd) { + printk(KERN_ERR "failed to alloc smem record\n"); + addr = 0; + goto error; + } + + size = PAGE_ALIGN(size); + addr = gen_pool_alloc(spool->gen, size); + if (!addr) { + printk(KERN_ERR "failed to alloc smem from gen pool\n"); + kfree(recd); + goto error; + } + + /* record smem alloc info */ + atomic_add(size, &spool->used); + recd->size = size; + recd->task = current; + recd->addr = addr; + spin_lock_irqsave(&spool->lock, flags); + list_add_tail(&recd->smem_list, &spool->smem_head); + spin_unlock_irqrestore(&spool->lock, flags); + +error: + return addr; +} + +void smem_free(uint32_t addr, uint32_t size) +{ + struct smem_pool *spool = &mem_pool; + struct smem_record *recd, *next; + unsigned long flags; + + size = PAGE_ALIGN(size); + atomic_sub(size, &spool->used); + gen_pool_free(spool->gen, addr, size); + /* delete record node from list */ + spin_lock_irqsave(&spool->lock, flags); + list_for_each_entry_safe(recd, next, &spool->smem_head, smem_list) { + if (recd->addr == addr) { + list_del(&recd->smem_list); + kfree(recd); + break; + } + } + spin_unlock_irqrestore(&spool->lock, flags); +} + +#ifdef CONFIG_DEBUG_FS +static int smem_debug_show(struct seq_file *m, void *private) +{ + struct smem_pool *spool = &mem_pool; + struct smem_record *recd; + uint32_t fsize; + unsigned long flags; + + fsize = gen_pool_avail(spool->gen); + + seq_printf(m, "smem pool infomation:\n"); + seq_printf(m, "phys_addr=0x%x, total=0x%x, used=0x%x, free=0x%x\n", + spool->addr, spool->size, spool->used, fsize); + seq_printf(m, "smem record list:\n"); + + spin_lock_irqsave(&spool->lock, flags); + list_for_each_entry(recd, &spool->smem_head, smem_list) { + seq_printf(m, "task %s: pid=%u, addr=0x%x, size=0x%x\n", + recd->task->comm, recd->task->pid, recd->addr, recd->size); + } + spin_unlock_irqrestore(&spool->lock, flags); + return 0; +} + +static int smem_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, smem_debug_show, inode->i_private); +} + +static const struct file_operations smem_debug_fops = { + .open = smem_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int smem_init_debugfs(void *root) +{ + if (!root) + return -ENXIO; + debugfs_create_file("smem", S_IRUGO, (struct dentry *)root, NULL, &smem_debug_fops); + return 0; +} + +#endif //endof CONFIG_DEBUG_FS + +EXPORT_SYMBOL(smem_alloc); +EXPORT_SYMBOL(smem_free); + +MODULE_AUTHOR("Chen Gaopeng"); +MODULE_DESCRIPTION("SIPC/SMEM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/smsg.c b/drivers/sipc/smsg.c new file mode 100644 index 00000000..437189dc --- /dev/null +++ b/drivers/sipc/smsg.c @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#ifdef CONFIG_SPRD_MAILBOX +#include <soc/sprd/mailbox.h> +#endif + +#include <linux/sipc.h> +#include <linux/sipc_priv.h> +#include <linux/wakelock.h> +#include <linux/syscore_ops.h> + +static struct smsg_ipc *smsg_ipcs[SIPC_ID_NR]; + +static ushort debug_enable = 0; + +static struct wake_lock sipc_wake_lock; + +module_param_named(debug_enable, debug_enable, ushort, 0644); + +irqreturn_t smsg_irq_handler(int irq, void *dev_id) +{ + struct smsg_ipc *ipc = (struct smsg_ipc *)dev_id; + struct smsg *msg; + struct smsg_channel *ch; + uintptr_t rxpos; + uint32_t wr; + + if (ipc->rxirq_status()) { + ipc->rxirq_clear(); + } + + while (readl(ipc->rxbuf_wrptr) != readl(ipc->rxbuf_rdptr)) { + rxpos = (readl(ipc->rxbuf_rdptr) & (ipc->rxbuf_size - 1)) * + sizeof (struct smsg) + ipc->rxbuf_addr; + msg = (struct smsg *)rxpos; + + pr_debug("irq get smsg: wrptr=%u, rdptr=%u, rxpos=0x%lx\n", + readl(ipc->rxbuf_wrptr), readl(ipc->rxbuf_rdptr), rxpos); + pr_debug("irq read smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n", + msg->channel, msg->type, msg->flag, msg->value); + if(msg->type == SMSG_TYPE_DIE) { + if(debug_enable) + panic("cpcrash"); + else { + /* update smsg rdptr */ + writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr); + + continue; + } + } + if (msg->channel >= SMSG_CH_NR || msg->type >= SMSG_TYPE_NR) { + /* invalid msg */ + printk(KERN_ERR "invalid smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n", + msg->channel, msg->type, msg->flag, msg->value); + + /* update smsg rdptr */ + writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr); + + continue; + } + + ch = ipc->channels[msg->channel]; + if (!ch) { + if (ipc->states[msg->channel] == CHAN_STATE_UNUSED && + msg->type == SMSG_TYPE_OPEN && + msg->flag == SMSG_OPEN_MAGIC) { + + ipc->states[msg->channel] = CHAN_STATE_WAITING; + } else { + /* drop this bad msg since channel is not opened */ + printk(KERN_ERR "smsg channel %d not opened! " + "drop smsg: type=%d, flag=0x%04x, value=0x%08x\n", + msg->channel, msg->type, msg->flag, msg->value); + } + /* update smsg rdptr */ + writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr); + + continue; + } + + atomic_inc(&(ipc->busy[msg->channel])); + + if ((int)(readl(ch->wrptr) - readl(ch->rdptr)) >= SMSG_CACHE_NR) { + /* msg cache is full, drop this msg */ + printk(KERN_ERR "smsg channel %d recv cache is full! " + "drop smsg: type=%d, flag=0x%04x, value=0x%08x\n", + msg->channel, msg->type, msg->flag, msg->value); + } else { + /* write smsg to cache */ + wr = readl(ch->wrptr) & (SMSG_CACHE_NR - 1); + memcpy(&(ch->caches[wr]), msg, sizeof(struct smsg)); + writel(readl(ch->wrptr) + 1, ch->wrptr); + } + + /* update smsg rdptr */ + writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr); + + wake_up_interruptible_all(&(ch->rxwait)); + + atomic_dec(&(ipc->busy[msg->channel])); + } + + wake_lock_timeout(&sipc_wake_lock, HZ / 2); + + return IRQ_HANDLED; +} + +int smsg_ipc_create(uint8_t dst, struct smsg_ipc *ipc) +{ + int rval; + + if (!ipc->irq_handler) { + ipc->irq_handler = smsg_irq_handler; + } + + spin_lock_init(&(ipc->txpinlock)); + + smsg_ipcs[dst] = ipc; + +#ifdef CONFIG_SPRD_MAILBOX + /* explicitly call irq handler in case of missing irq on boot */ + ipc->irq_handler(ipc->core_id, ipc); + + rval = mbox_register_irq_handle(ipc->core_id, ipc->irq_handler, ipc); + if (rval != 0) { + printk(KERN_ERR "Failed to register irq handler in mailbox: %s\n", + ipc->name); + return rval; + } +#else + /* explicitly call irq handler in case of missing irq on boot */ + ipc->irq_handler(ipc->irq, ipc); + + /* register IPI irq */ + rval = request_irq(ipc->irq, ipc->irq_handler, + IRQF_NO_SUSPEND, ipc->name, ipc); + if (rval != 0) { + printk(KERN_ERR "Failed to request irq %s: %d\n", + ipc->name, ipc->irq); + return rval; + } +#endif + return 0; +} + +int smsg_ipc_destroy(uint8_t dst) +{ + struct smsg_ipc *ipc = smsg_ipcs[dst]; + + kthread_stop(ipc->thread); + free_irq(ipc->irq, ipc); + smsg_ipcs[dst] = NULL; + + return 0; +} + +/* ****************************************************************** */ + +int smsg_ch_open(uint8_t dst, uint8_t channel, int timeout) +{ + struct smsg_ipc *ipc = smsg_ipcs[dst]; + struct smsg_channel *ch; + struct smsg mopen, mrecv; + int rval = 0; + + if(!ipc) { + return -ENODEV; + } + + ch = kzalloc(sizeof(struct smsg_channel), GFP_KERNEL); + if (!ch) { + return -ENOMEM; + } + + atomic_set(&(ipc->busy[channel]), 1); + init_waitqueue_head(&(ch->rxwait)); + mutex_init(&(ch->rxlock)); + ipc->channels[channel] = ch; + + smsg_set(&mopen, channel, SMSG_TYPE_OPEN, SMSG_OPEN_MAGIC, 0); + rval = smsg_send(dst, &mopen, timeout); + if (rval != 0) { + printk(KERN_ERR "smsg_ch_open smsg send error, errno %d!\n", rval); + ipc->states[channel] = CHAN_STATE_UNUSED; + ipc->channels[channel] = NULL; + atomic_dec(&(ipc->busy[channel])); + /* guarantee that channel resource isn't used in irq handler */ + while(atomic_read(&(ipc->busy[channel]))) { + ; + } + kfree(ch); + + return rval; + } + + /* open msg might be got before */ + if (ipc->states[channel] == CHAN_STATE_WAITING) { + goto open_done; + } + + smsg_set(&mrecv, channel, 0, 0, 0); + rval = smsg_recv(dst, &mrecv, timeout); + if (rval != 0) { + printk(KERN_ERR "smsg_ch_open smsg receive error, errno %d!\n", rval); + ipc->states[channel] = CHAN_STATE_UNUSED; + ipc->channels[channel] = NULL; + atomic_dec(&(ipc->busy[channel])); + /* guarantee that channel resource isn't used in irq handler */ + while(atomic_read(&(ipc->busy[channel]))) { + ; + } + kfree(ch); + + return rval; + } + + if (mrecv.type != SMSG_TYPE_OPEN || mrecv.flag != SMSG_OPEN_MAGIC) { + printk(KERN_ERR "Got bad open msg on channel %d-%d\n", dst, channel); + ipc->states[channel] = CHAN_STATE_UNUSED; + ipc->channels[channel] = NULL; + atomic_dec(&(ipc->busy[channel])); + /* guarantee that channel resource isn't used in irq handler */ + while(atomic_read(&(ipc->busy[channel]))) { + ; + } + kfree(ch); + + return -EIO; + } + +open_done: + ipc->states[channel] = CHAN_STATE_OPENED; + atomic_dec(&(ipc->busy[channel])); + + return 0; +} + +int smsg_ch_close(uint8_t dst, uint8_t channel, int timeout) +{ + struct smsg_ipc *ipc = smsg_ipcs[dst]; + struct smsg_channel *ch = ipc->channels[channel]; + struct smsg mclose; + + if (!ch) { + return 0; + } + + smsg_set(&mclose, channel, SMSG_TYPE_CLOSE, SMSG_CLOSE_MAGIC, 0); + smsg_send(dst, &mclose, timeout); + + ipc->states[channel] = CHAN_STATE_FREE; + wake_up_interruptible_all(&(ch->rxwait)); + + /* wait for the channel beeing unused */ + while(atomic_read(&(ipc->busy[channel]))) { + ; + } + + /* maybe channel has been free for smsg_ch_open failed */ + if (ipc->channels[channel]){ + ipc->channels[channel] = NULL; + /* guarantee that channel resource isn't used in irq handler */ + while(atomic_read(&(ipc->busy[channel]))) { + ; + } + kfree(ch); + } + + /* finally, update the channel state*/ + ipc->states[channel] = CHAN_STATE_UNUSED; + + return 0; +} + +int smsg_send(uint8_t dst, struct smsg *msg, int timeout) +{ + struct smsg_ipc *ipc = smsg_ipcs[dst]; + uintptr_t txpos; + int rval = 0; + unsigned long flags; + + if(!ipc) { + return -ENODEV; + } + + if (!ipc->channels[msg->channel]) { + printk(KERN_ERR "channel %d not inited!\n", msg->channel); + return -ENODEV; + } + + if (ipc->states[msg->channel] != CHAN_STATE_OPENED && + msg->type != SMSG_TYPE_OPEN && + msg->type != SMSG_TYPE_CLOSE) { + printk(KERN_ERR "channel %d not opened!\n", msg->channel); + return -EINVAL; + } + + pr_debug("smsg_send: dst=%d, channel=%d, timeout=%d\n", + dst, msg->channel, timeout); + pr_debug("send smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n", + msg->channel, msg->type, msg->flag, msg->value); + + spin_lock_irqsave(&(ipc->txpinlock), flags); + if ((int)(readl(ipc->txbuf_wrptr) - + readl(ipc->txbuf_rdptr)) >= ipc->txbuf_size) { + printk(KERN_WARNING "smsg txbuf is full!\n"); + rval = -EBUSY; + goto send_failed; + } + + /* calc txpos and write smsg */ + txpos = (readl(ipc->txbuf_wrptr) & (ipc->txbuf_size - 1)) * + sizeof(struct smsg) + ipc->txbuf_addr; + memcpy((void *)txpos, msg, sizeof(struct smsg)); + + pr_debug("write smsg: wrptr=%u, rdptr=%u, txpos=0x%lx\n", + readl(ipc->txbuf_wrptr), + readl(ipc->txbuf_rdptr), txpos); + + /* update wrptr */ + writel(readl(ipc->txbuf_wrptr) + 1, ipc->txbuf_wrptr); + ipc->txirq_trigger(); + +send_failed: + spin_unlock_irqrestore(&(ipc->txpinlock), flags); + + return rval; +} + +int smsg_recv(uint8_t dst, struct smsg *msg, int timeout) +{ + struct smsg_ipc *ipc = smsg_ipcs[dst]; + struct smsg_channel *ch; + uint32_t rd; + int rval = 0; + + if(!ipc) { + return -ENODEV; + } + + atomic_inc(&(ipc->busy[msg->channel])); + + ch = ipc->channels[msg->channel]; + + if (!ch) { + printk(KERN_ERR "channel %d not opened!\n", msg->channel); + atomic_dec(&(ipc->busy[msg->channel])); + return -ENODEV; + } + + pr_debug("smsg_recv: dst=%d, channel=%d, timeout=%d\n", + dst, msg->channel, timeout); + + + if (timeout == 0) { + if (!mutex_trylock(&(ch->rxlock))) { + printk(KERN_INFO "recv smsg busy!\n"); + atomic_dec(&(ipc->busy[msg->channel])); + + return -EBUSY; + } + + /* no wait */ + if (readl(ch->wrptr) == readl(ch->rdptr)) { + printk(KERN_WARNING "smsg rx cache is empty!\n"); + rval = -ENODATA; + + goto recv_failed; + } + } else if (timeout < 0) { + mutex_lock(&(ch->rxlock)); + /* wait forever */ + rval = wait_event_interruptible(ch->rxwait, + (readl(ch->wrptr) != readl(ch->rdptr)) || + (ipc->states[msg->channel] == CHAN_STATE_FREE)); + if (rval < 0) { + printk(KERN_WARNING "smsg_recv wait interrupted!\n"); + + goto recv_failed; + } + + if (ipc->states[msg->channel] == CHAN_STATE_FREE) { + printk(KERN_WARNING "smsg_recv smsg channel is free!\n"); + rval = -EIO; + + goto recv_failed; + } + } else { + mutex_lock(&(ch->rxlock)); + /* wait timeout */ + rval = wait_event_interruptible_timeout(ch->rxwait, + (readl(ch->wrptr) != readl(ch->rdptr)) || + (ipc->states[msg->channel] == CHAN_STATE_FREE), timeout); + if (rval < 0) { + printk(KERN_WARNING "smsg_recv wait interrupted!\n"); + + goto recv_failed; + } else if (rval == 0) { + printk(KERN_WARNING "smsg_recv wait timeout!\n"); + rval = -ETIME; + + goto recv_failed; + } + + if (ipc->states[msg->channel] == CHAN_STATE_FREE) { + printk(KERN_ERR "smsg_recv smsg channel is free!\n"); + rval = -EIO; + + goto recv_failed; + } + } + + /* read smsg from cache */ + rd = readl(ch->rdptr) & (SMSG_CACHE_NR - 1); + memcpy(msg, &(ch->caches[rd]), sizeof(struct smsg)); + writel(readl(ch->rdptr) + 1, ch->rdptr); + + pr_debug("read smsg: wrptr=%d, rdptr=%d, rd=%d\n", + readl(ch->wrptr), readl(ch->rdptr), rd); + pr_debug("recv smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n", + msg->channel, msg->type, msg->flag, msg->value); + +recv_failed: + mutex_unlock(&(ch->rxlock)); + atomic_dec(&(ipc->busy[msg->channel])); + return rval; +} + + +#if defined(CONFIG_DEBUG_FS) +static int smsg_debug_show(struct seq_file *m, void *private) +{ + struct smsg_ipc *smsg_sipc = NULL; + int i, j; + + for (i = 0; i < SIPC_ID_NR; i++) { + smsg_sipc = smsg_ipcs[i]; + if (!smsg_sipc) { + continue; + } + seq_printf(m, "sipc: %s: \n", smsg_sipc->name); + seq_printf(m, "dst: 0x%0x, irq: 0x%0x\n", + smsg_sipc->dst, smsg_sipc->irq); + seq_printf(m, "txbufAddr: 0x%0x, txbufsize: 0x%0x, txbufrdptr: [0x%0x]=%lu, txbufwrptr: [0x%0x]=%lu\n", + smsg_sipc->txbuf_addr, smsg_sipc->txbuf_size, smsg_sipc->txbuf_rdptr, readl(smsg_sipc->txbuf_rdptr), smsg_sipc->txbuf_wrptr, readl(smsg_sipc->txbuf_wrptr)); + seq_printf(m, "rxbufAddr: 0x%0x, rxbufsize: 0x%0x, rxbufrdptr: [0x%0x]=%lu, rxbufwrptr: [0x%0x]=%lu\n", + smsg_sipc->rxbuf_addr, smsg_sipc->rxbuf_size, smsg_sipc->rxbuf_rdptr, readl(smsg_sipc->rxbuf_rdptr), smsg_sipc->rxbuf_wrptr, readl(smsg_sipc->rxbuf_wrptr)); + + for (j=0; j<SMSG_CH_NR; j++) { + seq_printf(m, "channel[%d] states: %d\n", j, smsg_sipc->states[j]); + } + } + return 0; +} + +static int smsg_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, smsg_debug_show, inode->i_private); +} + +static const struct file_operations smsg_debug_fops = { + .open = smsg_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int smsg_init_debugfs(void *root ) +{ + if (!root) + return -ENXIO; + debugfs_create_file("smsg", S_IRUGO, (struct dentry *)root, NULL, &smsg_debug_fops); + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +static int sipc_syscore_suspend(void) +{ + int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0; + return ret; +} + +static struct syscore_ops sipc_syscore_ops = { + .suspend = sipc_syscore_suspend, +}; + +int smsg_suspend_init(void) +{ + wake_lock_init(&sipc_wake_lock, WAKE_LOCK_SUSPEND, "sipc-smsg"); + register_syscore_ops(&sipc_syscore_ops); + + return 0; +} + + +EXPORT_SYMBOL(smsg_ch_open); +EXPORT_SYMBOL(smsg_ch_close); +EXPORT_SYMBOL(smsg_send); +EXPORT_SYMBOL(smsg_recv); + +MODULE_AUTHOR("Chen Gaopeng"); +MODULE_DESCRIPTION("SIPC/SMSG driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/spipe.c b/drivers/sipc/spipe.c new file mode 100644 index 00000000..0cee34fb --- /dev/null +++ b/drivers/sipc/spipe.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#ifdef CONFIG_OF +#include <linux/of_device.h> +#endif + +#include <linux/sipc.h> +#include <linux/spipe.h> + +struct spipe_device { + struct spipe_init_data *init; + int major; + int minor; + struct cdev cdev; +}; + +struct spipe_sbuf { + uint8_t dst; + uint8_t channel; + uint32_t bufid; +}; + +static struct class *spipe_class; + +static int spipe_open(struct inode *inode, struct file *filp) +{ + int minor = iminor(filp->f_path.dentry->d_inode); + struct spipe_device *spipe; + struct spipe_sbuf *sbuf; + + spipe = container_of(inode->i_cdev, struct spipe_device, cdev); + if (sbuf_status(spipe->init->dst, spipe->init->channel) != 0) { + printk(KERN_ERR "spipe %d-%d not ready to open!\n", spipe->init->dst, spipe->init->channel); + filp->private_data = NULL; + return -ENODEV; + } + + sbuf = kmalloc(sizeof(struct spipe_sbuf), GFP_KERNEL); + if (!sbuf) { + return -ENOMEM; + } + filp->private_data = sbuf; + + sbuf->dst = spipe->init->dst; + sbuf->channel = spipe->init->channel; + sbuf->bufid = minor - spipe->minor; + + return 0; +} + +static int spipe_release(struct inode *inode, struct file *filp) +{ + struct spipe_sbuf *sbuf = filp->private_data; + + if (sbuf) { + kfree(sbuf); + } + + return 0; +} + +static ssize_t spipe_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct spipe_sbuf *sbuf = filp->private_data; + int timeout = -1; + + if (filp->f_flags & O_NONBLOCK) { + timeout = 0; + } + + return sbuf_read(sbuf->dst, sbuf->channel, sbuf->bufid, + (void *)buf, count, timeout); +} + +static ssize_t spipe_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct spipe_sbuf *sbuf = filp->private_data; + int timeout = -1; + + if (filp->f_flags & O_NONBLOCK) { + timeout = 0; + } + + return sbuf_write(sbuf->dst, sbuf->channel, sbuf->bufid, + (void *)buf, count, timeout); +} + +static unsigned int spipe_poll(struct file *filp, poll_table *wait) +{ + struct spipe_sbuf *sbuf = filp->private_data; + + return sbuf_poll_wait(sbuf->dst, sbuf->channel, sbuf->bufid, + filp, wait); +} + +static long spipe_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static const struct file_operations spipe_fops = { + .open = spipe_open, + .release = spipe_release, + .read = spipe_read, + .write = spipe_write, + .poll = spipe_poll, + .unlocked_ioctl = spipe_ioctl, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int spipe_parse_dt(struct spipe_init_data **init, struct device *dev) +{ +#ifdef CONFIG_OF + struct device_node *np = dev->of_node; + struct spipe_init_data *pdata = NULL; + int ret; + uint32_t data; + + pdata = kzalloc(sizeof(struct spipe_init_data), GFP_KERNEL); + if (!pdata) { + printk(KERN_ERR "Failed to allocate pdata memory\n"); + return -ENOMEM; + } + + ret = of_property_read_string(np, "sprd,name", (const char**)&pdata->name); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->dst = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->channel = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,ringnr", (uint32_t *)&pdata->ringnr); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np, "sprd,size-rxbuf", (uint32_t *)&pdata->rxbuf_size); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np, "sprd,size-txbuf", (uint32_t *)&pdata->txbuf_size); + if (ret) { + goto error; + } + *init = pdata; + return ret; +error: + kfree(pdata); + *init = NULL; + return ret; +#else + return -ENODEV; +#endif +} + +static inline void spipe_destroy_pdata(struct spipe_init_data **init) +{ +#ifdef CONFIG_OF + struct spipe_init_data *pdata = *init; + + if (pdata) { + kfree(pdata); + } + + *init = NULL; +#else + return; +#endif +} + +static int spipe_probe(struct platform_device *pdev) +{ + struct spipe_init_data *init = pdev->dev.platform_data; + struct spipe_device *spipe; + dev_t devid; + int i, rval; + + if (pdev->dev.of_node && !init) { + rval = spipe_parse_dt(&init, &pdev->dev); + if (rval) { + printk(KERN_ERR "Failed to parse spipe device tree, ret=%d\n", rval); + return rval; + } + } + pr_info("spipe: after parse device tree, name=%s, dst=%u, channel=%u, ringnr=%u, rxbuf_size=%u, txbuf_size=%u\n", + init->name, init->dst, init->channel, init->ringnr, init->rxbuf_size, init->txbuf_size); + + + rval = sbuf_create(init->dst, init->channel, init->ringnr, + init->txbuf_size, init->rxbuf_size); + if (rval != 0) { + printk(KERN_ERR "Failed to create sbuf: %d\n", rval); + spipe_destroy_pdata(&init); + return rval; + } + + spipe = kzalloc(sizeof(struct spipe_device), GFP_KERNEL); + if (spipe == NULL) { + sbuf_destroy(init->dst, init->channel); + spipe_destroy_pdata(&init); + printk(KERN_ERR "Failed to allocate spipe_device\n"); + return -ENOMEM; + } + + rval = alloc_chrdev_region(&devid, 0, init->ringnr, init->name); + if (rval != 0) { + sbuf_destroy(init->dst, init->channel); + kfree(spipe); + spipe_destroy_pdata(&init); + printk(KERN_ERR "Failed to alloc spipe chrdev\n"); + return rval; + } + + cdev_init(&(spipe->cdev), &spipe_fops); + rval = cdev_add(&(spipe->cdev), devid, init->ringnr); + if (rval != 0) { + sbuf_destroy(init->dst, init->channel); + kfree(spipe); + unregister_chrdev_region(devid, init->ringnr); + spipe_destroy_pdata(&init); + printk(KERN_ERR "Failed to add spipe cdev\n"); + return rval; + } + + spipe->major = MAJOR(devid); + spipe->minor = MINOR(devid); + if (init->ringnr > 1) { + for (i = 0; i < init->ringnr; i++) { + device_create(spipe_class, NULL, + MKDEV(spipe->major, spipe->minor + i), + NULL, "%s%d", init->name, i); + } + } else { + device_create(spipe_class, NULL, + MKDEV(spipe->major, spipe->minor), + NULL, "%s", init->name); + } + + spipe->init = init; + + platform_set_drvdata(pdev, spipe); + + return 0; +} + +static int spipe_remove(struct platform_device *pdev) +{ + struct spipe_device *spipe = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < spipe->init->ringnr; i++) { + device_destroy(spipe_class, + MKDEV(spipe->major, spipe->minor + i)); + } + cdev_del(&(spipe->cdev)); + unregister_chrdev_region( + MKDEV(spipe->major, spipe->minor), spipe->init->ringnr); + + sbuf_destroy(spipe->init->dst, spipe->init->channel); + + spipe_destroy_pdata(&spipe->init); + + kfree(spipe); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id spipe_match_table[] = { + {.compatible = "sprd,spipe", }, + { }, +}; + +static struct platform_driver spipe_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "spipe", + .of_match_table = spipe_match_table, + }, + .probe = spipe_probe, + .remove = spipe_remove, +}; + +static int __init spipe_init(void) +{ + spipe_class = class_create(THIS_MODULE, "spipe"); + if (IS_ERR(spipe_class)) + return PTR_ERR(spipe_class); + + return platform_driver_register(&spipe_driver); +} + +static void __exit spipe_exit(void) +{ + class_destroy(spipe_class); + platform_driver_unregister(&spipe_driver); +} + +module_init(spipe_init); +module_exit(spipe_exit); + +MODULE_AUTHOR("Chen Gaopeng"); +MODULE_DESCRIPTION("SIPC/SPIPE driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/spool.c b/drivers/sipc/spool.c new file mode 100644 index 00000000..3db07e2e --- /dev/null +++ b/drivers/sipc/spool.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/spool.h> +#include <linux/sipc.h> + +struct spool_device { + struct spool_init_data *init; + int major; + int minor; + struct cdev cdev; +}; + +struct spool_sblock { + uint8_t dst; + uint8_t channel; +}; + +static struct class *spool_class; + +static int spool_open(struct inode *inode, struct file *filp) +{ + static struct spool_device *spool; + struct spool_sblock *sblock; + + spool = container_of(inode->i_cdev, struct spool_device, cdev); + sblock = kmalloc(sizeof(struct spool_sblock), GFP_KERNEL); + if (!sblock) { + return -ENOMEM; + } + filp->private_data = sblock; + + sblock->dst = spool->init->dst; + sblock->channel = spool->init->channel; + + return 0; +} + +static int spool_release(struct inode *inode, struct file *filp) +{ + struct spool_sblock *sblock = filp->private_data; + + kfree(sblock); + + return 0; +} + +static ssize_t spool_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct spool_sblock *sblock = filp->private_data; + int timeout = -1; + int ret = 0; + int rdsize = 0; + struct sblock blk = {0}; + + if (filp->f_flags & O_NONBLOCK) { + timeout = 0; + } + + if((ret = sblock_receive(sblock->dst, sblock->channel, &blk, timeout)) < 0){ + pr_debug("spool_read: failed to receive block!\n"); + return ret; + } + + rdsize = blk.length > count ? count : blk.length; + + if(copy_to_user(buf, blk.addr, rdsize)){ + pr_debug("spool_read: failed to copy to user!\n"); + ret = -EFAULT; + }else{ + ret = rdsize; + } + + if(sblock_release(sblock->dst, sblock->channel, &blk)){ + pr_debug("failed to release block!\n"); + } + + return ret; +} + +static ssize_t spool_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct spool_sblock *sblock = filp->private_data; + int timeout = -1; + int ret = 0; + int wrsize = 0; + int pos = 0; + struct sblock blk = {0}; + size_t len = count; + + if(filp->f_flags & O_NONBLOCK){ + timeout = 0; + } + + do{ + if((ret = sblock_get(sblock->dst, sblock->channel, &blk, timeout)) < 0){ + printk(KERN_WARNING "spool_write: failed to get block!\n"); + return ret; + } + + wrsize = (blk.length > len ? len : blk.length); + pr_debug("spool_write: blk_len %d, count %d, wsize %d\n", blk.length, len, wrsize); + if(copy_from_user(blk.addr, buf + pos, wrsize)){ + printk(KERN_WARNING "spool_write: failed to copy from user!\n"); + ret = -EFAULT; + }else{ + blk.length = wrsize; + len -= wrsize; + pos += wrsize; + } + + if(sblock_send(sblock->dst, sblock->channel, &blk)){ + pr_debug("spool_write: failed to send block!"); + } + + pr_debug("spool_write len= %u, ret= %d\n", len, ret); + }while(len > 0 && ret == 0); + + return count - len; +} + +static unsigned int spool_poll(struct file *filp, poll_table *wait) +{ + return 0; +} + +static long spool_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static const struct file_operations spool_fops = { + .open = spool_open, + .release = spool_release, + .read = spool_read, + .write = spool_write, + .poll = spool_poll, + .unlocked_ioctl = spool_ioctl, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int __devinit spool_probe(struct platform_device *pdev) +{ + struct spool_init_data *init = pdev->dev.platform_data; + struct spool_device *spool; + dev_t devid; + int rval; + + rval = sblock_create(init->dst, init->channel, init->txblocknum, + init->txblocksize, init->rxblocknum, init->rxblocksize); + if (rval != 0) { + printk(KERN_ERR "Failed to create sblock: %d\n", rval); + return rval; + } + + spool = kzalloc(sizeof(struct spool_device), GFP_KERNEL); + if (spool == NULL) { + sblock_destroy(init->dst, init->channel); + printk(KERN_ERR "Failed to allocate spool_device\n"); + return -ENOMEM; + } + + rval = alloc_chrdev_region(&devid, 0, 1, init->name); + if (rval != 0) { + sblock_destroy(init->dst, init->channel); + kfree(spool); + printk(KERN_ERR "Failed to alloc spool chrdev\n"); + return rval; + } + cdev_init(&(spool->cdev), &spool_fops); + rval = cdev_add(&(spool->cdev), devid, 1); + if (rval != 0) { + sblock_destroy(init->dst, init->channel); + kfree(spool); + unregister_chrdev_region(devid, 1); + printk(KERN_ERR "Failed to add spool cdev\n"); + return rval; + } + + spool->major = MAJOR(devid); + spool->minor = MINOR(devid); + + device_create(spool_class, NULL, + MKDEV(spool->major, spool->minor), + NULL, "%s", init->name); + + spool->init = init; + + platform_set_drvdata(pdev, spool); + + return 0; +} + +static int __devexit spool_remove(struct platform_device *pdev) +{ + struct spool_device *spool = platform_get_drvdata(pdev); + device_destroy(spool_class, MKDEV(spool->major, spool->minor)); + + cdev_del(&(spool->cdev)); + unregister_chrdev_region( + MKDEV(spool->major, spool->minor), 1); + + sblock_destroy(spool->init->dst, spool->init->channel); + kfree(spool); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver spool_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "spool", + }, + .probe = spool_probe, + .remove = __devexit_p(spool_remove), +}; + +static int __init spool_init(void) +{ + spool_class = class_create(THIS_MODULE, "spool"); + if (IS_ERR(spool_class)) + return PTR_ERR(spool_class); + + return platform_driver_register(&spool_driver); +} + +static void __exit spool_exit(void) +{ + class_destroy(spool_class); + platform_driver_unregister(&spool_driver); +} + +module_init(spool_init); +module_exit(spool_exit); + +MODULE_AUTHOR("Qiu Yi"); +MODULE_DESCRIPTION("SIPC/SPOOL driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sipc/stty.c b/drivers/sipc/stty.c new file mode 100644 index 00000000..1f1d5c31 --- /dev/null +++ b/drivers/sipc/stty.c @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/sipc.h> +#include <linux/stty.h> +#include <linux/types.h> +#include <linux/kdev_t.h> +#include <linux/tty.h> +#include <linux/vt_kern.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/delay.h> +#ifdef CONFIG_OF +#include <linux/of_device.h> +#endif + +#include <linux/compat.h> +#include <linux/tty_flip.h> +#include <linux/kthread.h> + +#ifdef CONFIG_SPRD_2351 +#include <linux/sprd_2351.h> +#endif + + +#define STTY_DEV_MAX_NR 1 +#define STTY_MAX_DATA_LEN 4096 + +#define STTY_STATE_OPEN 1 +#define STTY_STATE_CLOSE 0 + +struct stty_device { + struct stty_init_data *pdata; + struct tty_port *port; + struct tty_struct *tty; + struct tty_driver *driver; + + /* stty state */ + uint32_t state; + struct mutex stat_lock; +}; + +static void stty_handler (int event, void* data) +{ + struct stty_device *stty = data; + int i, cnt = 0; + unsigned char buf[STTY_MAX_DATA_LEN] = {0}; + + pr_debug("stty handler event=%d \n", event); + + switch(event) { + case SBUF_NOTIFY_WRITE: + break; + case SBUF_NOTIFY_READ: + cnt = sbuf_read(stty->pdata->dst, stty->pdata->channel, + stty->pdata->bufid,(void *)buf, STTY_MAX_DATA_LEN, 0); + pr_debug("stty handler read data len =%d \n", cnt); + mutex_lock(&(stty->stat_lock)); + if ((stty->state == STTY_STATE_OPEN) && (cnt > 0)) { + for(i = 0; i < cnt; i++) { + tty_insert_flip_char(stty->port, buf[i], TTY_NORMAL); + } + tty_schedule_flip(stty->port); + } + mutex_unlock(&(stty->stat_lock)); + break; + default: + printk(KERN_ERR "Received event is invalid(event=%d)\n", event); + } + + return; +} + +static int stty_open(struct tty_struct *tty, struct file * filp) +{ + struct stty_device *stty = NULL; + struct tty_driver *driver = NULL; + + if (tty == NULL) { + printk(KERN_ERR "stty open input tty is NULL!\n"); + return -ENOMEM; + } + driver = tty->driver; + stty = (struct stty_device *)driver->driver_state; + + if(stty == NULL) { + printk(KERN_ERR "stty open input stty NULL!\n"); + return -ENOMEM; + } + + if (sbuf_status(stty->pdata->dst, stty->pdata->channel) != 0) { + printk(KERN_ERR "stty_open sbuf not ready to open!dst=%d,channel=%d\n" + ,stty->pdata->dst,stty->pdata->channel); + return -ENODEV; + } + stty->tty = tty; + tty->driver_data = (void *)stty; + + mutex_lock(&(stty->stat_lock)); + stty->state = STTY_STATE_OPEN; + mutex_unlock(&(stty->stat_lock)); + +#ifdef CONFIG_SPRD_2351 + rf2351_gpio_ctrl_power_enable(1); +#endif +#ifdef CONFIG_ARCH_SCX20 + rf2351_vddwpa_ctrl_power_enable(1); +#endif + + pr_debug("stty_open device success! \n"); + + return 0; +} + +static void stty_close(struct tty_struct *tty, struct file * filp) +{ + struct stty_device *stty = NULL; + + if (tty == NULL) { + printk(KERN_ERR "stty close input tty is NULL!\n"); + return; + } + stty = (struct stty_device *) tty->driver_data; + if (stty == NULL) { + printk(KERN_ERR "stty close s tty is NULL!\n"); + return; + } + + mutex_lock(&(stty->stat_lock)); + stty->state = STTY_STATE_CLOSE; + mutex_unlock(&(stty->stat_lock)); + + pr_debug("stty_close device success !\n"); + +#ifdef CONFIG_SPRD_2351 + rf2351_gpio_ctrl_power_enable(0); +#endif +#ifdef CONFIG_ARCH_SCX20 + rf2351_vddwpa_ctrl_power_enable(0); +#endif + + return; +} + +static int stty_write(struct tty_struct * tty, + const unsigned char *buf, int count) +{ + struct stty_device *stty = tty->driver_data; + int cnt; + pr_debug("stty write count=%d \n", count); + + cnt = sbuf_write(stty->pdata->dst, stty->pdata->channel, + stty->pdata->bufid, (void *)buf, count, -1); + return cnt; +} + + +static void stty_flush_chars(struct tty_struct *tty) +{ + return; +} + +static int stty_write_room(struct tty_struct *tty) +{ + return INT_MAX; +} + +static const struct tty_operations stty_ops = { + .open = stty_open, + .close = stty_close, + .write = stty_write, + .flush_chars = stty_flush_chars, + .write_room = stty_write_room, +}; + +static struct tty_port *stty_port_init() +{ + struct tty_port *port = NULL; + + port = kzalloc(sizeof(struct tty_port),GFP_KERNEL); + if (port == NULL) { + printk(KERN_ERR "stty_port_init Failed to allocate device!\n"); + return NULL; + } + tty_port_init(port); + return port; +} + +static int stty_driver_init(struct stty_device *device) +{ + struct tty_driver *driver; + int ret = 0; + + mutex_init(&(device->stat_lock)); + + device->port = stty_port_init(); + if (!device->port) { + return -ENOMEM; + } + + driver = alloc_tty_driver(STTY_DEV_MAX_NR); + if (!driver) + return -ENOMEM; + /* + * Initialize the tty_driver structure + * Entries in stty_driver that are NOT initialized: + * proc_entry, set_termios, flush_buffer, set_ldisc, write_proc + */ + driver->owner = THIS_MODULE; + driver->driver_name = device->pdata->name; + driver->name = device->pdata->name; + driver->major = 0; + driver->type = TTY_DRIVER_TYPE_SYSTEM; + driver->subtype = SYSTEM_TYPE_TTY; + driver->init_termios = tty_std_termios; + driver->driver_state = (void*)device; + device->driver = driver; + /* initialize the tty driver */ + tty_set_operations(driver, &stty_ops); + tty_port_link_device(device->port, driver, 0); + ret = tty_register_driver(driver); + if (ret) { + put_tty_driver(driver); + tty_port_destroy(device->port); + return ret; + } + return ret; +} + +static void stty_driver_exit(struct stty_device *device) +{ + struct tty_driver *driver = device->driver; + tty_unregister_driver(driver); + tty_port_destroy(device->port); +} + +static int stty_parse_dt(struct stty_init_data **init, struct device *dev) +{ +#ifdef CONFIG_OF + struct of_device_node *np = dev->of_node; + struct stty_init_data *pdata = NULL; + int ret; + uint32_t data; + + pdata = kzalloc(sizeof(struct stty_init_data), GFP_KERNEL); + if (!pdata) { + return -ENOMEM; + } + + ret = of_property_read_string(np, "sprd,name", (const char **)&pdata->name); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->dst = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->channel = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,bufid", (uint32_t *)&pdata->bufid); + if (ret) { + goto error; + } + + *init = pdata; + return 0; +error: + kfree(pdata); + *init = NULL; + return ret; +#else + return -ENODEV; +#endif +} + +static inline void stty_destroy_pdata(struct stty_init_data **init) +{ +#ifdef CONFIG_OF + struct stty_init_data *pdata = *init; + + if (pdata) { + kfree(pdata); + } + + *init = NULL; +#else + return; +#endif +} + + +static int stty_probe(struct platform_device *pdev) +{ + struct stty_init_data *pdata = (struct stty_init_data*)pdev->dev.platform_data; + struct stty_device *stty; + int rval= 0; + + if (pdev->dev.of_node && !pdata) { + rval = stty_parse_dt(&pdata, &pdev->dev); + if (rval) { + printk(KERN_ERR "failed to parse styy device tree, ret=%d\n", rval); + return rval; + } + } + pr_info("stty: after parse device tree, name=%s, dst=%u, channel=%u, bufid=%u\n", + pdata->name, pdata->dst, pdata->channel, pdata->bufid); + + stty = kzalloc(sizeof(struct stty_device),GFP_KERNEL); + if (stty == NULL) { + stty_destroy_pdata(&pdata); + printk(KERN_ERR "stty Failed to allocate device!\n"); + return -ENOMEM; + } + + stty->pdata = pdata; + rval = stty_driver_init(stty); + if (rval) { + kfree(stty); + stty_destroy_pdata(&pdata); + printk(KERN_ERR "stty driver init error!\n"); + return -EINVAL; + } + + rval = sbuf_register_notifier(pdata->dst, pdata->channel, + pdata->bufid, stty_handler, stty); + if (rval) { + stty_driver_exit(stty); + kfree(stty->port); + kfree(stty); + printk(KERN_ERR "regitster notifier failed (%d)\n", rval); + return rval; + } + + printk( "stty_probe init device addr: 0x%0x\n", (void *)stty); + platform_set_drvdata(pdev, stty); + + return 0; +} + +static int stty_remove(struct platform_device *pdev) +{ + struct stty_device *stty = platform_get_drvdata(pdev); + int rval; + + rval = sbuf_register_notifier(stty->pdata->dst, stty->pdata->channel, + stty->pdata->bufid, NULL, NULL); + if (rval) { + printk(KERN_ERR " unregitster notifier failed (%d)\n", rval); + return rval; + } + + stty_driver_exit(stty); + kfree(stty->port); + stty_destroy_pdata(&stty->pdata); + kfree(stty); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id stty_match_table[] = { + { .compatible = "sprd,stty4bt", }, + { }, +}; + +static struct platform_driver stty_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sttybt", + .of_match_table = stty_match_table, + }, + .probe = stty_probe, + .remove = stty_remove, +}; + +static int __init stty_init(void) +{ + return platform_driver_register(&stty_driver); +} + +static void __exit stty_exit(void) +{ + platform_driver_unregister(&stty_driver); +} + +late_initcall(stty_init); +module_exit(stty_exit); + +MODULE_AUTHOR("Dewu Jiang"); +MODULE_DESCRIPTION("SIPC/stty driver"); +MODULE_LICENSE("GPL"); |
