From f7415e595e613e37c5f7c4cebbc9a67a8eb68592 Mon Sep 17 00:00:00 2001 From: Grissiom Date: Tue, 6 Jan 2015 18:50:22 +0800 Subject: [PATCH] VBus: added Currently only lpc43xx is supported. --- bsp/lpc43xx/M0/applications/application.c | 71 +- bsp/lpc43xx/M0/applications/board.c | 2 + bsp/lpc43xx/M0/applications/vbus_conf.h | 13 + bsp/lpc43xx/M0/applications/vbus_drv.c | 57 + bsp/lpc43xx/M0/applications/vbus_hw.h | 53 + bsp/lpc43xx/M0/rtconfig.h | 3 +- bsp/lpc43xx/M0/rtthread-lpc43xx.ld | 6 + bsp/lpc43xx/M0/rtthread_lpc43xx.sct | 3 + bsp/lpc43xx/M0/vbus_local_conf.h | 8 + bsp/lpc43xx/M4/applications/application.c | 77 +- bsp/lpc43xx/M4/applications/board.c | 1 + bsp/lpc43xx/M4/applications/vbus_conf.h | 13 + bsp/lpc43xx/M4/applications/vbus_drv.c | 57 + bsp/lpc43xx/M4/applications/vbus_hw.h | 53 + bsp/lpc43xx/M4/rtconfig.h | 3 +- bsp/lpc43xx/M4/rtthread-lpc43xx.ld | 7 + bsp/lpc43xx/M4/rtthread_lpc43xx.sct | 3 + bsp/lpc43xx/M4/vbus_local_conf.h | 8 + components/vbus/SConscript | 29 + components/vbus/prio_queue.c | 275 +++++ components/vbus/prio_queue.h | 44 + components/vbus/rt_watermark_queue.c | 70 ++ components/vbus/rt_watermark_queue.h | 148 +++ components/vbus/share_hdr/vbus_api.h | 79 ++ components/vbus/vbus.c | 1370 +++++++++++++++++++++ components/vbus/vbus.h | 195 +++ components/vbus/vbus_chnx.c | 286 +++++ 27 files changed, 2889 insertions(+), 45 deletions(-) create mode 100644 bsp/lpc43xx/M0/applications/vbus_conf.h create mode 100644 bsp/lpc43xx/M0/applications/vbus_drv.c create mode 100644 bsp/lpc43xx/M0/applications/vbus_hw.h create mode 100644 bsp/lpc43xx/M0/vbus_local_conf.h create mode 100644 bsp/lpc43xx/M4/applications/vbus_conf.h create mode 100644 bsp/lpc43xx/M4/applications/vbus_drv.c create mode 100644 bsp/lpc43xx/M4/applications/vbus_hw.h create mode 100644 bsp/lpc43xx/M4/vbus_local_conf.h create mode 100644 components/vbus/SConscript create mode 100644 components/vbus/prio_queue.c create mode 100644 components/vbus/prio_queue.h create mode 100644 components/vbus/rt_watermark_queue.c create mode 100644 components/vbus/rt_watermark_queue.h create mode 100644 components/vbus/share_hdr/vbus_api.h create mode 100644 components/vbus/vbus.c create mode 100644 components/vbus/vbus.h create mode 100644 components/vbus/vbus_chnx.c diff --git a/bsp/lpc43xx/M0/applications/application.c b/bsp/lpc43xx/M0/applications/application.c index 209b7071b1..8e861873c8 100644 --- a/bsp/lpc43xx/M0/applications/application.c +++ b/bsp/lpc43xx/M0/applications/application.c @@ -33,38 +33,67 @@ /* thread phase init */ void rt_init_thread_entry(void *parameter) { +#ifdef RT_USING_LOGTRACE + log_trace_init(); + log_trace_set_device(RT_CONSOLE_DEVICE_NAME); +#endif + #ifdef RT_USING_FINSH /* initialize finsh */ finsh_system_init(); finsh_set_device(RT_CONSOLE_DEVICE_NAME); #endif + +#ifdef RT_USING_VBUS + rt_vbus_do_init(); +#endif } + /*the led thread*/ ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t led_stack[ 512 ]; +static rt_uint8_t led_stack[1024]; static struct rt_thread led_thread; static void led_thread_entry(void *parameter) { - rt_uint8_t led_value = 0; rt_device_t led_dev; + rt_device_t vbus_dev; + rt_err_t err; + rt_led_hw_init(); + led_dev = rt_device_find("led"); if (led_dev == RT_NULL) { - rt_kprintf("can not find the led device!\n"); + rt_kprintf("can not find the led device\n"); return; } + + vbus_dev = rt_device_find("vecho"); + if (vbus_dev == RT_NULL) + { + rt_kprintf("can not find the vbus device\n"); + return; + } + + err = rt_device_open(vbus_dev, RT_DEVICE_OFLAG_RDWR); + if (err != RT_EOK) + { + rt_kprintf("open vbus failed: %d\n", err); + return; + } + while (1) { - /* led0 on */ - led_value = 1; - led_dev->write(led_dev, 1, &led_value, 1); - rt_thread_delay(RT_TICK_PER_SECOND / 2); /* sleep 0.5 second and switch to other thread */ - - /* led0 off */ - led_value = 0; - led_dev->write(led_dev, 1, &led_value, 1); - rt_thread_delay(RT_TICK_PER_SECOND / 2); + rt_uint8_t led_value; + int len; + + len = rt_device_read(vbus_dev, 0, &led_value, sizeof(led_value)); + if (len <= 0) + { + rt_kprintf("vbus read err: %d, %d\n", len, rt_get_errno()); + } + + led_dev->write(led_dev, 1, &led_value, sizeof(led_value)); } } @@ -74,17 +103,15 @@ int rt_application_init(void) rt_err_t result; tid = rt_thread_create("init", rt_init_thread_entry, RT_NULL, - 2048, RT_THREAD_PRIORITY_MAX / 3, 20); - if (tid != RT_NULL) rt_thread_startup(tid); + 2048, 3, 20); + if (tid != RT_NULL) + rt_thread_startup(tid); + /* init led thread */ - result = rt_thread_init(&led_thread, - "led", - led_thread_entry, - RT_NULL, - (rt_uint8_t *)&led_stack[0], - sizeof(led_stack), - 20, - 5); + result = rt_thread_init(&led_thread, "led", + led_thread_entry, RT_NULL, + (rt_uint8_t *)&led_stack[0], sizeof(led_stack), + 20, 5); if (result == RT_EOK) { rt_thread_startup(&led_thread); diff --git a/bsp/lpc43xx/M0/applications/board.c b/bsp/lpc43xx/M0/applications/board.c index c3377aac2a..324e1bc7ae 100644 --- a/bsp/lpc43xx/M0/applications/board.c +++ b/bsp/lpc43xx/M0/applications/board.c @@ -62,5 +62,7 @@ void rt_hw_board_init() /* setup the console device */ rt_console_set_device(RT_CONSOLE_DEVICE_NAME); + + rt_kprintf("timer compval: %d\n", LPC_RITIMER->COMPVAL); } diff --git a/bsp/lpc43xx/M0/applications/vbus_conf.h b/bsp/lpc43xx/M0/applications/vbus_conf.h new file mode 100644 index 0000000000..62bec83227 --- /dev/null +++ b/bsp/lpc43xx/M0/applications/vbus_conf.h @@ -0,0 +1,13 @@ +#ifndef __VBUS_CONF_H__ +#define __VBUS_CONF_H__ + +/* Number of blocks in VBus. The total size of VBus is + * RT_VMM_RB_BLK_NR * 64byte * 2. */ +#define RT_VMM_RB_BLK_NR 20 + +/* We don't use the IRQ number to trigger IRQ in this BSP. */ +#define RT_VBUS_GUEST_VIRQ 0 +#define RT_VBUS_HOST_VIRQ 0 + +#endif /* end of include guard: __VBUS_CONF_H__ */ + diff --git a/bsp/lpc43xx/M0/applications/vbus_drv.c b/bsp/lpc43xx/M0/applications/vbus_drv.c new file mode 100644 index 0000000000..ad9c8f9210 --- /dev/null +++ b/bsp/lpc43xx/M0/applications/vbus_drv.c @@ -0,0 +1,57 @@ +#include + +#ifdef RT_USING_VBUS +#include +#include +#include + +struct rt_vbus_ring rt_vbus_rings[2] SECTION("vbus_ring"); + +int rt_vbus_do_init(void) +{ + return rt_vbus_init(&rt_vbus_rings[1], &rt_vbus_rings[0]); +} +INIT_COMPONENT_EXPORT(rt_vbus_do_init); + +int rt_vbus_hw_init(void) +{ + NVIC_ClearPendingIRQ(M0_M4CORE_IRQn); + NVIC_EnableIRQ(M0_M4CORE_IRQn); + return 0; +} + +void M4CORE_IRQHandler(void) +{ + LPC_CREG->M4TXEVENT = 0; + rt_vbus_isr(M0_M4CORE_IRQn, RT_NULL); +} + +int rt_vbus_hw_eoi(int irqnr, void *param) +{ + /* Nothing to do here as we cleared the interrupt in IRQHandler. */ + return 0; +} + +struct rt_vbus_dev rt_vbus_chn_devx[] = { + { + .req = + { + .prio = 30, + .name = "vecho", + .is_server = 0, + .recv_wm.low = RT_VMM_RB_BLK_NR / 3, + .recv_wm.high = RT_VMM_RB_BLK_NR * 2 / 3, + .post_wm.low = RT_VMM_RB_BLK_NR / 3, + .post_wm.high = RT_VMM_RB_BLK_NR * 2 / 3, + } + }, + { + .req = + { + .name = RT_NULL, + } + }, +}; + +#endif /* RT_USING_VBUS */ + diff --git a/bsp/lpc43xx/M0/applications/vbus_hw.h b/bsp/lpc43xx/M0/applications/vbus_hw.h new file mode 100644 index 0000000000..758d2e9e5c --- /dev/null +++ b/bsp/lpc43xx/M0/applications/vbus_hw.h @@ -0,0 +1,53 @@ +/* + * VMM Bus + * + * COPYRIGHT (C) 2014, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2014-04-15 Grissiom init commit + */ + +#include +#include + +rt_inline void rt_vbus_tick(unsigned int target_cpu, unsigned int irqnr) +{ + __SEV(); +} + +/* Read memory barrier. */ +rt_inline void rt_vbus_smp_rmb(void) +{ + __DMB(); +} + +/* Write memory barrier. */ +rt_inline void rt_vbus_smp_wmb(void) +{ + __DSB(); +} + +/* General memory barrier. */ +rt_inline void rt_vbus_smp_mb(void) +{ + __DSB(); +} diff --git a/bsp/lpc43xx/M0/rtconfig.h b/bsp/lpc43xx/M0/rtconfig.h index a1ac5509ab..77846191fc 100644 --- a/bsp/lpc43xx/M0/rtconfig.h +++ b/bsp/lpc43xx/M0/rtconfig.h @@ -224,7 +224,8 @@ #define RT_LWIP_MSKADDR3 0 // - +#define RT_USING_VBUS +#define RT_USING_LOGTRACE // diff --git a/bsp/lpc43xx/M0/rtthread-lpc43xx.ld b/bsp/lpc43xx/M0/rtthread-lpc43xx.ld index 3419e6f235..e8ef2fe490 100644 --- a/bsp/lpc43xx/M0/rtthread-lpc43xx.ld +++ b/bsp/lpc43xx/M0/rtthread-lpc43xx.ld @@ -8,6 +8,7 @@ MEMORY { CODE (rx) : ORIGIN = 0x1B000000, LENGTH = 0x00080000 DATA (rw) : ORIGIN = 0x10080000, LENGTH = 0x00008000 + AHBRAM (rw) : ORIGIN = 0x20000000, LENGTH = 0x00010000 } ENTRY(Reset_Handler) _system_stack_size = 0x200; @@ -95,6 +96,11 @@ SECTIONS } > DATA __bss_end = .; + .vbus_ring (NOLOAD) : + { + *(vbus_ring) + } > AHBRAM + _end = .; /* Stabs debugging sections. */ diff --git a/bsp/lpc43xx/M0/rtthread_lpc43xx.sct b/bsp/lpc43xx/M0/rtthread_lpc43xx.sct index 2232a4aa9b..d0fd443a85 100644 --- a/bsp/lpc43xx/M0/rtthread_lpc43xx.sct +++ b/bsp/lpc43xx/M0/rtthread_lpc43xx.sct @@ -11,5 +11,8 @@ LR_IROM2 0x1B000000 0x00080000 { ; load region size_region RW_IRAM2 0x10080000 0x00008000 { ; RW data .ANY (+RW +ZI) } + RW_AHBRAM 0x20000000 0x00010000 { ; RW data + * (vbus_ring) + } } diff --git a/bsp/lpc43xx/M0/vbus_local_conf.h b/bsp/lpc43xx/M0/vbus_local_conf.h new file mode 100644 index 0000000000..571c51e612 --- /dev/null +++ b/bsp/lpc43xx/M0/vbus_local_conf.h @@ -0,0 +1,8 @@ +#ifndef __VBUS_LOCAL_CONF_H__ +#define __VBUS_LOCAL_CONF_H__ + +#define RT_VBUS_USING_FLOW_CONTROL + +#define RT_VBUS_USING_TESTS + +#endif /* end of include guard: __VBUS_LOCAL_CONF_H__ */ diff --git a/bsp/lpc43xx/M4/applications/application.c b/bsp/lpc43xx/M4/applications/application.c index 8ca6f823f1..6575991895 100644 --- a/bsp/lpc43xx/M4/applications/application.c +++ b/bsp/lpc43xx/M4/applications/application.c @@ -58,12 +58,26 @@ static void _boot_M0(void) /* thread phase init */ void rt_init_thread_entry(void *parameter) { + /* + *register unsigned int _msp __asm("msp"); + *register unsigned int _psp __asm("psp"); + *rt_kprintf("msp@ %p, psp@ %p\n", _msp, _psp); + */ +#ifdef RT_USING_LOGTRACE + log_trace_init(); + log_trace_set_device(RT_CONSOLE_DEVICE_NAME); +#endif + #ifdef RT_USING_FINSH /* initialize finsh */ finsh_system_init(); finsh_set_device(RT_CONSOLE_DEVICE_NAME); #endif +#ifdef RT_USING_VBUS + rt_vbus_do_init(); +#endif + _boot_M0(); } @@ -73,26 +87,49 @@ static rt_uint8_t led_stack[ 512 ]; static struct rt_thread led_thread; static void led_thread_entry(void *parameter) { - rt_uint8_t led_value = 0; + rt_uint8_t led_value; rt_device_t led_dev; + rt_device_t vbus_dev; + rt_err_t err; + rt_led_hw_init(); + led_dev = rt_device_find("led"); if (led_dev == RT_NULL) { - rt_kprintf("can not find the led device!\n"); + rt_kprintf("can not find the led device\n"); return; } + + vbus_dev = rt_device_find("vecho"); + if (vbus_dev == RT_NULL) + { + rt_kprintf("can not find the vbus device\n"); + return; + } + + err = rt_device_open(vbus_dev, RT_DEVICE_OFLAG_RDWR); + if (err != RT_EOK) + { + rt_kprintf("open vbus failed: %d\n", err); + return; + } + + led_value = 0; while (1) { - /* led0 on */ - led_value = 1; - led_dev->write(led_dev, 0, &led_value, 1); - rt_thread_delay(RT_TICK_PER_SECOND / 2); /* sleep 0.5 second and switch to other thread */ - - /* led0 off */ - led_value = 0; - led_dev->write(led_dev, 0, &led_value, 1); - rt_thread_delay(RT_TICK_PER_SECOND / 2); + int len; + + led_dev->write(led_dev, 0, &led_value, sizeof(led_value)); + + led_value = !led_value; + len = rt_device_write(vbus_dev, 0, &led_value, sizeof(led_value)); + if (len <= 0) + { + rt_kprintf("vbus write err: %d, %d\n", len, rt_get_errno()); + } + + rt_thread_delay(1000); } } @@ -102,17 +139,15 @@ int rt_application_init(void) rt_err_t result; tid = rt_thread_create("init", rt_init_thread_entry, RT_NULL, - 2048, RT_THREAD_PRIORITY_MAX / 3, 20); - if (tid != RT_NULL) rt_thread_startup(tid); + 2048, 3, 20); + if (tid != RT_NULL) + rt_thread_startup(tid); + /* init led thread */ - result = rt_thread_init(&led_thread, - "led", - led_thread_entry, - RT_NULL, - (rt_uint8_t *)&led_stack[0], - sizeof(led_stack), - 20, - 5); + result = rt_thread_init(&led_thread, "led", + led_thread_entry, RT_NULL, + (rt_uint8_t *)&led_stack[0], sizeof(led_stack), + 20, 5); if (result == RT_EOK) { rt_thread_startup(&led_thread); diff --git a/bsp/lpc43xx/M4/applications/board.c b/bsp/lpc43xx/M4/applications/board.c index b7d3ead87e..3358e52c96 100644 --- a/bsp/lpc43xx/M4/applications/board.c +++ b/bsp/lpc43xx/M4/applications/board.c @@ -65,6 +65,7 @@ void rt_hw_board_init() /* setup the console device */ rt_console_set_device(RT_CONSOLE_DEVICE_NAME); + rt_kprintf("timer compval: %d\n", SystemCoreClock / RT_TICK_PER_SECOND - 1); #if LPC_EXT_SDRAM == 1 lpc_sdram_hw_init(); diff --git a/bsp/lpc43xx/M4/applications/vbus_conf.h b/bsp/lpc43xx/M4/applications/vbus_conf.h new file mode 100644 index 0000000000..62bec83227 --- /dev/null +++ b/bsp/lpc43xx/M4/applications/vbus_conf.h @@ -0,0 +1,13 @@ +#ifndef __VBUS_CONF_H__ +#define __VBUS_CONF_H__ + +/* Number of blocks in VBus. The total size of VBus is + * RT_VMM_RB_BLK_NR * 64byte * 2. */ +#define RT_VMM_RB_BLK_NR 20 + +/* We don't use the IRQ number to trigger IRQ in this BSP. */ +#define RT_VBUS_GUEST_VIRQ 0 +#define RT_VBUS_HOST_VIRQ 0 + +#endif /* end of include guard: __VBUS_CONF_H__ */ + diff --git a/bsp/lpc43xx/M4/applications/vbus_drv.c b/bsp/lpc43xx/M4/applications/vbus_drv.c new file mode 100644 index 0000000000..6d85a2e11e --- /dev/null +++ b/bsp/lpc43xx/M4/applications/vbus_drv.c @@ -0,0 +1,57 @@ +#include + +#ifdef RT_USING_VBUS +#include +#include +#include + +struct rt_vbus_ring rt_vbus_rings[2] SECTION("vbus_ring"); + +int rt_vbus_do_init(void) +{ + return rt_vbus_init(&rt_vbus_rings[0], &rt_vbus_rings[1]); +} +INIT_COMPONENT_EXPORT(rt_vbus_do_init); + +int rt_vbus_hw_init(void) +{ + NVIC_ClearPendingIRQ(M0CORE_IRQn); + NVIC_EnableIRQ(M0CORE_IRQn); + return 0; +} + +void M0CORE_IRQHandler(void) +{ + LPC_CREG->M0TXEVENT = 0; + rt_vbus_isr(M0CORE_IRQn, RT_NULL); +} + +int rt_vbus_hw_eoi(int irqnr, void *param) +{ + /* Nothing to do here as we cleared the interrupt in IRQHandler. */ + return 0; +} + +struct rt_vbus_dev rt_vbus_chn_devx[] = { + { + .req = + { + .prio = 30, + .name = "vecho", + .is_server = 1, + .recv_wm.low = RT_VMM_RB_BLK_NR / 3, + .recv_wm.high = RT_VMM_RB_BLK_NR * 2 / 3, + .post_wm.low = RT_VMM_RB_BLK_NR / 3, + .post_wm.high = RT_VMM_RB_BLK_NR * 2 / 3, + } + }, + { + .req = + { + .name = RT_NULL, + } + }, +}; + +#endif /* RT_USING_VBUS */ + diff --git a/bsp/lpc43xx/M4/applications/vbus_hw.h b/bsp/lpc43xx/M4/applications/vbus_hw.h new file mode 100644 index 0000000000..758d2e9e5c --- /dev/null +++ b/bsp/lpc43xx/M4/applications/vbus_hw.h @@ -0,0 +1,53 @@ +/* + * VMM Bus + * + * COPYRIGHT (C) 2014, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2014-04-15 Grissiom init commit + */ + +#include +#include + +rt_inline void rt_vbus_tick(unsigned int target_cpu, unsigned int irqnr) +{ + __SEV(); +} + +/* Read memory barrier. */ +rt_inline void rt_vbus_smp_rmb(void) +{ + __DMB(); +} + +/* Write memory barrier. */ +rt_inline void rt_vbus_smp_wmb(void) +{ + __DSB(); +} + +/* General memory barrier. */ +rt_inline void rt_vbus_smp_mb(void) +{ + __DSB(); +} diff --git a/bsp/lpc43xx/M4/rtconfig.h b/bsp/lpc43xx/M4/rtconfig.h index fb65f5bf3d..31854ca2fc 100644 --- a/bsp/lpc43xx/M4/rtconfig.h +++ b/bsp/lpc43xx/M4/rtconfig.h @@ -223,7 +223,8 @@ #define RT_LWIP_MSKADDR3 0 // - +#define RT_USING_VBUS +#define RT_USING_LOGTRACE // diff --git a/bsp/lpc43xx/M4/rtthread-lpc43xx.ld b/bsp/lpc43xx/M4/rtthread-lpc43xx.ld index 81d1655b52..63057abc40 100644 --- a/bsp/lpc43xx/M4/rtthread-lpc43xx.ld +++ b/bsp/lpc43xx/M4/rtthread-lpc43xx.ld @@ -9,6 +9,7 @@ MEMORY CODE (rx) : ORIGIN = 0x1A000000, LENGTH = 0x00080000 M0CODE (rx) : ORIGIN = 0x1B000000, LENGTH = 0x00080000 DATA (rw) : ORIGIN = 0x10000000, LENGTH = 0x00008000 + AHBRAM (rw) : ORIGIN = 0x20000000, LENGTH = 0x00010000 } ENTRY(Reset_Handler) _system_stack_size = 0x200; @@ -97,6 +98,12 @@ SECTIONS } > DATA __bss_end = .; + .vbus_ring (NOLOAD) : + { + *(vbus_ring) + } > AHBRAM + + .text.M0CODE : { *(M0_CODE) diff --git a/bsp/lpc43xx/M4/rtthread_lpc43xx.sct b/bsp/lpc43xx/M4/rtthread_lpc43xx.sct index c483ea87d2..238ca28c6e 100644 --- a/bsp/lpc43xx/M4/rtthread_lpc43xx.sct +++ b/bsp/lpc43xx/M4/rtthread_lpc43xx.sct @@ -11,6 +11,9 @@ LR_IROM1 0x1A000000 0x00080000 { ; load region size_region RW_IRAM1 0x10000000 0x00008000 { ; RW data .ANY (+RW +ZI) } + RW_AHBRAM 0x20000000 0x00010000 { ; RW data + * (vbus_ring) + } } LR_IROM2 0x1B000000 0x00080000 { diff --git a/bsp/lpc43xx/M4/vbus_local_conf.h b/bsp/lpc43xx/M4/vbus_local_conf.h new file mode 100644 index 0000000000..571c51e612 --- /dev/null +++ b/bsp/lpc43xx/M4/vbus_local_conf.h @@ -0,0 +1,8 @@ +#ifndef __VBUS_LOCAL_CONF_H__ +#define __VBUS_LOCAL_CONF_H__ + +#define RT_VBUS_USING_FLOW_CONTROL + +#define RT_VBUS_USING_TESTS + +#endif /* end of include guard: __VBUS_LOCAL_CONF_H__ */ diff --git a/components/vbus/SConscript b/components/vbus/SConscript new file mode 100644 index 0000000000..e9097d19ff --- /dev/null +++ b/components/vbus/SConscript @@ -0,0 +1,29 @@ +# RT-Thread building script for component + +import SCons, os +from building import * + +group = [] +if not GetDepend(['RT_USING_VBUS']): + Return('group') + +cwd = GetCurrentDir() +src = Glob('*.c') + +for c, f in [['RT_USING_VBUS_RFS', 'utilities/rfs.c'], + ['RT_USING_VBUS_RSHELL', 'utilities/rshell.c'], + ]: + if GetDepend(c): + src += Glob(f) + +with open(os.path.join(Dir('#').get_abspath(), 'vbus_local_conf.h'), 'r') as f: + cpp = SCons.cpp.PreProcessor() + cpp.process_contents(f.read()) + if 'RT_VBUS_USING_TESTS' in cpp.cpp_namespace: + src += Glob('tests/*.c') + +CPPPATH = [cwd, os.path.join(cwd, 'share_hdr')] + +group = DefineGroup('VBus', src, depend = ['RT_USING_VBUS'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/vbus/prio_queue.c b/components/vbus/prio_queue.c new file mode 100644 index 0000000000..e0f57d1e05 --- /dev/null +++ b/components/vbus/prio_queue.c @@ -0,0 +1,275 @@ +/* + * Priority Queue + * + * COPYRIGHT (C) 2013, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2013-11-04 Grissiom add comment + */ + +#include +#include + +#include "prio_queue.h" + +struct rt_prio_queue_item { + struct rt_prio_queue_item *next; + /* data follows */ +}; + +static void _do_push(struct rt_prio_queue *que, + rt_uint8_t prio, + struct rt_prio_queue_item *item) +{ + if (que->head[prio] == RT_NULL) + { + que->head[prio] = item; + que->bitmap |= 1 << prio; + } + else + { + RT_ASSERT(que->tail[prio]); + que->tail[prio]->next = item; + } + que->tail[prio] = item; +} + +static struct rt_prio_queue_item* _do_pop(struct rt_prio_queue *que) +{ + int ffs; + struct rt_prio_queue_item *item; + + ffs = __rt_ffs(que->bitmap); + if (ffs == 0) + return RT_NULL; + ffs--; + + item = que->head[ffs]; + RT_ASSERT(item); + + que->head[ffs] = item->next; + if (que->head[ffs] == RT_NULL) + { + que->bitmap &= ~(1 << ffs); + } + + return item; +} + +rt_err_t rt_prio_queue_init(struct rt_prio_queue *que, + const char *name, + void *buf, + rt_size_t bufsz, + rt_size_t itemsz) +{ + RT_ASSERT(que); + + rt_memset(que, 0, sizeof(*que)); + + rt_list_init(&(que->suspended_pop_list)); + + rt_mp_init(&que->pool, name, buf, bufsz, + sizeof(struct rt_prio_queue_item) + itemsz); + + que->item_sz = itemsz; + + return RT_EOK; +} + +void rt_prio_queue_detach(struct rt_prio_queue *que) +{ + /* wake up all suspended pop threads, push thread is suspended on mempool. + */ + while (!rt_list_isempty(&(que->suspended_pop_list))) + { + rt_thread_t thread; + + /* disable interrupt */ + rt_ubase_t temp = rt_hw_interrupt_disable(); + + /* get next suspend thread */ + thread = rt_list_entry(que->suspended_pop_list.next, struct rt_thread, tlist); + /* set error code to RT_ERROR */ + thread->error = -RT_ERROR; + + rt_thread_resume(thread); + + /* enable interrupt */ + rt_hw_interrupt_enable(temp); + } + rt_mp_detach(&que->pool); +} + +#ifdef RT_USING_HEAP +struct rt_prio_queue* rt_prio_queue_create(const char *name, + rt_size_t item_nr, + rt_size_t item_sz) +{ + struct rt_prio_queue *que; + rt_size_t bufsz; + + bufsz = item_nr * (sizeof(struct rt_prio_queue_item) + + item_sz + + sizeof(void*)); + + RT_ASSERT(item_nr); + + que = rt_malloc(sizeof(*que) + bufsz); + if (!que) + return RT_NULL; + + rt_prio_queue_init(que, name, que+1, bufsz, item_sz); + + return que; +} + +void rt_prio_queue_delete(struct rt_prio_queue *que) +{ + rt_prio_queue_detach(que); + rt_free(que); +} +#endif + +rt_err_t rt_prio_queue_push(struct rt_prio_queue *que, + rt_uint8_t prio, + void *data, + rt_int32_t timeout) +{ + rt_ubase_t level; + struct rt_prio_queue_item *item; + + RT_ASSERT(que); + + if (prio >= RT_PRIO_QUEUE_PRIO_MAX) + return -RT_ERROR; + + item = rt_mp_alloc(&que->pool, timeout); + if (item == RT_NULL) + return -RT_ENOMEM; + + rt_memcpy(item+1, data, que->item_sz); + item->next = RT_NULL; + + level = rt_hw_interrupt_disable(); + + _do_push(que, prio, item); + + if (!rt_list_isempty(&(que->suspended_pop_list))) + { + rt_thread_t thread; + + /* get thread entry */ + thread = rt_list_entry(que->suspended_pop_list.next, + struct rt_thread, + tlist); + /* resume it */ + rt_thread_resume(thread); + rt_hw_interrupt_enable(level); + + /* perform a schedule */ + rt_schedule(); + + return RT_EOK; + } + + rt_hw_interrupt_enable(level); + + return RT_EOK; +} + +rt_err_t rt_prio_queue_pop(struct rt_prio_queue *que, + void *data, + rt_int32_t timeout) +{ + rt_ubase_t level; + struct rt_prio_queue_item *item; + + RT_ASSERT(que); + RT_ASSERT(data); + + level = rt_hw_interrupt_disable(); + for (item = _do_pop(que); + item == RT_NULL; + item = _do_pop(que)) + { + rt_thread_t thread; + + if (timeout == 0) + { + rt_hw_interrupt_enable(level); + return -RT_ETIMEOUT; + } + + RT_DEBUG_NOT_IN_INTERRUPT; + + thread = rt_thread_self(); + thread->error = RT_EOK; + rt_thread_suspend(thread); + + rt_list_insert_before(&(que->suspended_pop_list), &(thread->tlist)); + + if (timeout > 0) + { + rt_timer_control(&(thread->thread_timer), + RT_TIMER_CTRL_SET_TIME, + &timeout); + rt_timer_start(&(thread->thread_timer)); + } + + rt_hw_interrupt_enable(level); + + rt_schedule(); + + /* thread is waked up */ + if (thread->error != RT_EOK) + return thread->error; + level = rt_hw_interrupt_disable(); + } + + rt_hw_interrupt_enable(level); + + rt_memcpy(data, item+1, que->item_sz); + rt_mp_free(item); + + return RT_EOK; +} + +void rt_prio_queue_dump(struct rt_prio_queue *que) +{ + int level = 0; + + rt_kprintf("bitmap: %08x\n", que->bitmap); + for (level = 0; level < RT_PRIO_QUEUE_PRIO_MAX; level++) + { + struct rt_prio_queue_item *item; + + rt_kprintf("%2d: ", level); + for (item = que->head[level]; + item; + item = item->next) + { + rt_kprintf("%p, ", item); + } + rt_kprintf("\n"); + } +} + diff --git a/components/vbus/prio_queue.h b/components/vbus/prio_queue.h new file mode 100644 index 0000000000..09bc3c7e63 --- /dev/null +++ b/components/vbus/prio_queue.h @@ -0,0 +1,44 @@ +#ifndef __PRIO_QUEUE_H__ +#define __PRIO_QUEUE_H__ + +#include + +#define RT_PRIO_QUEUE_PRIO_MAX 32 + +struct rt_prio_queue_item; + +struct rt_prio_queue { + rt_uint32_t bitmap; + struct rt_prio_queue_item *head[RT_PRIO_QUEUE_PRIO_MAX]; + struct rt_prio_queue_item *tail[RT_PRIO_QUEUE_PRIO_MAX]; + /* push thread suspend on the mempool, not queue */ + rt_list_t suspended_pop_list; + rt_size_t item_sz; + + struct rt_mempool pool; +}; + +rt_err_t rt_prio_queue_init(struct rt_prio_queue *que, + const char *name, + void *buf, + rt_size_t bufsz, + rt_size_t itemsz); +void rt_prio_queue_detach(struct rt_prio_queue *que); + +rt_err_t rt_prio_queue_push(struct rt_prio_queue *que, + rt_uint8_t prio, + void *data, + rt_int32_t timeout); +rt_err_t rt_prio_queue_pop(struct rt_prio_queue *que, + void *data, + rt_int32_t timeout); +#ifdef RT_USING_HEAP +struct rt_prio_queue* rt_prio_queue_create(const char *name, + rt_size_t item_nr, + rt_size_t item_sz); +void rt_prio_queue_delete(struct rt_prio_queue *que); +#endif + +void rt_prio_queue_dump(struct rt_prio_queue *que); + +#endif /* end of include guard: __PRIO_QUEUE_H__ */ diff --git a/components/vbus/rt_watermark_queue.c b/components/vbus/rt_watermark_queue.c new file mode 100644 index 0000000000..9c551fa525 --- /dev/null +++ b/components/vbus/rt_watermark_queue.c @@ -0,0 +1,70 @@ +/* + * Water Gauge + * + * COPYRIGHT (C) 2014, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2014-04-16 Grissiom first version + */ + +#include +#include + +#include "rt_watermark_queue.h" + +void rt_wm_que_set_mark(struct rt_watermark_queue *wg, + unsigned int low, unsigned int high) +{ + RT_ASSERT(low <= high); + + wg->high_mark = high; + wg->low_mark = low; +} + +void rt_wm_que_init(struct rt_watermark_queue *wg, + unsigned int low, unsigned int high) +{ + rt_wm_que_set_mark(wg, low, high); + rt_list_init(&wg->suspended_threads); + wg->level = 0; +} + +void rt_wm_que_dump(struct rt_watermark_queue *wg) +{ + struct rt_list_node *node; + + rt_kprintf("wg %p: low: %d, high: %d, cur: %d\n", + wg, wg->low_mark, wg->high_mark, wg->level); + rt_kprintf("thread suspend:"); + for (node = wg->suspended_threads.next; + node != &wg->suspended_threads; + node = node->next) + { + rt_thread_t thread; + + thread = rt_list_entry(wg->suspended_threads.next, + struct rt_thread, + tlist); + rt_kprintf(" %.*s", RT_NAME_MAX, thread->name); + } + rt_kprintf("\n"); +} diff --git a/components/vbus/rt_watermark_queue.h b/components/vbus/rt_watermark_queue.h new file mode 100644 index 0000000000..c29c65bda0 --- /dev/null +++ b/components/vbus/rt_watermark_queue.h @@ -0,0 +1,148 @@ +/* + * Thread queue with water mark + * + * COPYRIGHT (C) 2014, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2014-04-16 Grissiom first version + */ + +struct rt_watermark_queue +{ + /* Current water level. */ + unsigned int level; + unsigned int high_mark; + unsigned int low_mark; + rt_list_t suspended_threads; +}; + +/** Init the struct rt_watermark_queue. + */ +void rt_wm_que_init(struct rt_watermark_queue *wg, + unsigned int low, unsigned int high); +void rt_wm_que_set_mark(struct rt_watermark_queue *wg, + unsigned int low, unsigned int high); +void rt_wm_que_dump(struct rt_watermark_queue *wg); + +/* Water marks are often used in performance critical places. Benchmark shows + * inlining functions will have 10% performance gain in some situation(for + * example, VBus). So keep the inc/dec compact and inline. */ + +/** Increase the water level. + * + * It should be called in the thread that want to raise the water level. If the + * current level is above the high mark, the thread will be suspended up to + * @timeout ticks. + * + * @return RT_EOK if water level increased successfully. -RT_EFULL on @timeout + * is zero and the level is above water mark. -RT_ETIMEOUT if timeout occurred. + */ +rt_inline rt_err_t rt_wm_que_inc(struct rt_watermark_queue *wg, + int timeout) +{ + rt_base_t ilvl; + + /* Assert as early as possible. */ + if (timeout != 0) + { + RT_DEBUG_IN_THREAD_CONTEXT; + } + + ilvl = rt_hw_interrupt_disable(); + + while (wg->level > wg->high_mark) + { + rt_thread_t thread; + + if (timeout == 0) + { + rt_hw_interrupt_enable(ilvl); + return -RT_EFULL; + } + + thread = rt_thread_self(); + thread->error = RT_EOK; + rt_thread_suspend(thread); + rt_list_insert_after(&wg->suspended_threads, &thread->tlist); + if (timeout > 0) + { + rt_timer_control(&(thread->thread_timer), + RT_TIMER_CTRL_SET_TIME, + &timeout); + rt_timer_start(&(thread->thread_timer)); + } + rt_hw_interrupt_enable(ilvl); + rt_schedule(); + if (thread->error != RT_EOK) + return thread->error; + + ilvl = rt_hw_interrupt_disable(); + } + + wg->level++; + + if (wg->level == 0) + { + wg->level = ~0; + } + + rt_hw_interrupt_enable(ilvl); + + return RT_EOK; +} + +/** Decrease the water level. + * + * It should be called by the consumer that drain the water out. If the water + * level reached low mark, all the thread suspended in this queue will be waken + * up. It's safe to call this function in interrupt context. + */ +rt_inline void rt_wm_que_dec(struct rt_watermark_queue *wg) +{ + int need_sched = 0; + rt_base_t ilvl; + + if (wg->level == 0) + return; + + ilvl = rt_hw_interrupt_disable(); + wg->level--; + if (wg->level == wg->low_mark) + { + /* There should be spaces between the low mark and high mark, so it's + * safe to resume all the threads. */ + while (!rt_list_isempty(&wg->suspended_threads)) + { + rt_thread_t thread; + + thread = rt_list_entry(wg->suspended_threads.next, + struct rt_thread, + tlist); + rt_thread_resume(thread); + need_sched = 1; + } + } + rt_hw_interrupt_enable(ilvl); + + if (need_sched) + rt_schedule(); +} diff --git a/components/vbus/share_hdr/vbus_api.h b/components/vbus/share_hdr/vbus_api.h new file mode 100644 index 0000000000..fb15fa24b0 --- /dev/null +++ b/components/vbus/share_hdr/vbus_api.h @@ -0,0 +1,79 @@ +#ifndef __VBUS_API_H__ +#define __VBUS_API_H__ + +#include "vbus_conf.h" + +#define RT_VBUS_CHANNEL_NR 32 + +#define RT_VBUS_BLK_HEAD_SZ 4 +#define RT_VBUS_MAX_PKT_SZ (256 - RT_VBUS_BLK_HEAD_SZ) + +#ifndef __ASSEMBLY__ +#include /* For size_t */ + +struct rt_vbus_blk +{ + unsigned char id; + unsigned char qos; + unsigned char len; + unsigned char reserved; + unsigned char data[60]; +} __attribute__((packed)); + +struct rt_vbus_ring +{ + volatile size_t put_idx; + volatile size_t get_idx; + /* whether the writer is blocked on this ring. For RTT, it means the + * central writer thread is waiting. For Linux, it means there are some + * threads waiting for space to write. + * + * Note that we don't record whether there are reading thread blocked. When + * there is new data, the other side will always be waked up. */ + volatile unsigned int blocked; + struct rt_vbus_blk blks[RT_VMM_RB_BLK_NR]; +}; + +enum +{ + RT_VBUS_CHN0_CMD_ENABLE, + RT_VBUS_CHN0_CMD_DISABLE, + RT_VBUS_CHN0_CMD_SET, + RT_VBUS_CHN0_CMD_ACK, + RT_VBUS_CHN0_CMD_NAK, + /* If the recieving side reached high water mark. It has the right to + * suspend the channel. All the server/client should know about this + * command but the one that does not implement flow control could ignore + * this command. */ + RT_VBUS_CHN0_CMD_SUSPEND, + RT_VBUS_CHN0_CMD_RESUME, + RT_VBUS_CHN0_CMD_MAX, +}; + +enum rt_vbus_chn_status +{ + /* initial state, available for reuse */ + RT_VBUS_CHN_ST_AVAILABLE, + /* ACK DISABLE send(CS) or received(CS), but not ready for reuse.(the + * channel is not closed by this end) */ + RT_VBUS_CHN_ST_CLOSED, + /* ENABLE send(client) or received(server) */ + RT_VBUS_CHN_ST_ESTABLISHING, + /* ACK SET send(C) or received(S) */ + RT_VBUS_CHN_ST_ESTABLISHED, + /* Channel suspended by flow control. */ + RT_VBUS_CHN_ST_SUSPEND, + /* DISABLE received(CS) */ + RT_VBUS_CHN_ST_CLOSING, +}; +#endif + +#undef BUILD_ASSERT +/* borrowed from http://lxr.linux.no/linux+v2.6.26.5/include/linux/kernel.h#L494 */ +#define BUILD_ASSERT(condition) ((void)sizeof(char[1 - 2*!(condition)])) + +/* max length of a channel name, including the \0 */ +#define RT_VBUS_CHN_NAME_MAX 16 + +#endif /* end of include guard: __VBUS_API_H__ */ + diff --git a/components/vbus/vbus.c b/components/vbus/vbus.c new file mode 100644 index 0000000000..4ce618ee86 --- /dev/null +++ b/components/vbus/vbus.c @@ -0,0 +1,1370 @@ +/* + * VMM Bus + * + * COPYRIGHT (C) 2013-2014, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2013-11-04 Grissiom add comment + */ + +#include +#include +#include + +#include "vbus.h" +#include "prio_queue.h" +#include "vbus_hw.h" + +//#define RT_VBUS_STATISTICS + +#define RT_VBUS_RB_LOW_TICK (RT_VMM_RB_BLK_NR * 2 / 3) +#define RT_VBUS_RB_TICK_STEP (100) + +#ifndef RT_USING_LOGTRACE +/* console could be run on vbus. If we log on it, there will be oops. */ +#define vbus_debug(...) +#define vbus_verbose(...) +#define vbus_info(...) +#define vbus_error(...) +#else // have RT_USING_LOGTRACE +#include + +#if defined(log_session_lvl) +/* Define log_trace_session as const so the compiler could optimize some log + * out. */ +const static struct log_trace_session _lgs = { + .id = {.name = "vbus"}, + .lvl = LOG_TRACE_LEVEL_VERBOSE, +}; + +#define vbus_debug(fmt, ...) log_session_lvl(&_lgs, LOG_TRACE_LEVEL_DEBUG, fmt, ##__VA_ARGS__) +#define vbus_verbose(fmt, ...) log_session_lvl(&_lgs, LOG_TRACE_LEVEL_VERBOSE, fmt, ##__VA_ARGS__) +#define vbus_info(fmt, ...) log_session_lvl(&_lgs, LOG_TRACE_LEVEL_INFO, fmt, ##__VA_ARGS__) +#define vbus_error(fmt, ...) log_session_lvl(&_lgs, LOG_TRACE_LEVEL_ERROR, fmt, ##__VA_ARGS__) +#else +static struct log_trace_session _lgs = { + .id = {.name = "vbus"}, + .lvl = LOG_TRACE_LEVEL_VERBOSE, +}; +#define vbus_debug(fmt, ...) log_session(&_lgs, LOG_TRACE_DEBUG""fmt, ##__VA_ARGS__) +#define vbus_verbose(fmt, ...) log_session(&_lgs, LOG_TRACE_VERBOSE""fmt, ##__VA_ARGS__) +#define vbus_info(fmt, ...) log_session(&_lgs, LOG_TRACE_INFO""fmt, ##__VA_ARGS__) +#define vbus_error(fmt, ...) log_session(&_lgs, LOG_TRACE_ERROR""fmt, ##__VA_ARGS__) +#endif +#endif // RT_USING_LOGTRACE + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof(ar[0])) +#endif + +struct rt_vbus_ring *RT_VBUS_OUT_RING; +struct rt_vbus_ring *RT_VBUS_IN_RING; + +const char *rt_vbus_chn_st2str[] = { + "available", + "closed", + "establishing", + "established", + "suspended", + "closing", +}; + +const char *rt_vbus_sess_st2str[] = { + "available", + "listening", + "establishing", +}; + +const char *rt_vbus_cmd2str[] = { + "ENABLE", + "DISABLE", + "SET", + "ACK", + "NAK", + "SUSPEND", + "RESUME", +}; + +static char* dump_cmd_pkt(unsigned char *dp, size_t dsize); + +/* 4 bytes for the head */ +#define LEN2BNR(len) ((len + RT_VBUS_BLK_HEAD_SZ \ + + sizeof(struct rt_vbus_blk) - 1) \ + / sizeof(struct rt_vbus_blk)) + +rt_inline void _ring_add_get_bnr(struct rt_vbus_ring *ring, + rt_size_t bnr) +{ + int nidx = ring->get_idx + bnr; + + if (nidx >= RT_VMM_RB_BLK_NR) + { + nidx -= RT_VMM_RB_BLK_NR; + } + rt_vbus_smp_wmb(); + ring->get_idx = nidx; +} + +rt_inline int _bus_ring_space_nr(struct rt_vbus_ring *rg) +{ + int delta; + + rt_vbus_smp_rmb(); + delta = rg->get_idx - rg->put_idx; + + if (delta > 0) + { + /* Put is behind the get. */ + return delta - 1; + } + else + { + /* delta is negative. */ + return RT_VMM_RB_BLK_NR + delta - 1; + } +} + +struct rt_vbus_pkg { + rt_uint8_t id; + rt_uint8_t prio; + rt_uint8_t finished; + rt_uint8_t len; + const void *data; +}; + +/* chn0 is always connected */ +static enum rt_vbus_chn_status _chn_status[RT_VBUS_CHANNEL_NR]; + +rt_inline int _chn_connected(unsigned char chnr) +{ + return _chn_status[chnr] == RT_VBUS_CHN_ST_ESTABLISHED || + _chn_status[chnr] == RT_VBUS_CHN_ST_SUSPEND; +} + +#ifdef RT_VBUS_USING_FLOW_CONTROL +#include +struct rt_watermark_queue _chn_wm_que[RT_VBUS_CHANNEL_NR]; +void rt_vbus_set_post_wm(unsigned char chnr, unsigned int low, unsigned int high) +{ + RT_ASSERT((0 < chnr) && (chnr < ARRAY_SIZE(_chn_wm_que))); + rt_wm_que_set_mark(&_chn_wm_que[chnr], low, high); +} + +/* Threads suspended by the flow control of other side. */ +rt_list_t _chn_suspended_threads[RT_VBUS_CHANNEL_NR]; + +struct +{ + unsigned int level; + unsigned int high_mark; + unsigned int low_mark; + /* The suspend command does not have ACK. So if the other side still + * sending pkg after SUSPEND, warn it again. Also use it as a flag that + * tell me whether are we dropping from the high mark or not when reaching + * the low mark. */ + unsigned int last_warn; +} _chn_recv_wm[RT_VBUS_CHANNEL_NR]; + +void rt_vbus_set_recv_wm(unsigned char chnr, unsigned int low, unsigned int high) +{ + RT_ASSERT((0 < chnr) && (chnr < ARRAY_SIZE(_chn_recv_wm))); + _chn_recv_wm[chnr].low_mark = low; + _chn_recv_wm[chnr].high_mark = high; +} +#else +void rt_vbus_set_recv_wm(unsigned char chnr, unsigned int low, unsigned int high) +{} +void rt_vbus_set_post_wm(unsigned char chnr, unsigned int low, unsigned int high) +{} +#endif + +struct { + rt_vbus_event_listener indicate; + void *ctx; +} _vbus_rx_indi[RT_VBUS_EVENT_ID_MAX][RT_VBUS_CHANNEL_NR]; + +void rt_vbus_register_listener(unsigned char chnr, + enum rt_vbus_event_id eve, + rt_vbus_event_listener indi, + void *ctx) +{ + RT_ASSERT(chnr != 0 && chnr < RT_VBUS_CHANNEL_NR); + RT_ASSERT(eve < sizeof(_vbus_rx_indi)/sizeof(_vbus_rx_indi[0])); + + _vbus_rx_indi[eve][chnr].indicate = indi; + _vbus_rx_indi[eve][chnr].ctx = ctx; +} + +static void _vbus_indicate(enum rt_vbus_event_id eve, unsigned char chnr) +{ + RT_ASSERT(eve < sizeof(_vbus_rx_indi)/sizeof(_vbus_rx_indi[0])); + + if (_vbus_rx_indi[eve][chnr].indicate) + _vbus_rx_indi[eve][chnr].indicate(_vbus_rx_indi[eve][chnr].ctx); +} + +#define _BUS_OUT_THRD_STACK_SZ 2048 +#define _BUS_OUT_THRD_PRIO 8 +#define _BUS_OUT_PKG_NR RT_VMM_RB_BLK_NR + +static struct rt_thread _bus_out_thread; +static rt_uint8_t _bus_out_thread_stack[_BUS_OUT_THRD_STACK_SZ]; +struct rt_prio_queue *_bus_out_que; + +static void _bus_out_entry(void *param) +{ + struct rt_vbus_pkg dpkg; + + _bus_out_que = rt_prio_queue_create("vbus", + _BUS_OUT_PKG_NR, + sizeof(struct rt_vbus_pkg)); + + if (!_bus_out_que) + { + rt_kprintf("could not create vmm bus queue\n"); + return; + } + + while (rt_prio_queue_pop(_bus_out_que, &dpkg, + RT_WAITING_FOREVER) == RT_EOK) + { + int sp; + rt_uint32_t nxtidx; + const int dnr = LEN2BNR(dpkg.len); + +#ifdef RT_VBUS_USING_FLOW_CONTROL + rt_wm_que_dec(&_chn_wm_que[dpkg.id]); +#endif + + if (!_chn_connected(dpkg.id)) + continue; + + sp = _bus_ring_space_nr(RT_VBUS_OUT_RING); + + vbus_debug("vmm bus out" + "(data: %p, len: %d, prio: %d, id: %d)\n", + dpkg.data, dpkg.len, dpkg.prio, dpkg.id); + + /* wait for enough space */ + while (sp < dnr) + { + rt_ubase_t lvl = rt_hw_interrupt_disable(); + + RT_VBUS_OUT_RING->blocked = 1; + rt_vbus_smp_wmb(); + + /* kick the guest, hoping this could force it do the work */ + rt_vbus_tick(0, RT_VBUS_GUEST_VIRQ); + + rt_thread_suspend(rt_thread_self()); + rt_schedule(); + + RT_VBUS_OUT_RING->blocked = 0; + + rt_hw_interrupt_enable(lvl); + + sp = _bus_ring_space_nr(RT_VBUS_OUT_RING); + } + + nxtidx = RT_VBUS_OUT_RING->put_idx + dnr; + + RT_VBUS_OUT_RING->blks[RT_VBUS_OUT_RING->put_idx].id = dpkg.id; + RT_VBUS_OUT_RING->blks[RT_VBUS_OUT_RING->put_idx].qos = dpkg.prio; + RT_VBUS_OUT_RING->blks[RT_VBUS_OUT_RING->put_idx].len = dpkg.len; + + if (nxtidx >= RT_VMM_RB_BLK_NR) + { + unsigned int tailsz; + + tailsz = (RT_VMM_RB_BLK_NR - RT_VBUS_OUT_RING->put_idx) + * sizeof(RT_VBUS_OUT_RING->blks[0]) - RT_VBUS_BLK_HEAD_SZ; + + /* the remaining block is sufficient for the data */ + if (tailsz > dpkg.len) + tailsz = dpkg.len; + + rt_memcpy(&RT_VBUS_OUT_RING->blks[RT_VBUS_OUT_RING->put_idx].data, + dpkg.data, tailsz); + rt_memcpy(&RT_VBUS_OUT_RING->blks[0], + ((char*)dpkg.data)+tailsz, + dpkg.len - tailsz); + + rt_vbus_smp_wmb(); + RT_VBUS_OUT_RING->put_idx = nxtidx - RT_VMM_RB_BLK_NR; + } + else + { + rt_memcpy(&RT_VBUS_OUT_RING->blks[RT_VBUS_OUT_RING->put_idx].data, + dpkg.data, dpkg.len); + + rt_vbus_smp_wmb(); + RT_VBUS_OUT_RING->put_idx = nxtidx; + } + + rt_vbus_smp_wmb(); + rt_vbus_tick(0, RT_VBUS_GUEST_VIRQ); + + if (dpkg.finished) + { + _vbus_indicate(RT_VBUS_EVENT_ID_TX, dpkg.id); + } + } + RT_ASSERT(0); +} + +void rt_vbus_resume_out_thread(void) +{ + rt_thread_resume(&_bus_out_thread); + rt_schedule(); +} + +rt_err_t rt_vbus_post(rt_uint8_t id, + rt_uint8_t prio, + const void *data, + rt_size_t size, + rt_int32_t timeout) +{ + rt_err_t err = RT_EOK; + struct rt_vbus_pkg pkg; + unsigned int putsz; + const unsigned char *dp; + + if (!_bus_out_que) + { + rt_kprintf("post (data: %p, size: %d, timeout: %d) " + "to bus before initialition\n", + data, size, timeout); + return -RT_ERROR; + } + + if (id >= RT_VBUS_CHANNEL_NR) + return -RT_ERROR; + + if (timeout != 0) + { + RT_DEBUG_IN_THREAD_CONTEXT; + } + +#ifdef RT_VBUS_USING_FLOW_CONTROL + while (_chn_status[id] == RT_VBUS_CHN_ST_SUSPEND) + { + rt_thread_t thread; + + if (timeout == 0) + { + return -RT_EFULL; + } + + thread = rt_thread_self(); + thread->error = RT_EOK; + /* We only touch the _chn_suspended_threads in thread, so lock the + * scheduler is enough. */ + rt_enter_critical(); + rt_thread_suspend(thread); + + rt_list_insert_after(&_chn_suspended_threads[id], &thread->tlist); + if (timeout > 0) + { + rt_timer_control(&(thread->thread_timer), + RT_TIMER_CTRL_SET_TIME, + &timeout); + rt_timer_start(&(thread->thread_timer)); + } + /* rt_exit_critical will do schedule on need. */ + rt_exit_critical(); + + if (thread->error != RT_EOK) + return thread->error; + } +#endif + + if (_chn_status[id] != RT_VBUS_CHN_ST_ESTABLISHED) + return -RT_ERROR; + + dp = data; + pkg.id = id; + pkg.prio = prio; + for (putsz = 0; size; size -= putsz) + { + pkg.data = dp; + + if (size > RT_VBUS_MAX_PKT_SZ) + { + putsz = RT_VBUS_MAX_PKT_SZ; + pkg.finished = 0; + } + else + { + putsz = size; + pkg.finished = 1; + } + + pkg.len = putsz; + dp += putsz; + +#ifdef RT_VBUS_USING_FLOW_CONTROL + err = rt_wm_que_inc(&_chn_wm_que[id], timeout); + if (err != RT_EOK) + break; +#endif + + vbus_debug("post (data: %p(%d), size: %d, finshed: %d, timeout: %d)\n", + pkg.data, ((unsigned char*)pkg.data)[0], + pkg.len, pkg.finished, timeout); + + err = rt_prio_queue_push(_bus_out_que, prio, &pkg, timeout); + if (err != RT_EOK) + break; + } + + return err; +} + +struct rt_completion _chn0_post_cmp; + +void _chn0_tx_listener(void *p) +{ + rt_completion_done(&_chn0_post_cmp); +} + +/* Posts in channel0 should be sync. */ +static rt_err_t _chn0_post(const void *data, + rt_size_t size, + int timeout) +{ + rt_err_t err; + + rt_completion_init(&_chn0_post_cmp); + err = rt_vbus_post(0, 0, data, size, timeout); + if (err != RT_EOK) + return err; + return rt_completion_wait(&_chn0_post_cmp, timeout); +} + +#define _BUS_IN_THRD_STACK_SZ 1024 +#define _BUS_IN_THRD_PRIO (_BUS_OUT_THRD_PRIO+1) +#if (_BUS_IN_THRD_PRIO == RT_THREAD_PRIORITY_MAX) +#error "_BUS_OUT_THRD_PRIO too low" +#endif + +static struct rt_thread _bus_in_thread; +static rt_uint8_t _bus_in_thread_stack[_BUS_OUT_THRD_STACK_SZ]; +static struct rt_semaphore _bus_in_sem; +static struct rt_event _bus_in_event; +/* {head, tail} */ +#define _IN_ACT_HEAD 0 +#define _IN_ACT_TAIL 1 +static struct rt_vbus_data *_bus_in_action[RT_VBUS_CHANNEL_NR][2]; +#ifdef RT_VBUS_STATISTICS +static unsigned int _bus_in_action_nr[RT_VBUS_CHANNEL_NR]; +#endif + +static void rt_vbus_notify_chn(unsigned char chnr, rt_err_t err) +{ +#ifdef RT_VBUS_USING_FLOW_CONTROL + /* TODO: get rid of this */ + /* Protect the list. */ + rt_enter_critical(); + while (!rt_list_isempty(&_chn_suspended_threads[chnr])) + { + rt_thread_t thread; + + thread = rt_list_entry(_chn_suspended_threads[chnr].next, + struct rt_thread, + tlist); + thread->error = err; + rt_thread_resume(thread); + } + rt_exit_critical(); +#endif + rt_event_send(&_bus_in_event, 1 << chnr); +} + +static void rt_vbus_notify_set(rt_uint32_t set) +{ + rt_event_send(&_bus_in_event, set); +} + +rt_err_t rt_vbus_listen_on(rt_uint8_t chnr, + rt_int32_t timeout) +{ + rt_uint32_t notuse; + + if (chnr == 0 || chnr >= RT_VBUS_CHANNEL_NR || !_chn_connected(chnr)) + return -RT_EIO; + + return rt_event_recv(&_bus_in_event, 1 << chnr, + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + timeout, ¬use); +} + +void rt_vbus_data_push(unsigned int id, struct rt_vbus_data *act) +{ + rt_ubase_t lvl; + + RT_ASSERT(0 < id && id < RT_VBUS_CHANNEL_NR); + + lvl = rt_hw_interrupt_disable(); + + if (_bus_in_action[id][_IN_ACT_HEAD] == RT_NULL) + { + _bus_in_action[id][_IN_ACT_HEAD] = act; + _bus_in_action[id][_IN_ACT_TAIL] = act; + } + else + { + _bus_in_action[id][_IN_ACT_TAIL]->next = act; + _bus_in_action[id][_IN_ACT_TAIL] = act; + } + +#ifdef RT_VBUS_STATISTICS + _bus_in_action_nr[id]++; +#endif + + rt_hw_interrupt_enable(lvl); + +#ifdef RT_VBUS_USING_FLOW_CONTROL + _chn_recv_wm[id].level++; + if (_chn_recv_wm[id].level == 0) + _chn_recv_wm[id].level = ~0; + if (_chn_recv_wm[id].level > _chn_recv_wm[id].high_mark && + _chn_recv_wm[id].level > _chn_recv_wm[id].last_warn) + { + unsigned char buf[2]; + + buf[0] = RT_VBUS_CHN0_CMD_SUSPEND; + buf[1] = id; + vbus_debug("%s --> remote\n", dump_cmd_pkt(buf, sizeof(buf))); + _chn0_post(buf, sizeof(buf), RT_WAITING_FOREVER); + /* Warn the other side in 100 more pkgs. */ + _chn_recv_wm[id].last_warn = _chn_recv_wm[id].level + 100; + } +#endif +} + +struct rt_vbus_data* rt_vbus_data_pop(unsigned int id) +{ + struct rt_vbus_data *act; + rt_ubase_t lvl; + + RT_ASSERT(0 < id && id < RT_VBUS_CHANNEL_NR); + + lvl = rt_hw_interrupt_disable(); + + act = _bus_in_action[id][_IN_ACT_HEAD]; + if (act) + { + _bus_in_action[id][_IN_ACT_HEAD] = act->next; + } + + rt_hw_interrupt_enable(lvl); + +#ifdef RT_VBUS_USING_FLOW_CONTROL + if (_chn_recv_wm[id].level != 0) + { + _chn_recv_wm[id].level--; + if (_chn_recv_wm[id].level <= _chn_recv_wm[id].low_mark && + _chn_recv_wm[id].last_warn > _chn_recv_wm[id].low_mark) + { + unsigned char buf[2]; + + buf[0] = RT_VBUS_CHN0_CMD_RESUME; + buf[1] = id; + vbus_debug("%s --> remote\n", dump_cmd_pkt(buf, sizeof(buf))); + _chn0_post(buf, sizeof(buf), RT_WAITING_FOREVER); + _chn_recv_wm[id].last_warn = 0; + } + } +#endif + return act; +} + +/* dump cmd that is not start with ACK/NAK */ +static size_t __dump_naked_cmd(char *dst, size_t lsize, + unsigned char *dp, size_t dsize) +{ + size_t len; + if (dp[0] == RT_VBUS_CHN0_CMD_DISABLE || + dp[0] == RT_VBUS_CHN0_CMD_SUSPEND || + dp[0] == RT_VBUS_CHN0_CMD_RESUME) + { + len = rt_snprintf(dst, lsize, "%s %d", + rt_vbus_cmd2str[dp[0]], dp[1]); + } + else if (dp[0] == RT_VBUS_CHN0_CMD_ENABLE) + { + len = rt_snprintf(dst, lsize, "%s %s", + rt_vbus_cmd2str[dp[0]], dp+1); + } + else if (dp[0] < RT_VBUS_CHN0_CMD_MAX) + { + len = rt_snprintf(dst, lsize, "%s %s %d", + rt_vbus_cmd2str[dp[0]], + dp+1, dp[2+rt_strlen((char*)dp+1)]); + } + else + { + len = rt_snprintf(dst, lsize, "(invalid)%d %d", + dp[0], dp[1]); + } + return len; +} + +static char _cmd_dump_buf[64]; +static char* dump_cmd_pkt(unsigned char *dp, size_t dsize) +{ + size_t len; + + if (dp[0] == RT_VBUS_CHN0_CMD_ACK || dp[0] == RT_VBUS_CHN0_CMD_NAK ) + { + len = rt_snprintf(_cmd_dump_buf, sizeof(_cmd_dump_buf), + "%s ", rt_vbus_cmd2str[dp[0]]); + len += __dump_naked_cmd(_cmd_dump_buf+len, sizeof(_cmd_dump_buf)-len, + dp+1, dsize-1); + } + else + { + len = __dump_naked_cmd(_cmd_dump_buf, sizeof(_cmd_dump_buf), + dp, dsize); + } + + if (len > sizeof(_cmd_dump_buf) - 1) + len = sizeof(_cmd_dump_buf) - 1; + + _cmd_dump_buf[len] = '\0'; + return _cmd_dump_buf; +} + +static rt_err_t _chn0_echo_with(rt_uint8_t prefix, + rt_uint32_t dsize, + unsigned char *dp) +{ + rt_err_t err; + unsigned char *resp; + + resp = rt_malloc(dsize+1); + if (!resp) + return -RT_ENOMEM; + *resp = prefix; + rt_memcpy(resp+1, dp, dsize); + vbus_verbose("%s --> remote\n", dump_cmd_pkt(resp, dsize+1)); + + err = _chn0_post(resp, dsize+1, RT_WAITING_FOREVER); + + rt_free(resp); + + return err; +} + +static rt_err_t _chn0_nak(rt_uint32_t dsize, unsigned char *dp) +{ + return _chn0_echo_with(RT_VBUS_CHN0_CMD_NAK, dsize, dp); +} + +static rt_err_t _chn0_ack(rt_uint32_t dsize, unsigned char *dp) +{ + return _chn0_echo_with(RT_VBUS_CHN0_CMD_ACK, dsize, dp); +} + +enum _vbus_session_st +{ + SESSIOM_AVAILABLE, + SESSIOM_LISTENING, + SESSIOM_ESTABLISHING, +}; + +struct rt_vbus_conn_session +{ + /* negative value means error */ + int chnr; + enum _vbus_session_st st; + struct rt_completion cmp; + struct rt_vbus_request *req; +}; + +static struct rt_vbus_conn_session _sess[RT_VBUS_CHANNEL_NR/2]; + +static int _sess_find(const unsigned char *name, + enum _vbus_session_st st) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(_sess); i++) + { + if (_sess[i].st == st && _sess[i].req->name && + rt_strcmp(_sess[i].req->name, (char*)name) == 0) + break; + } + return i; +} + +static int _chn0_actor(unsigned char *dp, size_t dsize) +{ + if (*dp != RT_VBUS_CHN0_CMD_SUSPEND && *dp != RT_VBUS_CHN0_CMD_RESUME) + vbus_verbose("local <-- %s\n", dump_cmd_pkt(dp, dsize)); + + switch (*dp) + { + case RT_VBUS_CHN0_CMD_ENABLE: + { + int i, chnr; + rt_err_t err; + unsigned char *resp; + + i = _sess_find(dp+1, SESSIOM_LISTENING); + if (i == ARRAY_SIZE(_sess)) + { + _chn0_nak(dsize, dp); + break; + } + + for (chnr = 0; chnr < ARRAY_SIZE(_chn_status); chnr++) + { + if (_chn_status[chnr] == RT_VBUS_CHN_ST_AVAILABLE) + break; + } + if (chnr == ARRAY_SIZE(_chn_status)) + { + _chn0_nak(dsize, dp); + break; + } + + resp = rt_malloc(dsize + 1); + if (!resp) + break; + + *resp = RT_VBUS_CHN0_CMD_SET; + rt_memcpy(resp+1, dp+1, dsize-1); + resp[dsize] = chnr; + + rt_vbus_set_recv_wm(chnr, _sess[i].req->recv_wm.low, _sess[i].req->recv_wm.high); + rt_vbus_set_post_wm(chnr, _sess[i].req->post_wm.low, _sess[i].req->post_wm.high); + + vbus_verbose("%s --> remote\n", dump_cmd_pkt(resp, dsize+1)); + err = _chn0_post(resp, dsize+1, RT_WAITING_FOREVER); + + if (err == RT_EOK) + { + _sess[i].st = SESSIOM_ESTABLISHING; + vbus_debug("set sess %d st: %s\n", i, + rt_vbus_sess_st2str[_sess[i].st]); + _sess[i].chnr = chnr; + _chn_status[chnr] = RT_VBUS_CHN_ST_ESTABLISHING; + } + rt_free(resp); + } + break; + case RT_VBUS_CHN0_CMD_SET: + { + int i, chnr; + + i = _sess_find(dp+1, SESSIOM_ESTABLISHING); + if (i == ARRAY_SIZE(_sess)) + { + vbus_verbose("drop spurious packet\n"); + break; + } + + chnr = dp[1+rt_strlen((const char*)dp+1)+1]; + + if (chnr == 0 || chnr >= RT_VBUS_CHANNEL_NR) + { + vbus_verbose("SET wrong chnr %d\n", chnr); + break; + } + if (_chn_status[chnr] != RT_VBUS_CHN_ST_AVAILABLE) + { + _chn0_nak(dsize, dp); + vbus_verbose("SET wrong chnr status %d, %s\n", + chnr, rt_vbus_chn_st2str[_chn_status[chnr]]); + break; + } + + rt_vbus_set_recv_wm(chnr, _sess[i].req->recv_wm.low, _sess[i].req->recv_wm.high); + rt_vbus_set_post_wm(chnr, _sess[i].req->post_wm.low, _sess[i].req->post_wm.high); + + if (_chn0_ack(dsize, dp) >= 0) + { + _sess[i].chnr = chnr; + _chn_status[chnr] = RT_VBUS_CHN_ST_ESTABLISHED; + vbus_debug("chn %d %s\n", chnr, + rt_vbus_chn_st2str[_chn_status[chnr]]); + rt_completion_done(&_sess[i].cmp); + } + } + break; + case RT_VBUS_CHN0_CMD_ACK: + if (dp[1] == RT_VBUS_CHN0_CMD_SET) + { + int i, chnr; + + i = _sess_find(dp+2, SESSIOM_ESTABLISHING); + if (i == ARRAY_SIZE(_sess)) + /* drop that spurious packet */ + break; + + chnr = dp[1+rt_strlen((const char*)dp+2)+2]; + + _sess[i].chnr = chnr; + _chn_status[chnr] = RT_VBUS_CHN_ST_ESTABLISHED; + vbus_debug("chn %d %s\n", chnr, + rt_vbus_chn_st2str[_chn_status[chnr]]); + rt_completion_done(&_sess[i].cmp); + } + else if (dp[1] == RT_VBUS_CHN0_CMD_DISABLE) + { + unsigned char chnr = dp[2]; + + if (chnr == 0 || chnr >= RT_VBUS_CHANNEL_NR) + break; + + /* We could only get here by sending DISABLE command, which is + * initiated by the rt_vbus_close_chn. */ + _chn_status[chnr] = RT_VBUS_CHN_ST_AVAILABLE; + + _vbus_indicate(RT_VBUS_EVENT_ID_DISCONN, chnr); + /* notify the thread that the channel has been closed */ + rt_vbus_notify_chn(chnr, -RT_ERROR); + } + else + { + vbus_info("invalid ACK for %d\n", dp[1]); + } + break; + case RT_VBUS_CHN0_CMD_DISABLE: + { + unsigned char chnr = dp[1]; + + if (chnr == 0 || chnr >= RT_VBUS_CHANNEL_NR) + break; + + _chn_status[chnr] = RT_VBUS_CHN_ST_CLOSING; + + _chn0_ack(dsize, dp); + + _vbus_indicate(RT_VBUS_EVENT_ID_DISCONN, chnr); + /* notify the thread that the channel has been closed */ + rt_vbus_notify_chn(chnr, -RT_ERROR); + } + break; + case RT_VBUS_CHN0_CMD_SUSPEND: +#ifdef RT_VBUS_USING_FLOW_CONTROL + { + unsigned char chnr = dp[1]; + + if (chnr == 0 || chnr >= RT_VBUS_CHANNEL_NR) + break; + + if (_chn_status[chnr] != RT_VBUS_CHN_ST_ESTABLISHED) + break; + + _chn_status[chnr] = RT_VBUS_CHN_ST_SUSPEND; + } +#endif + break; + case RT_VBUS_CHN0_CMD_RESUME: +#ifdef RT_VBUS_USING_FLOW_CONTROL + { + unsigned char chnr = dp[1]; + + if (chnr == 0 || chnr >= RT_VBUS_CHANNEL_NR) + break; + + if (_chn_status[chnr] != RT_VBUS_CHN_ST_SUSPEND) + break; + + _chn_status[chnr] = RT_VBUS_CHN_ST_ESTABLISHED; + + /* Protect the list. */ + rt_enter_critical(); + while (!rt_list_isempty(&_chn_suspended_threads[chnr])) + { + rt_thread_t thread; + + thread = rt_list_entry(_chn_suspended_threads[chnr].next, + struct rt_thread, + tlist); + rt_thread_resume(thread); + } + rt_exit_critical(); + } +#endif + break; + case RT_VBUS_CHN0_CMD_NAK: + if (dp[1] == RT_VBUS_CHN0_CMD_ENABLE) + { + int i; + + i = _sess_find(dp+2, SESSIOM_ESTABLISHING); + if (i == ARRAY_SIZE(_sess)) + /* drop that spurious packet */ + break; + + _sess[i].chnr = -RT_EIO; + rt_completion_done(&_sess[i].cmp); + } + else if (dp[1] == RT_VBUS_CHN0_CMD_SET) + { + vbus_info("NAK for %d not implemented\n", dp[1]); + } + else + { + vbus_info("invalid NAK for %d\n", dp[1]); + } + break; + default: + /* just ignore the invalid cmd */ + vbus_info("drop unknown cmd %d on chn0\n", *dp); + break; + }; + + return RT_EOK; +} + +int rt_vbus_request_chn(struct rt_vbus_request *req, + int timeout) +{ + int i, chnr, err; + size_t plen = rt_strlen(req->name) + 2; + unsigned char *pbuf; + rt_ubase_t lvl; + + lvl = rt_hw_interrupt_disable(); + for (i = 0; i < ARRAY_SIZE(_sess); i++) + { + if (_sess[i].st == SESSIOM_AVAILABLE) + break; + } + if (i == ARRAY_SIZE(_sess)) + { + rt_hw_interrupt_enable(lvl); + return -RT_ERROR; + } + + rt_completion_init(&_sess[i].cmp); + _sess[i].req = req; + + if (req->is_server) + { + _sess[i].st = SESSIOM_LISTENING; + rt_hw_interrupt_enable(lvl); + + vbus_debug("request listening %s on %d\n", req->name, i); + + /* always wait on the condition */ + err = RT_EOK; + goto _waitforcmp; + } + + pbuf = rt_malloc(plen); + if (!pbuf) + { + rt_hw_interrupt_enable(lvl); + return -RT_ENOMEM; + } + + _sess[i].st = SESSIOM_ESTABLISHING; + rt_hw_interrupt_enable(lvl); + + pbuf[0] = RT_VBUS_CHN0_CMD_ENABLE; + rt_memcpy(pbuf+1, req->name, plen-1); + vbus_verbose("%s --> remote\n", dump_cmd_pkt(pbuf, plen)); + + err = _chn0_post(pbuf, plen, RT_WAITING_FOREVER); + rt_free(pbuf); + +_waitforcmp: + if (err == RT_EOK) + err = rt_completion_wait(&_sess[i].cmp, timeout); + + vbus_debug("request wait cmp done %d, chnr %d\n", err, _sess[i].chnr); + + if (err) + { + /* cleanup the mass when the wait is time out but we have done some job + */ + if (_sess[i].st == SESSIOM_ESTABLISHING) + _chn_status[_sess[i].chnr] = RT_VBUS_CHN_ST_AVAILABLE; + chnr = err; + goto Out; + } + + RT_ASSERT(_sess[i].chnr != 0); + + chnr = _sess[i].chnr; + +Out: + /* detach the sess as we finished the job */ + _sess[i].st = SESSIOM_AVAILABLE; + _sess[i].req = RT_NULL; + + return chnr; +} + +void rt_vbus_close_chn(unsigned char chnr) +{ + void *p; + rt_err_t err; + unsigned char buf[2]; + + buf[0] = RT_VBUS_CHN0_CMD_DISABLE; + buf[1] = chnr; + + RT_ASSERT(0 < chnr && chnr < RT_VBUS_CHANNEL_NR); + + if (_chn_status[chnr] == RT_VBUS_CHN_ST_CLOSED || + _chn_status[chnr] == RT_VBUS_CHN_ST_CLOSING) + { + _chn_status[chnr] = RT_VBUS_CHN_ST_AVAILABLE; + return; + } + + if (!_chn_connected(chnr)) + return; + + _chn_status[chnr] = RT_VBUS_CHN_ST_CLOSING; + vbus_info("%s --> remote\n", dump_cmd_pkt(buf, sizeof(buf))); + err = _chn0_post(&buf, sizeof(buf), RT_WAITING_FOREVER); + if (err == RT_EOK) + /* wait for the ack */ + rt_vbus_listen_on(chnr, 10 * RT_TICK_PER_SECOND); + + /* cleanup the remaining data */ + for (p = rt_vbus_data_pop(chnr); p; p = rt_vbus_data_pop(chnr)) + rt_free(p); + /* FIXME: there is a chance that there are some data left on the send + * buffer. So if we connect other channel with the same number immediately, + * the new channel will receive some garbage data. However, this is highly + * un-probable. */ +} + +#ifdef RT_VBUS_STATISTICS +static unsigned int _total_data_sz; +#endif + +static void _bus_in_entry(void *param) +{ + rt_sem_init(&_bus_in_sem, "vbus", 0, RT_IPC_FLAG_FIFO); + rt_event_init(&_bus_in_event, "vbus", RT_IPC_FLAG_FIFO); + rt_memset(_bus_in_action, 0, sizeof(_bus_in_action)); + + while (rt_sem_take(&_bus_in_sem, + RT_WAITING_FOREVER) == RT_EOK) + { + rt_uint32_t event_set = 0; + + /* while(not empty) */ + while (RT_VBUS_IN_RING->get_idx != RT_VBUS_IN_RING->put_idx) + { + unsigned int id, nxtidx; + rt_size_t size; + struct rt_vbus_data *act; + + rt_vbus_smp_rmb(); + size = RT_VBUS_IN_RING->blks[RT_VBUS_IN_RING->get_idx].len; + id = RT_VBUS_IN_RING->blks[RT_VBUS_IN_RING->get_idx].id; + + vbus_debug("vmm bus in: chnr %d, size %d\n", id, size); + + /* Suspended channel can still recv data. */ + if (id > RT_VBUS_CHANNEL_NR || !_chn_connected(id)) + { + vbus_error("drop on invalid chn %d\n", id); + /* drop the invalid packet */ + _ring_add_get_bnr(RT_VBUS_IN_RING, LEN2BNR(size)); + continue; + } + + if (id == 0) + { + if (size > 60) + vbus_error("too big(%d) packet on chn0\n", size); + else + _chn0_actor(RT_VBUS_IN_RING->blks[RT_VBUS_IN_RING->get_idx].data, size); + _ring_add_get_bnr(RT_VBUS_IN_RING, LEN2BNR(size)); + continue; + } + +#ifdef RT_VBUS_STATISTICS + _total_data_sz += size; +#endif + + act = rt_malloc(sizeof(*act) + size); + if (act == RT_NULL) + { + //vbus_error("drop on OOM (%d, %d)\n", id, size); + /* drop the packet on malloc fall */ + _ring_add_get_bnr(RT_VBUS_IN_RING, LEN2BNR(size)); + continue; + } + + act->size = size; + act->next = RT_NULL; + + nxtidx = RT_VBUS_IN_RING->get_idx + LEN2BNR(size); + if (nxtidx >= RT_VMM_RB_BLK_NR) + { + unsigned int tailsz; + + tailsz = (RT_VMM_RB_BLK_NR - RT_VBUS_IN_RING->get_idx) + * sizeof(RT_VBUS_IN_RING->blks[0]) - RT_VBUS_BLK_HEAD_SZ; + + /* the remaining block is sufficient for the data */ + if (tailsz > size) + tailsz = size; + + rt_memcpy(act+1, &RT_VBUS_IN_RING->blks[RT_VBUS_IN_RING->get_idx].data, tailsz); + rt_memcpy((char*)(act+1) + tailsz, &RT_VBUS_IN_RING->blks[0], size - tailsz); + + /* It shall make sure the CPU has finished reading the item + * before it writes the new tail pointer, which will erase the + * item. */ + rt_vbus_smp_wmb(); + RT_VBUS_IN_RING->get_idx = nxtidx - RT_VMM_RB_BLK_NR; + } + else + { + rt_memcpy(act+1, &RT_VBUS_IN_RING->blks[RT_VBUS_IN_RING->get_idx].data, size); + + rt_vbus_smp_wmb(); + RT_VBUS_IN_RING->get_idx = nxtidx; + } + + rt_vbus_data_push(id, act); + _vbus_indicate(RT_VBUS_EVENT_ID_RX, id); + event_set |= 1 << id; + + if (RT_VBUS_IN_RING->blocked) + rt_vbus_tick(0, RT_VBUS_GUEST_VIRQ); + } + + if (event_set != 0) + rt_vbus_notify_set(event_set); + } + RT_ASSERT(0); +} + +void rt_vbus_isr(int irqnr, void *param) +{ + if (RT_VBUS_OUT_RING->blocked) + rt_vbus_resume_out_thread(); + + rt_sem_release(&_bus_in_sem); + rt_vbus_hw_eoi(irqnr, param); +} + +int rt_vbus_init(void *outr, void *inr) +{ + int i; + +#ifdef RT_USING_LOGTRACE + log_trace_register_session(&_lgs); +#endif + + if (outr > inr) + { + RT_ASSERT((char*)outr - (char*)inr >= sizeof(struct rt_vbus_ring)); + } + else + { + RT_ASSERT((char*)inr - (char*)outr >= sizeof(struct rt_vbus_ring)); + } + + RT_VBUS_OUT_RING = outr; + RT_VBUS_IN_RING = inr; + + rt_memset(RT_VBUS_OUT_RING, 0, sizeof(*RT_VBUS_OUT_RING)); + rt_memset(RT_VBUS_IN_RING, 0, sizeof(*RT_VBUS_IN_RING)); + _chn_status[0] = RT_VBUS_CHN_ST_ESTABLISHED; + for (i = 1; i < ARRAY_SIZE(_chn_status); i++) + { + _chn_status[i] = RT_VBUS_CHN_ST_AVAILABLE; + } + for (i = 0; i < ARRAY_SIZE(_sess); i++) + { + _sess[i].req = RT_NULL; + _sess[i].st = SESSIOM_AVAILABLE; + } + _vbus_rx_indi[RT_VBUS_EVENT_ID_TX][0].indicate = _chn0_tx_listener; + _vbus_rx_indi[RT_VBUS_EVENT_ID_TX][0].ctx = RT_NULL; + +#ifdef RT_VBUS_USING_FLOW_CONTROL + for (i = 0; i < ARRAY_SIZE(_chn_wm_que); i++) + { + rt_wm_que_init(&_chn_wm_que[i], + RT_VMM_RB_BLK_NR / 3, + RT_VMM_RB_BLK_NR * 2 / 3); + } + /* Channel 0 has the full channel. */ + rt_wm_que_set_mark(&_chn_wm_que[0], 0, ~0); + + for (i = 0; i < ARRAY_SIZE(_chn_suspended_threads); i++) + { + rt_list_init(&_chn_suspended_threads[i]); + } + + for (i = 1; i < ARRAY_SIZE(_chn_recv_wm); i++) + { + rt_vbus_set_recv_wm(i, + RT_VMM_RB_BLK_NR / 3, + RT_VMM_RB_BLK_NR * 2 / 3); + _chn_recv_wm[i].level = 0; + _chn_recv_wm[i].last_warn = 0; + } + /* Channel 0 has the full channel. Don't suspend it. */ + _chn_recv_wm[0].low_mark = 0; + _chn_recv_wm[0].high_mark = ~0; + _chn_recv_wm[0].level = 0; + _chn_recv_wm[0].last_warn = 0; +#endif + + rt_thread_init(&_bus_out_thread, "vbusout", + _bus_out_entry, RT_NULL, + _bus_out_thread_stack, sizeof(_bus_out_thread_stack), + _BUS_OUT_THRD_PRIO, 20); + rt_thread_startup(&_bus_out_thread); + + rt_thread_init(&_bus_in_thread, "vbusin", + _bus_in_entry, RT_NULL, + _bus_in_thread_stack, sizeof(_bus_in_thread_stack), + _BUS_IN_THRD_PRIO, 20); + + + rt_thread_startup(&_bus_in_thread); + + rt_vbus_hw_init(); + + rt_kprintf("VBus loaded: %d out blocks, %d in blocks\n", + RT_VMM_RB_BLK_NR, RT_VMM_RB_BLK_NR); + + rt_vbus_chnx_init(); + + return 0; +} + +void rt_vbus_rb_dump(void) +{ + rt_kprintf("OUT ring:(%s blocked)\n", RT_VBUS_OUT_RING->blocked ? "is" : "not"); + rt_kprintf("put idx: %8x, get idx: %8x\n", + RT_VBUS_OUT_RING->put_idx, RT_VBUS_OUT_RING->get_idx); + rt_kprintf("space: %d\n", _bus_ring_space_nr(RT_VBUS_OUT_RING)); + + + rt_kprintf("IN ring:(%s blocked)\n", RT_VBUS_IN_RING->blocked ? "is" : "not"); + rt_kprintf("put idx: %8x, get idx: %8x\n", + RT_VBUS_IN_RING->put_idx, RT_VBUS_IN_RING->get_idx); + rt_kprintf("space: %d\n", _bus_ring_space_nr(RT_VBUS_IN_RING)); +} + +void rt_vbus_chn_dump(void) +{ + int i; + rt_kprintf("vbus channel status:\n"); + for (i = 0; i < ARRAY_SIZE(_chn_status); i++) + { + rt_kprintf("%2d:%s\n", i, rt_vbus_chn_st2str[_chn_status[i]]); + } +} + +void rt_vbus_sess_dump(void) +{ + int i; + + rt_kprintf("vbus conn session:\n"); + for (i = 0; i < ARRAY_SIZE(_sess); i++) + { + rt_kprintf("%2d(%s):%s\n", i, _sess[i].req ? _sess[i].req->name : "", + rt_vbus_sess_st2str[_sess[i].st]); + } +} + +void rt_vbus_que_dump(void) +{ + rt_kprintf("out que:\n"); + rt_prio_queue_dump(_bus_out_que); +} + +unsigned int rt_vbus_total_data_sz(void) +{ +#ifdef RT_VBUS_STATISTICS + return _total_data_sz; +#else + return (unsigned int)-1; +#endif +} + +void rt_vbus_data_pkt_dump(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(_bus_in_action); i++) + { + struct rt_vbus_data *dp; + +#ifdef RT_VBUS_STATISTICS + rt_kprintf("%2d %4d: ", i, _bus_in_action_nr[i]); +#else + rt_kprintf("%2d: ", i); +#endif + for (dp = _bus_in_action[i][_IN_ACT_HEAD]; + dp; + dp = dp->next) + { + rt_kprintf("%p(%d) -> ", dp, dp->size); + } + rt_kprintf(" nil\n"); + } +} + +#ifdef RT_VBUS_USING_FLOW_CONTROL +void rt_vbus_chm_wm_dump(void) +{ + int i; + + rt_kprintf("post wm:\n"); + for (i = 0; i < ARRAY_SIZE(_chn_wm_que); i++) + rt_wm_que_dump(&_chn_wm_que[i]); + + rt_kprintf("recv wm:\n"); + rt_kprintf(" low, high, cur, last warn\n"); + for (i = 0; i < ARRAY_SIZE(_chn_recv_wm); i++) + { + rt_kprintf("%8x, %8x, %8x, %8x\n", + _chn_recv_wm[i].low_mark, _chn_recv_wm[i].high_mark, + _chn_recv_wm[i].level, _chn_recv_wm[i].last_warn); + } +} +#endif + +#ifdef RT_USING_FINSH +#include +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_rb_dump, vbrb, dump vbus ringbuffer status); +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_chn_dump, vbchn, dump vbus channel status); +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_sess_dump, vbses, dump vbus session status); +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_que_dump, vbque, dump vbus out queue status); +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_total_data_sz, vbtsz, total in data); +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_data_pkt_dump, vbdq, dump the data queue); +#ifdef RT_VBUS_USING_FLOW_CONTROL +FINSH_FUNCTION_EXPORT_ALIAS(rt_vbus_chm_wm_dump, vbwm, dump vbus water mark status); +#endif +#endif + diff --git a/components/vbus/vbus.h b/components/vbus/vbus.h new file mode 100644 index 0000000000..83f0bb295e --- /dev/null +++ b/components/vbus/vbus.h @@ -0,0 +1,195 @@ +#ifndef __VBUS_H__ +#define __VBUS_H__ +/* + * VBus + * + * COPYRIGHT (C) 2013-2014, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2014-06-09 Grissiom version 2.0.2; add comment + */ + +#include "vbus_local_conf.h" +#include + +int rt_vbus_init(void *outr, void *inr); + +void rt_vbus_resume_out_thread(void); + +/** Post data on channel. + * + * @param chnr the channel number + * @param prio the priority of the data + * @param datap pointer to the actual data + * @param size number of byte of the data + * @param timeout the value used in the blocking API + * + * Note: rt_vbus_post is an asynchronous function that when it returns, the + * @datap and @size is recorded in the post queue at least but there is no + * guarantee that the data is copied into the ring buffer. To avoid data + * corruption, you need to wait on the RT_VBUS_EVENT_ID_TX event. + * + * However, if you just post static data such as static string, there is no + * need to wait. + * + * @sa rt_vbus_register_listener . + */ +rt_err_t rt_vbus_post(rt_uint8_t chnr, + rt_uint8_t prio, + const void *datap, + rt_size_t size, + rt_int32_t timeout); + +struct rt_vbus_data { + /* Number of bytes in current data package. */ + unsigned char size; + /* Used internally in VBus. Don't modify this field as it may corrupt the + * receive queue. */ + struct rt_vbus_data *next; + /* Data follows the struct */ +}; + +struct rt_vbus_wm_cfg { + unsigned int low, high; +}; + +struct rt_vbus_request { + unsigned char prio; + const char *name; + int is_server; + struct rt_vbus_wm_cfg recv_wm, post_wm; +}; + +/** Request a channel. + * + * @return channel number. Negative if error happened. + */ +int rt_vbus_request_chn(struct rt_vbus_request *req, int timeout); + +/** Close channel @chnr */ +void rt_vbus_close_chn(unsigned char chnr); + +/** Set the water mark level for posting into the channel @chnr. */ +void rt_vbus_set_post_wm(unsigned char chnr, unsigned int low, unsigned int high); +/** Set the water mark level for receiving from the channel @chnr. */ +void rt_vbus_set_recv_wm(unsigned char chnr, unsigned int low, unsigned int high); + +typedef void (*rt_vbus_event_listener)(void *ctx); + +enum rt_vbus_event_id { + /* On a packet received in channel. */ + RT_VBUS_EVENT_ID_RX, + /* On the data of rt_vbus_post has been written to the ring buffer. */ + RT_VBUS_EVENT_ID_TX, + /* On the channel has been closed. */ + RT_VBUS_EVENT_ID_DISCONN, + RT_VBUS_EVENT_ID_MAX, +}; + +/** Register callback @indi on the event @eve on the @chnr. + * + * @ctx will passed to @indi on calling the @indi. + */ +void rt_vbus_register_listener(unsigned char chnr, + enum rt_vbus_event_id eve, + rt_vbus_event_listener indi, + void *ctx); + +/** Listen on any events happen on the @chnr for @timeout ticks. + * + * This function blocks until events occur or timeout happened. + */ +rt_err_t rt_vbus_listen_on(rt_uint8_t chnr, + rt_int32_t timeout); + +/** Push a data package into the receive queue of the channel @chnr. */ +void rt_vbus_data_push(unsigned int chnr, + struct rt_vbus_data *data); +/** Pop a data package from the receive queue of the channel @chnr. + * + * The actual data is following the struct rt_vbus_data. After using it, it + * should be freed by rt_free. + */ +struct rt_vbus_data* rt_vbus_data_pop(unsigned int chnr); + +struct rt_vbus_dev +{ + /* Runtime infomations. */ + rt_uint8_t chnr; + struct rt_vbus_data *act; + rt_size_t pos; + + /* There will be a request for each channel. So no need to seperate them so + * clearly. */ + struct rt_vbus_request req; +}; + +rt_err_t rt_vbus_chnx_init(void); +/** Get the corresponding channel number from the VBus device @dev. */ +rt_uint8_t rt_vbus_get_chnnr(rt_device_t dev); +/** Register a call back on the other side disconnect the channel. + * + * @sa rt_vbus_register_listener . + */ +void rt_vbus_chnx_register_disconn(rt_device_t dev, + rt_vbus_event_listener indi, + void *ctx); + +/* Commands for the device control interface. */ +#define VBUS_IOCRECV_WM 0xD1 +#define VBUS_IOCPOST_WM 0xD2 +/** Configure event listener */ +#define VBUS_IOC_LISCFG 0xD3 + +struct rt_vbus_dev_liscfg +{ + enum rt_vbus_event_id event; + rt_vbus_event_listener listener; + void *ctx; +}; + +int rt_vbus_shell_start(void); +#ifdef RT_USING_VBUS_RFS +int dfs_rfs_init(void); +#endif + +/** VBus hardware init function. + * + * BSP should implement this function to initialize the interrupts etc. + */ +int rt_vbus_hw_init(void); + +/** VBus ISR function. + * + * BSP should call this function when the interrupt from other core is + * triggered. @param is not used by VBus and will pass to rt_vbus_hw_eoi. + */ +void rt_vbus_isr(int irqnr, void *param); + +/** VBus End Of Interrupt function. + * + * This function will be called when VBus finished the ISR handling. BSP should + * define this function to clear the interrupt flag etc. + */ +int rt_vbus_hw_eoi(int irqnr, void *param); + +#endif /* end of include guard: __VBUS_H__ */ diff --git a/components/vbus/vbus_chnx.c b/components/vbus/vbus_chnx.c new file mode 100644 index 0000000000..fab6b20c2d --- /dev/null +++ b/components/vbus/vbus_chnx.c @@ -0,0 +1,286 @@ +/* + * Channel on VMM Bus + * + * COPYRIGHT (C) 2013, Shanghai Real-Thread Technology Co., Ltd + * + * This file is part of RT-Thread (http://www.rt-thread.org) + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2013-11-04 Grissiom add comment + */ + +#include +#include +#include + +#include "vbus.h" + +static void _rx_indicate(void *ctx) +{ + rt_device_t dev = ctx; + + if (dev->rx_indicate) + dev->rx_indicate(dev, 0); +} + +static void _tx_complete(void *ctx) +{ + rt_device_t dev = ctx; + + if (dev->tx_complete) + dev->tx_complete(dev, 0); +} + +static rt_err_t _open(rt_device_t dev, rt_uint16_t oflag) +{ + int chnr; + struct rt_vbus_dev *vdev = dev->user_data; + + if (vdev->chnr) + return RT_EOK; + + /* FIXME: request the same name for twice will crash */ + chnr = rt_vbus_request_chn(&vdev->req, RT_WAITING_FOREVER); + if (chnr < 0) + return chnr; + + vdev->chnr = chnr; + rt_vbus_register_listener(chnr, RT_VBUS_EVENT_ID_RX, _rx_indicate, dev); + rt_vbus_register_listener(chnr, RT_VBUS_EVENT_ID_TX, _tx_complete, dev); + + return RT_EOK; +} + +static rt_err_t _close(rt_device_t dev) +{ + struct rt_vbus_dev *vdev = dev->user_data; + + RT_ASSERT(vdev->chnr != 0); + + rt_vbus_close_chn(vdev->chnr); + vdev->chnr = 0; + + return RT_EOK; +} + +static rt_size_t _read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) +{ + rt_size_t outsz = 0; + struct rt_vbus_dev *vdev = dev->user_data; + + RT_ASSERT(vdev->chnr != 0); + + if (vdev->act == RT_NULL) + { + vdev->act = rt_vbus_data_pop(vdev->chnr); + vdev->pos = 0; + } + + while (1) + { + rt_err_t err; + + while (vdev->act) + { + rt_size_t cpysz; + + if (size - outsz > vdev->act->size - vdev->pos) + cpysz = vdev->act->size - vdev->pos; + else + cpysz = size - outsz; + + rt_memcpy((char*)buffer + outsz, ((char*)(vdev->act+1)) + vdev->pos, cpysz); + vdev->pos += cpysz; + + outsz += cpysz; + if (outsz == size) + { + return outsz; + } + else if (outsz > size) + RT_ASSERT(0); + + /* free old and get new */ + rt_free(vdev->act); + vdev->act = rt_vbus_data_pop(vdev->chnr); + vdev->pos = 0; + } + + /* TODO: We don't want to touch the rx_indicate here. But this lead to + * some duplication. Maybe we should find a better way to handle this. + */ + if (rt_interrupt_get_nest() == 0) + { + err = rt_vbus_listen_on(vdev->chnr, RT_WAITING_FOREVER); + } + else + { + err = rt_vbus_listen_on(vdev->chnr, 0); + } + if (err != RT_EOK) + { + rt_set_errno(err); + return outsz; + } + vdev->act = rt_vbus_data_pop(vdev->chnr); + vdev->pos = 0; + } +} + +static rt_size_t _write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + rt_err_t err; + struct rt_vbus_dev *vdev = dev->user_data; + + RT_ASSERT(vdev->chnr != 0); + + if (rt_interrupt_get_nest() == 0) + { + /* Thread context. */ + err = rt_vbus_post(vdev->chnr, vdev->req.prio, + buffer, size, RT_WAITING_FOREVER); + } + else + { + /* Interrupt context. */ + err = rt_vbus_post(vdev->chnr, vdev->req.prio, + buffer, size, 0); + } + + if (err) + { + rt_set_errno(err); + return 0; + } + + return size; +} + +rt_err_t _control(rt_device_t dev, rt_uint8_t cmd, void *args) +{ + RT_ASSERT(dev); + + switch (cmd) { + case VBUS_IOC_LISCFG: { + struct rt_vbus_dev *vdev = dev->user_data; + struct rt_vbus_dev_liscfg *liscfg = args; + + RT_ASSERT(vdev->chnr != 0); + if (!liscfg) + return -RT_ERROR; + + rt_vbus_register_listener(vdev->chnr, liscfg->event, + liscfg->listener, liscfg->ctx); + return RT_EOK; + } + break; +#ifdef RT_VBUS_USING_FLOW_CONTROL + case VBUS_IOCRECV_WM: { + struct rt_vbus_dev *vdev = dev->user_data; + struct rt_vbus_wm_cfg *cfg; + + RT_ASSERT(vdev->chnr != 0); + + if (!args) + return -RT_ERROR; + + cfg = (struct rt_vbus_wm_cfg*)args; + if (cfg->low > cfg->high) + return -RT_ERROR; + + rt_vbus_set_recv_wm(vdev->chnr, cfg->low, cfg->high); + return RT_EOK; + } + break; + case VBUS_IOCPOST_WM: { + struct rt_vbus_dev *vdev = dev->user_data; + struct rt_vbus_wm_cfg *cfg; + + RT_ASSERT(vdev->chnr != 0); + + if (!args) + return -RT_ERROR; + + cfg = (struct rt_vbus_wm_cfg*)args; + if (cfg->low > cfg->high) + return -RT_ERROR; + + rt_vbus_set_post_wm(vdev->chnr, cfg->low, cfg->high); + return RT_EOK; + } + break; +#endif + default: + break; + }; + + return -RT_ENOSYS; +} + +rt_uint8_t rt_vbus_get_chnnr(rt_device_t dev) +{ + struct rt_vbus_dev *vdev; + + RT_ASSERT(dev); + + vdev = dev->user_data; + + return vdev->chnr; +} + +void rt_vbus_chnx_register_disconn(rt_device_t dev, + rt_vbus_event_listener indi, + void *ctx) +{ + struct rt_vbus_dev *vdev = dev->user_data; + + RT_ASSERT(vdev->chnr != 0); + + if (vdev) + rt_vbus_register_listener(vdev->chnr, RT_VBUS_EVENT_ID_DISCONN, + indi, ctx); +} + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +extern struct rt_vbus_dev rt_vbus_chn_devx[]; +static struct rt_device _devx[32]; + +rt_err_t rt_vbus_chnx_init(void) +{ + int i; + struct rt_vbus_dev *p; + + for (i = 0, p = rt_vbus_chn_devx; + i < ARRAY_SIZE(_devx) && p->req.name; + i++, p++) + { + _devx[i].type = RT_Device_Class_Char; + _devx[i].open = _open; + _devx[i].close = _close; + _devx[i].read = _read; + _devx[i].write = _write; + _devx[i].control = _control; + _devx[i].user_data = p; + rt_device_register(&_devx[i], p->req.name, RT_DEVICE_FLAG_RDWR); + } + + return RT_EOK; +} -- GitLab