From c61e7b522c85c566b52f0a805166ebd246453d9d Mon Sep 17 00:00:00 2001 From: Rbb666 Date: Fri, 28 Oct 2022 14:51:13 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=AE=8F=E5=86=85?= =?UTF-8?q?=E6=A0=B8=E4=B8=BB=E5=88=86=E6=94=AF=E9=83=A8=E5=88=86=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Kconfig | 2 + components/drivers/Kconfig | 35 +- components/drivers/audio/audio.c | 26 +- components/drivers/audio/audio_pipe.c | 12 +- components/drivers/i2c/i2c-bit-ops.c | 25 +- components/drivers/i2c/i2c_core.c | 29 +- components/drivers/i2c/i2c_dev.c | 19 +- .../drivers/include/drivers/i2c-bit-ops.h | 2 +- components/drivers/include/drivers/i2c.h | 20 +- components/drivers/include/drivers/i2c_dev.h | 12 +- components/drivers/include/drivers/pin.h | 1 - .../drivers/include/drivers/serial_v2.h | 193 ++ components/drivers/include/drivers/touch.h | 115 ++ components/drivers/include/ipc/dataqueue.h | 2 +- components/drivers/include/rtdevice.h | 8 + components/drivers/serial/SConscript | 12 +- components/drivers/serial/serial.c | 2 +- components/drivers/serial/serial_v2.c | 1617 +++++++++++++++++ components/drivers/src/dataqueue.c | 4 +- components/drivers/touch/touch.c | 61 +- components/drivers/touch/touch.h | 101 +- components/fal/Kconfig | 55 + components/fal/SConscript | 14 + components/fal/docs/fal_api.md | 145 ++ components/fal/docs/fal_api_en.md | 144 ++ components/fal/docs/figures/fal-api-en.png | Bin 0 -> 55328 bytes components/fal/docs/figures/fal-api.png | Bin 0 -> 47111 bytes components/fal/docs/figures/fal-port-en.png | Bin 0 -> 24848 bytes components/fal/docs/figures/fal-port.png | Bin 0 -> 24465 bytes .../fal/docs/figures/fal_framework-en.png | Bin 0 -> 35858 bytes components/fal/docs/figures/fal_framework.png | Bin 0 -> 33974 bytes components/fal/inc/fal.h | 160 ++ components/fal/inc/fal_def.h | 121 ++ components/fal/samples/README.md | 4 + components/fal/samples/porting/README.md | 108 ++ components/fal/samples/porting/fal_cfg.h | 41 + .../fal/samples/porting/fal_flash_sfud_port.c | 96 + .../samples/porting/fal_flash_stm32f2_port.c | 198 ++ components/fal/src/fal.c | 62 + components/fal/src/fal_flash.c | 79 + components/fal/src/fal_partition.c | 514 ++++++ components/fal/src/fal_rtt.c | 934 ++++++++++ components/finsh/Kconfig | 133 +- components/finsh/cmd.c | 4 +- include/rtdef.h | 32 +- include/rtthread.h | 1 + src/clock.c | 21 + src/kservice.c | 34 + 48 files changed, 4926 insertions(+), 272 deletions(-) create mode 100644 components/drivers/include/drivers/serial_v2.h create mode 100644 components/drivers/include/drivers/touch.h create mode 100644 components/drivers/serial/serial_v2.c create mode 100644 components/fal/Kconfig create mode 100644 components/fal/SConscript create mode 100644 components/fal/docs/fal_api.md create mode 100644 components/fal/docs/fal_api_en.md create mode 100644 components/fal/docs/figures/fal-api-en.png create mode 100644 components/fal/docs/figures/fal-api.png create mode 100644 components/fal/docs/figures/fal-port-en.png create mode 100644 components/fal/docs/figures/fal-port.png create mode 100644 components/fal/docs/figures/fal_framework-en.png create mode 100644 components/fal/docs/figures/fal_framework.png create mode 100644 components/fal/inc/fal.h create mode 100644 components/fal/inc/fal_def.h create mode 100644 components/fal/samples/README.md create mode 100644 components/fal/samples/porting/README.md create mode 100644 components/fal/samples/porting/fal_cfg.h create mode 100644 components/fal/samples/porting/fal_flash_sfud_port.c create mode 100644 components/fal/samples/porting/fal_flash_stm32f2_port.c create mode 100644 components/fal/src/fal.c create mode 100644 components/fal/src/fal_flash.c create mode 100644 components/fal/src/fal_partition.c create mode 100644 components/fal/src/fal_rtt.c diff --git a/components/Kconfig b/components/Kconfig index 9a9efa8434..780274142a 100644 --- a/components/Kconfig +++ b/components/Kconfig @@ -27,6 +27,8 @@ source "$RTT_DIR/components/dfs/Kconfig" source "$RTT_DIR/components/drivers/Kconfig" +source "$RTT_DIR/components/fal/Kconfig" + source "$RTT_DIR/components/libc/Kconfig" source "$RTT_DIR/components/net/Kconfig" diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index d8af429134..ea54505b7b 100644 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -28,22 +28,30 @@ if RT_USING_DEVICE_IPC endif endif -config RT_USING_SERIAL - bool "Using serial device drivers" +menuconfig RT_USING_SERIAL + bool "USING Serial device drivers" select RT_USING_DEVICE_IPC select RT_USING_DEVICE default y -if RT_USING_SERIAL - config RT_SERIAL_USING_DMA - bool "Enable serial DMA mode" - default y - - config RT_SERIAL_RB_BUFSZ - int "Set RX buffer size" - default 64 + if RT_USING_SERIAL + choice + prompt "Choice Serial version" + default RT_USING_SERIAL_V1 + config RT_USING_SERIAL_V1 + bool "RT_USING_SERIAL_V1" + config RT_USING_SERIAL_V2 + bool "RT_USING_SERIAL_V2" + endchoice + config RT_SERIAL_USING_DMA + bool "Enable serial DMA mode" + default y -endif + config RT_SERIAL_RB_BUFSZ + int "Set RX buffer size" + depends on !RT_USING_SERIAL_V2 + default 64 + endif config RT_USING_TTY bool "Using TTY SYSTEM" @@ -330,6 +338,11 @@ endif config RT_USING_TOUCH bool "Using Touch device drivers" default n + if RT_USING_TOUCH + config RT_TOUCH_PIN_IRQ + bool "touch irq use pin irq" + default n + endif config RT_USING_LCD bool "Using LCD graphic drivers" diff --git a/components/drivers/audio/audio.c b/components/drivers/audio/audio.c index 846dad07cf..edef7d0eeb 100644 --- a/components/drivers/audio/audio.c +++ b/components/drivers/audio/audio.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -12,7 +12,6 @@ #include #include #include -#include #include #define DBG_TAG "audio" @@ -45,27 +44,27 @@ static rt_err_t _audio_send_replay_frame(struct rt_audio_device *audio) position = audio->replay->pos; dst_size = buf_info->block_size; - /* check repaly queue is empty */ - if (rt_data_queue_peak(&audio->replay->queue, (const void **)&data, &src_size) != RT_EOK) + /* check replay queue is empty */ + if (rt_data_queue_peek(&audio->replay->queue, (const void **)&data, &src_size) != RT_EOK) { /* ack stop event */ if (audio->replay->event & REPLAY_EVT_STOP) rt_completion_done(&audio->replay->cmp); /* send zero frames */ - memset(&buf_info->buffer[audio->replay->pos], 0, dst_size); + rt_memset(&buf_info->buffer[audio->replay->pos], 0, dst_size); audio->replay->pos += dst_size; audio->replay->pos %= buf_info->total_size; } else { - memset(&buf_info->buffer[audio->replay->pos], 0, dst_size); + rt_memset(&buf_info->buffer[audio->replay->pos], 0, dst_size); /* copy data from memory pool to hardware device fifo */ while (index < dst_size) { - result = rt_data_queue_peak(&audio->replay->queue, (const void **)&data, &src_size); + result = rt_data_queue_peek(&audio->replay->queue, (const void **)&data, &src_size); if (result != RT_EOK) { LOG_D("under run %d, remain %d", audio->replay->pos, remain_bytes); @@ -78,7 +77,7 @@ static rt_err_t _audio_send_replay_frame(struct rt_audio_device *audio) } remain_bytes = MIN((dst_size - index), (src_size - audio->replay->read_index)); - memcpy(&buf_info->buffer[audio->replay->pos], + rt_memcpy(&buf_info->buffer[audio->replay->pos], &data[audio->replay->read_index], remain_bytes); index += remain_bytes; @@ -230,14 +229,14 @@ static rt_err_t _audio_dev_init(struct rt_device *dev) if (replay == RT_NULL) return -RT_ENOMEM; - memset(replay, 0, sizeof(struct rt_audio_replay)); + rt_memset(replay, 0, sizeof(struct rt_audio_replay)); /* init memory pool for replay */ replay->mp = rt_mp_create("adu_mp", RT_AUDIO_REPLAY_MP_BLOCK_COUNT, RT_AUDIO_REPLAY_MP_BLOCK_SIZE); if (replay->mp == RT_NULL) { rt_free(replay); - LOG_E("create memory pool for repaly failed"); + LOG_E("create memory pool for replay failed"); return -RT_ENOMEM; } @@ -259,7 +258,7 @@ static rt_err_t _audio_dev_init(struct rt_device *dev) if (record == RT_NULL) return -RT_ENOMEM; - memset(record, 0, sizeof(struct rt_audio_record)); + rt_memset(record, 0, sizeof(struct rt_audio_record)); /* init pipe for record*/ buffer = rt_malloc(RT_AUDIO_RECORD_PIPE_SIZE); @@ -394,12 +393,12 @@ static rt_size_t _audio_dev_write(struct rt_device *dev, rt_off_t pos, const voi if (audio->replay->write_index % block_size == 0) { audio->replay->write_data = rt_mp_alloc(audio->replay->mp, RT_WAITING_FOREVER); - memset(audio->replay->write_data, 0, block_size); + rt_memset(audio->replay->write_data, 0, block_size); } /* copy data to replay memory pool */ remain_bytes = MIN((block_size - audio->replay->write_index), (size - index)); - memcpy(&audio->replay->write_data[audio->replay->write_index], &ptr[index], remain_bytes); + rt_memcpy(&audio->replay->write_data[audio->replay->write_index], &ptr[index], remain_bytes); index += remain_bytes; audio->replay->write_index += remain_bytes; @@ -590,7 +589,6 @@ int rt_audio_samplerate_to_speed(rt_uint32_t bitValue) speed = 192000; break; default: - break; } diff --git a/components/drivers/audio/audio_pipe.c b/components/drivers/audio/audio_pipe.c index 4fd8b0edfe..35a3faf73f 100644 --- a/components/drivers/audio/audio_pipe.c +++ b/components/drivers/audio/audio_pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -9,9 +9,7 @@ */ #include -#include #include - #include "audio_pipe.h" static void _rt_pipe_resume_writer(struct rt_audio_pipe *pipe) @@ -39,7 +37,7 @@ static rt_size_t rt_pipe_read(rt_device_t dev, void *buffer, rt_size_t size) { - rt_uint32_t level; + rt_base_t level; rt_thread_t thread; struct rt_audio_pipe *pipe; rt_size_t read_nbytes; @@ -72,7 +70,7 @@ static rt_size_t rt_pipe_read(rt_device_t dev, read_nbytes = rt_ringbuffer_get(&(pipe->ringbuffer), (rt_uint8_t *)buffer, size); if (read_nbytes == 0) { - rt_thread_suspend_with_flag(thread, RT_UNINTERRUPTIBLE); + rt_thread_suspend(thread); /* waiting on suspended read list */ rt_list_insert_before(&(pipe->suspended_read_list), &(thread->tlist)); @@ -121,7 +119,7 @@ static rt_size_t rt_pipe_write(rt_device_t dev, const void *buffer, rt_size_t size) { - rt_uint32_t level; + rt_base_t level; rt_thread_t thread; struct rt_audio_pipe *pipe; rt_size_t write_nbytes; @@ -160,7 +158,7 @@ static rt_size_t rt_pipe_write(rt_device_t dev, if (write_nbytes == 0) { /* pipe full, waiting on suspended write list */ - rt_thread_suspend_with_flag(thread, RT_UNINTERRUPTIBLE); + rt_thread_suspend(thread); /* waiting on suspended read list */ rt_list_insert_before(&(pipe->suspended_write_list), &(thread->tlist)); diff --git a/components/drivers/i2c/i2c-bit-ops.c b/components/drivers/i2c/i2c-bit-ops.c index 99338eef54..dd294731aa 100644 --- a/components/drivers/i2c/i2c-bit-ops.c +++ b/components/drivers/i2c/i2c-bit-ops.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -372,11 +372,12 @@ static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus, { struct rt_i2c_msg *msg; struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv; - rt_int32_t i, ret; + rt_int32_t ret; + rt_uint32_t i; rt_uint16_t ignore_nack; - LOG_D("send start condition"); - i2c_start(ops); + if (num == 0) return 0; + for (i = 0; i < num; i++) { msg = &msgs[i]; @@ -387,6 +388,11 @@ static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus, { i2c_restart(ops); } + else + { + LOG_D("send start condition"); + i2c_start(ops); + } ret = i2c_bit_send_address(bus, msg); if ((ret != RT_EOK) && !ignore_nack) { @@ -399,7 +405,9 @@ static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus, { ret = i2c_recv_bytes(bus, msg); if (ret >= 1) + { LOG_D("read %d byte%s", ret, ret == 1 ? "" : "s"); + } if (ret < msg->len) { if (ret >= 0) @@ -411,7 +419,9 @@ static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus, { ret = i2c_send_bytes(bus, msg); if (ret >= 1) + { LOG_D("write %d byte%s", ret, ret == 1 ? "" : "s"); + } if (ret < msg->len) { if (ret >= 0) @@ -423,8 +433,11 @@ static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus, ret = i; out: - LOG_D("send stop condition"); - i2c_stop(ops); + if (!(msg->flags & RT_I2C_NO_STOP)) + { + LOG_D("send stop condition"); + i2c_stop(ops); + } return ret; } diff --git a/components/drivers/i2c/i2c_core.c b/components/drivers/i2c/i2c_core.c index f358c2b758..39147be605 100644 --- a/components/drivers/i2c/i2c_core.c +++ b/components/drivers/i2c/i2c_core.c @@ -1,11 +1,12 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2012-04-25 weety first version + * 2021-04-20 RiceChen added support for bus control api */ #include @@ -23,7 +24,7 @@ rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus, { rt_err_t res = RT_EOK; - rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_FIFO); + rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_PRIO); if (bus->timeout == 0) bus->timeout = RT_TICK_PER_SECOND; @@ -81,13 +82,33 @@ rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, } } +rt_err_t rt_i2c_control(struct rt_i2c_bus_device *bus, + rt_uint32_t cmd, + rt_uint32_t arg) +{ + rt_err_t ret; + + if(bus->ops->i2c_bus_control) + { + ret = bus->ops->i2c_bus_control(bus, cmd, arg); + + return ret; + } + else + { + LOG_E("I2C bus operation not supported"); + + return 0; + } +} + rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus, rt_uint16_t addr, rt_uint16_t flags, const rt_uint8_t *buf, rt_uint32_t count) { - rt_err_t ret; + rt_size_t ret; struct rt_i2c_msg msg; msg.addr = addr; @@ -106,7 +127,7 @@ rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus, rt_uint8_t *buf, rt_uint32_t count) { - rt_err_t ret; + rt_size_t ret; struct rt_i2c_msg msg; RT_ASSERT(bus != RT_NULL); diff --git a/components/drivers/i2c/i2c_dev.c b/components/drivers/i2c/i2c_dev.c index 59f728be09..4e2ca8924f 100644 --- a/components/drivers/i2c/i2c_dev.c +++ b/components/drivers/i2c/i2c_dev.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -7,6 +7,7 @@ * Date Author Notes * 2012-04-25 weety first version * 2014-08-03 bernard fix some compiling warning + * 2021-04-20 RiceChen added support for bus clock control */ #include @@ -66,6 +67,7 @@ static rt_err_t i2c_bus_device_control(rt_device_t dev, rt_err_t ret; struct rt_i2c_priv_data *priv_data; struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data; + rt_uint32_t bus_clock; RT_ASSERT(bus != RT_NULL); @@ -75,9 +77,6 @@ static rt_err_t i2c_bus_device_control(rt_device_t dev, case RT_I2C_DEV_CTRL_10BIT: bus->flags |= RT_I2C_ADDR_10BIT; break; - case RT_I2C_DEV_CTRL_ADDR: - bus->addr = *(rt_uint16_t *)args; - break; case RT_I2C_DEV_CTRL_TIMEOUT: bus->timeout = *(rt_uint32_t *)args; break; @@ -89,6 +88,14 @@ static rt_err_t i2c_bus_device_control(rt_device_t dev, return -RT_EIO; } break; + case RT_I2C_DEV_CTRL_CLK: + bus_clock = *(rt_uint32_t *)args; + ret = rt_i2c_control(bus, cmd, bus_clock); + if (ret < 0) + { + return -RT_EIO; + } + break; default: break; } @@ -97,9 +104,9 @@ static rt_err_t i2c_bus_device_control(rt_device_t dev, } #ifdef RT_USING_DEVICE_OPS -const static struct rt_device_ops i2c_ops = +const static struct rt_device_ops i2c_ops = { - RT_NULL, + RT_NULL, RT_NULL, RT_NULL, i2c_bus_device_read, diff --git a/components/drivers/include/drivers/i2c-bit-ops.h b/components/drivers/include/drivers/i2c-bit-ops.h index ddf9a36d73..0d94de114a 100644 --- a/components/drivers/include/drivers/i2c-bit-ops.h +++ b/components/drivers/include/drivers/i2c-bit-ops.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * diff --git a/components/drivers/include/drivers/i2c.h b/components/drivers/include/drivers/i2c.h index 17d57288cd..308c344294 100644 --- a/components/drivers/include/drivers/i2c.h +++ b/components/drivers/include/drivers/i2c.h @@ -1,11 +1,12 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2012-04-25 weety first version + * 2021-04-20 RiceChen added support for bus control api */ #ifndef __I2C_H__ @@ -23,6 +24,7 @@ extern "C" { #define RT_I2C_NO_START (1u << 4) #define RT_I2C_IGNORE_NACK (1u << 5) #define RT_I2C_NO_READ_ACK (1u << 6) /* when I2C reading, we do not ACK */ +#define RT_I2C_NO_STOP (1u << 7) struct rt_i2c_msg { @@ -53,7 +55,6 @@ struct rt_i2c_bus_device struct rt_device parent; const struct rt_i2c_bus_device_ops *ops; rt_uint16_t flags; - rt_uint16_t addr; struct rt_mutex lock; rt_uint32_t timeout; rt_uint32_t retries; @@ -62,7 +63,6 @@ struct rt_i2c_bus_device struct rt_i2c_client { - struct rt_device parent; struct rt_i2c_bus_device *bus; rt_uint16_t client_addr; }; @@ -73,6 +73,9 @@ struct rt_i2c_bus_device *rt_i2c_bus_device_find(const char *bus_name); rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num); +rt_err_t rt_i2c_control(struct rt_i2c_bus_device *bus, + rt_uint32_t cmd, + rt_uint32_t arg); rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus, rt_uint16_t addr, rt_uint16_t flags, @@ -83,6 +86,17 @@ rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus, rt_uint16_t flags, rt_uint8_t *buf, rt_uint32_t count); + +rt_inline rt_err_t rt_i2c_bus_lock(struct rt_i2c_bus_device *bus, rt_tick_t timeout) +{ + return rt_mutex_take(&bus->lock, timeout); +} + +rt_inline rt_err_t rt_i2c_bus_unlock(struct rt_i2c_bus_device *bus) +{ + return rt_mutex_release(&bus->lock); +} + int rt_i2c_core_init(void); #ifdef __cplusplus diff --git a/components/drivers/include/drivers/i2c_dev.h b/components/drivers/include/drivers/i2c_dev.h index 5ef9dd1a8c..9fc454b1b2 100644 --- a/components/drivers/include/drivers/i2c_dev.h +++ b/components/drivers/include/drivers/i2c_dev.h @@ -1,11 +1,12 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2012-04-25 weety first version + * 2021-04-20 RiceChen added bus clock command */ #ifndef __I2C_DEV_H__ @@ -17,10 +18,11 @@ extern "C" { #endif -#define RT_I2C_DEV_CTRL_10BIT 0x20 -#define RT_I2C_DEV_CTRL_ADDR 0x21 -#define RT_I2C_DEV_CTRL_TIMEOUT 0x22 -#define RT_I2C_DEV_CTRL_RW 0x23 +#define RT_I2C_DEV_CTRL_10BIT (RT_DEVICE_CTRL_BASE(I2CBUS) + 0x01) +#define RT_I2C_DEV_CTRL_ADDR (RT_DEVICE_CTRL_BASE(I2CBUS) + 0x02) +#define RT_I2C_DEV_CTRL_TIMEOUT (RT_DEVICE_CTRL_BASE(I2CBUS) + 0x03) +#define RT_I2C_DEV_CTRL_RW (RT_DEVICE_CTRL_BASE(I2CBUS) + 0x04) +#define RT_I2C_DEV_CTRL_CLK (RT_DEVICE_CTRL_BASE(I2CBUS) + 0x05) struct rt_i2c_priv_data { diff --git a/components/drivers/include/drivers/pin.h b/components/drivers/include/drivers/pin.h index f5b2ded9f6..811ca33dcc 100644 --- a/components/drivers/include/drivers/pin.h +++ b/components/drivers/include/drivers/pin.h @@ -13,7 +13,6 @@ #define PIN_H__ #include -#include #ifdef __cplusplus extern "C" { diff --git a/components/drivers/include/drivers/serial_v2.h b/components/drivers/include/drivers/serial_v2.h new file mode 100644 index 0000000000..b1020aaf99 --- /dev/null +++ b/components/drivers/include/drivers/serial_v2.h @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2021-06-01 KyleChan first version + */ + +#ifndef __SERIAL_V2_H__ +#define __SERIAL_V2_H__ + +#include + +#define BAUD_RATE_2400 2400 +#define BAUD_RATE_4800 4800 +#define BAUD_RATE_9600 9600 +#define BAUD_RATE_19200 19200 +#define BAUD_RATE_38400 38400 +#define BAUD_RATE_57600 57600 +#define BAUD_RATE_115200 115200 +#define BAUD_RATE_230400 230400 +#define BAUD_RATE_460800 460800 +#define BAUD_RATE_921600 921600 +#define BAUD_RATE_2000000 2000000 +#define BAUD_RATE_2500000 2500000 +#define BAUD_RATE_3000000 3000000 + +#define DATA_BITS_5 5 +#define DATA_BITS_6 6 +#define DATA_BITS_7 7 +#define DATA_BITS_8 8 +#define DATA_BITS_9 9 + +#define STOP_BITS_1 0 +#define STOP_BITS_2 1 +#define STOP_BITS_3 2 +#define STOP_BITS_4 3 + +#ifdef _WIN32 +#include +#else +#define PARITY_NONE 0 +#define PARITY_ODD 1 +#define PARITY_EVEN 2 +#endif + +#define BIT_ORDER_LSB 0 +#define BIT_ORDER_MSB 1 + +#define NRZ_NORMAL 0 /* Non Return to Zero : normal mode */ +#define NRZ_INVERTED 1 /* Non Return to Zero : inverted mode */ + +#define RT_DEVICE_FLAG_RX_BLOCKING 0x1000 +#define RT_DEVICE_FLAG_RX_NON_BLOCKING 0x2000 + +#define RT_DEVICE_FLAG_TX_BLOCKING 0x4000 +#define RT_DEVICE_FLAG_TX_NON_BLOCKING 0x8000 + +#define RT_SERIAL_RX_BLOCKING RT_DEVICE_FLAG_RX_BLOCKING +#define RT_SERIAL_RX_NON_BLOCKING RT_DEVICE_FLAG_RX_NON_BLOCKING +#define RT_SERIAL_TX_BLOCKING RT_DEVICE_FLAG_TX_BLOCKING +#define RT_SERIAL_TX_NON_BLOCKING RT_DEVICE_FLAG_TX_NON_BLOCKING + +#define RT_DEVICE_CHECK_OPTMODE 0x20 + +#define RT_SERIAL_EVENT_RX_IND 0x01 /* Rx indication */ +#define RT_SERIAL_EVENT_TX_DONE 0x02 /* Tx complete */ +#define RT_SERIAL_EVENT_RX_DMADONE 0x03 /* Rx DMA transfer done */ +#define RT_SERIAL_EVENT_TX_DMADONE 0x04 /* Tx DMA transfer done */ +#define RT_SERIAL_EVENT_RX_TIMEOUT 0x05 /* Rx timeout */ + +#define RT_SERIAL_ERR_OVERRUN 0x01 +#define RT_SERIAL_ERR_FRAMING 0x02 +#define RT_SERIAL_ERR_PARITY 0x03 + +#define RT_SERIAL_TX_DATAQUEUE_SIZE 2048 +#define RT_SERIAL_TX_DATAQUEUE_LWM 30 + +#define RT_SERIAL_RX_MINBUFSZ 64 +#define RT_SERIAL_TX_MINBUFSZ 64 + +#define RT_SERIAL_TX_BLOCKING_BUFFER 1 +#define RT_SERIAL_TX_BLOCKING_NO_BUFFER 0 + +#define RT_SERIAL_FLOWCONTROL_CTSRTS 1 +#define RT_SERIAL_FLOWCONTROL_NONE 0 + +/* Default config for serial_configure structure */ +#define RT_SERIAL_CONFIG_DEFAULT \ +{ \ + BAUD_RATE_115200, /* 115200 bits/s */ \ + DATA_BITS_8, /* 8 databits */ \ + STOP_BITS_1, /* 1 stopbit */ \ + PARITY_NONE, /* No parity */ \ + BIT_ORDER_LSB, /* LSB first sent */ \ + NRZ_NORMAL, /* Normal mode */ \ + RT_SERIAL_RX_MINBUFSZ, /* rxBuf size */ \ + RT_SERIAL_TX_MINBUFSZ, /* txBuf size */ \ + RT_SERIAL_FLOWCONTROL_NONE, /* Off flowcontrol */ \ + 0 \ +} + +struct serial_configure +{ + rt_uint32_t baud_rate; + + rt_uint32_t data_bits :4; + rt_uint32_t stop_bits :2; + rt_uint32_t parity :2; + rt_uint32_t bit_order :1; + rt_uint32_t invert :1; + rt_uint32_t rx_bufsz :16; + rt_uint32_t tx_bufsz :16; + rt_uint32_t flowcontrol :1; + rt_uint32_t reserved :5; +}; + +/* + * Serial Receive FIFO mode + */ +struct rt_serial_rx_fifo +{ + struct rt_ringbuffer rb; + + struct rt_completion rx_cpt; + + rt_uint16_t rx_cpt_index; + + /* software fifo */ + rt_uint8_t buffer[]; +}; + +/* + * Serial Transmit FIFO mode + */ +struct rt_serial_tx_fifo +{ + struct rt_ringbuffer rb; + + rt_size_t put_size; + + rt_bool_t activated; + + struct rt_completion tx_cpt; + + /* software fifo */ + rt_uint8_t buffer[]; +}; + +struct rt_serial_device +{ + struct rt_device parent; + + const struct rt_uart_ops *ops; + struct serial_configure config; + + void *serial_rx; + void *serial_tx; + + struct rt_device_notify rx_notify; +}; + +/** + * uart operators + */ +struct rt_uart_ops +{ + rt_err_t (*configure)(struct rt_serial_device *serial, + struct serial_configure *cfg); + + rt_err_t (*control)(struct rt_serial_device *serial, + int cmd, + void *arg); + + int (*putc)(struct rt_serial_device *serial, char c); + int (*getc)(struct rt_serial_device *serial); + + rt_size_t (*transmit)(struct rt_serial_device *serial, + rt_uint8_t *buf, + rt_size_t size, + rt_uint32_t tx_flag); +}; + +void rt_hw_serial_isr(struct rt_serial_device *serial, int event); + +rt_err_t rt_hw_serial_register(struct rt_serial_device *serial, + const char *name, + rt_uint32_t flag, + void *data); + +#endif diff --git a/components/drivers/include/drivers/touch.h b/components/drivers/include/drivers/touch.h new file mode 100644 index 0000000000..318f307081 --- /dev/null +++ b/components/drivers/include/drivers/touch.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2019-05-20 tyustli the first version + */ + +#ifndef __TOUCH_H__ +#define __TOUCH_H__ + +#include +#include "pin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef RT_USING_RTC +#define rt_touch_get_ts() time(RT_NULL) /* API for the touch to get the timestamp */ +#else +#define rt_touch_get_ts() rt_tick_get() /* API for the touch to get the timestamp */ +#endif + +#define RT_PIN_NONE 0xFFFF /* RT PIN NONE */ + +/* Touch vendor types */ +#define RT_TOUCH_VENDOR_UNKNOWN (0) /* unknown */ +#define RT_TOUCH_VENDOR_GT (1) /* GTxx series */ +#define RT_TOUCH_VENDOR_FT (2) /* FTxx series */ + +/* Touch ic type*/ +#define RT_TOUCH_TYPE_NONE (0) /* touch ic none */ +#define RT_TOUCH_TYPE_CAPACITANCE (1) /* capacitance ic */ +#define RT_TOUCH_TYPE_RESISTANCE (2) /* resistance ic */ + +/* Touch control cmd types */ +#define RT_TOUCH_CTRL_GET_ID (RT_DEVICE_CTRL_BASE(Touch) + 0) /* Get device id */ +#define RT_TOUCH_CTRL_GET_INFO (RT_DEVICE_CTRL_BASE(Touch) + 1) /* Get touch info */ +#define RT_TOUCH_CTRL_SET_MODE (RT_DEVICE_CTRL_BASE(Touch) + 2) /* Set touch's work mode. ex. RT_TOUCH_MODE_POLLING,RT_TOUCH_MODE_INT */ +#define RT_TOUCH_CTRL_SET_X_RANGE (RT_DEVICE_CTRL_BASE(Touch) + 3) /* Set x coordinate range */ +#define RT_TOUCH_CTRL_SET_Y_RANGE (RT_DEVICE_CTRL_BASE(Touch) + 4) /* Set y coordinate range */ +#define RT_TOUCH_CTRL_SET_X_TO_Y (RT_DEVICE_CTRL_BASE(Touch) + 5) /* Set X Y coordinate exchange */ +#define RT_TOUCH_CTRL_DISABLE_INT (RT_DEVICE_CTRL_BASE(Touch) + 6) /* Disable interrupt */ +#define RT_TOUCH_CTRL_ENABLE_INT (RT_DEVICE_CTRL_BASE(Touch) + 7) /* Enable interrupt */ +#define RT_TOUCH_CTRL_POWER_ON (RT_DEVICE_CTRL_BASE(Touch) + 8) /* Touch Power On */ +#define RT_TOUCH_CTRL_POWER_OFF (RT_DEVICE_CTRL_BASE(Touch) + 9) /* Touch Power Off */ +#define RT_TOUCH_CTRL_GET_STATUS (RT_DEVICE_CTRL_BASE(Touch) + 10) /* Get Touch Power Status */ + +/* Touch event */ +#define RT_TOUCH_EVENT_NONE (0) /* Touch none */ +#define RT_TOUCH_EVENT_UP (1) /* Touch up event */ +#define RT_TOUCH_EVENT_DOWN (2) /* Touch down event */ +#define RT_TOUCH_EVENT_MOVE (3) /* Touch move event */ + +struct rt_touch_info +{ + rt_uint8_t type; /* The touch type */ + rt_uint8_t vendor; /* Vendor of touchs */ + rt_uint8_t point_num; /* Support point num */ + rt_int32_t range_x; /* X coordinate range */ + rt_int32_t range_y; /* Y coordinate range */ +}; + +struct rt_touch_config +{ +#ifdef RT_TOUCH_PIN_IRQ + struct rt_device_pin_mode irq_pin; /* Interrupt pin, The purpose of this pin is to notification read data */ +#endif + char *dev_name; /* The name of the communication device */ + void *user_data; +}; + +typedef struct rt_touch_device *rt_touch_t; +struct rt_touch_device +{ + struct rt_device parent; /* The standard device */ + struct rt_touch_info info; /* The touch info data */ + struct rt_touch_config config; /* The touch config data */ + + const struct rt_touch_ops *ops; /* The touch ops */ + rt_err_t (*irq_handle)(rt_touch_t touch); /* Called when an interrupt is generated, registered by the driver */ +}; + +struct rt_touch_data +{ + rt_uint8_t event; /* The touch event of the data */ + rt_uint8_t track_id; /* Track id of point */ + rt_uint8_t width; /* Point of width */ + rt_uint16_t x_coordinate; /* Point of x coordinate */ + rt_uint16_t y_coordinate; /* Point of y coordinate */ + rt_tick_t timestamp; /* The timestamp when the data was received */ +}; + +struct rt_touch_ops +{ + rt_size_t (*touch_readpoint)(struct rt_touch_device *touch, void *buf, rt_size_t touch_num); + rt_err_t (*touch_control)(struct rt_touch_device *touch, int cmd, void *arg); +}; + +int rt_hw_touch_register(rt_touch_t touch, + const char *name, + rt_uint32_t flag, + void *data); + +/* if you doesn't use pin device. you must call this function in your touch irq callback */ +void rt_hw_touch_isr(rt_touch_t touch); + +#ifdef __cplusplus +} +#endif + +#endif /* __TOUCH_H__ */ diff --git a/components/drivers/include/ipc/dataqueue.h b/components/drivers/include/ipc/dataqueue.h index 0262f4702b..76203c404d 100644 --- a/components/drivers/include/ipc/dataqueue.h +++ b/components/drivers/include/ipc/dataqueue.h @@ -55,7 +55,7 @@ rt_err_t rt_data_queue_pop(struct rt_data_queue *queue, const void **data_ptr, rt_size_t *size, rt_int32_t timeout); -rt_err_t rt_data_queue_peak(struct rt_data_queue *queue, +rt_err_t rt_data_queue_peek(struct rt_data_queue *queue, const void **data_ptr, rt_size_t *size); void rt_data_queue_reset(struct rt_data_queue *queue); diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index 57c55fa038..2fc672d75b 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -57,7 +57,11 @@ extern "C" { #endif /* RT_USING_USB_HOST */ #ifdef RT_USING_SERIAL +#ifdef RT_USING_SERIAL_V2 +#include "drivers/serial_v2.h" +#else #include "drivers/serial.h" +#endif #endif /* RT_USING_SERIAL */ #ifdef RT_USING_I2C @@ -143,6 +147,10 @@ extern "C" { #include "drivers/rt_inputcapture.h" #endif +#ifdef RT_USING_TOUCH +#include "drivers/touch.h" +#endif + #ifdef RT_USING_LCD #include "drivers/lcd.h" #endif diff --git a/components/drivers/serial/SConscript b/components/drivers/serial/SConscript index a6eb115919..81d791803a 100644 --- a/components/drivers/serial/SConscript +++ b/components/drivers/serial/SConscript @@ -1,8 +1,14 @@ from building import * -cwd = GetCurrentDir() -src = Glob('*.c') +cwd = GetCurrentDir() CPPPATH = [cwd + '/../include'] -group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_SERIAL'], CPPPATH = CPPPATH) +group = [] +if GetDepend(['RT_USING_SERIAL']): + if GetDepend(['RT_USING_SERIAL_V2']): + src = Glob('serial_v2.c') + group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_SERIAL_V2'], CPPPATH = CPPPATH) + else: + src = Glob('serial.c') + group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_SERIAL'], CPPPATH = CPPPATH) Return('group') diff --git a/components/drivers/serial/serial.c b/components/drivers/serial/serial.c index eaab586888..c2881ca4ab 100644 --- a/components/drivers/serial/serial.c +++ b/components/drivers/serial/serial.c @@ -1106,7 +1106,7 @@ void rt_hw_serial_isr(struct rt_serial_device *serial, int event) tx_dma = (struct rt_serial_tx_dma*) serial->serial_tx; rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0); - if (rt_data_queue_peak(&(tx_dma->data_queue), &data_ptr, &data_size) == RT_EOK) + if (rt_data_queue_peek(&(tx_dma->data_queue), &data_ptr, &data_size) == RT_EOK) { /* transmit next data node */ tx_dma->activated = RT_TRUE; diff --git a/components/drivers/serial/serial_v2.c b/components/drivers/serial/serial_v2.c new file mode 100644 index 0000000000..a1b976bf73 --- /dev/null +++ b/components/drivers/serial/serial_v2.c @@ -0,0 +1,1617 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2021-06-01 KyleChan first version + */ + +#include +#include +#include + +#define DBG_TAG "Serial" +#define DBG_LVL DBG_INFO +#include + +#ifdef RT_USING_POSIX_STDIO +#include +#include +#include +#include +#include + +#ifdef RT_USING_POSIX_TERMIOS +#include +#endif + +#ifdef getc +#undef getc +#endif + +#ifdef putc +#undef putc +#endif + +static rt_err_t serial_fops_rx_ind(rt_device_t dev, rt_size_t size) +{ + rt_wqueue_wakeup(&(dev->wait_queue), (void*)POLLIN); + + return RT_EOK; +} + +/* fops for serial */ +static int serial_fops_open(struct dfs_fd *fd) +{ + rt_err_t ret = 0; + rt_uint16_t flags = 0; + rt_device_t device; + + device = (rt_device_t)fd->data; + RT_ASSERT(device != RT_NULL); + + switch (fd->flags & O_ACCMODE) + { + case O_RDONLY: + LOG_D("fops open: O_RDONLY!"); + flags = RT_DEVICE_FLAG_RDONLY; + break; + case O_WRONLY: + LOG_D("fops open: O_WRONLY!"); + flags = RT_DEVICE_FLAG_WRONLY; + break; + case O_RDWR: + LOG_D("fops open: O_RDWR!"); + flags = RT_DEVICE_FLAG_RDWR; + break; + default: + LOG_E("fops open: unknown mode - %d!", fd->flags & O_ACCMODE); + break; + } + + if ((fd->flags & O_ACCMODE) != O_WRONLY) + rt_device_set_rx_indicate(device, serial_fops_rx_ind); + ret = rt_device_open(device, flags); + if (ret == RT_EOK) return 0; + + return ret; +} + +static int serial_fops_close(struct dfs_fd *fd) +{ + rt_device_t device; + + device = (rt_device_t)fd->data; + + rt_device_set_rx_indicate(device, RT_NULL); + rt_device_close(device); + + return 0; +} + +static int serial_fops_ioctl(struct dfs_fd *fd, int cmd, void *args) +{ + rt_device_t device; + int flags = (int)(rt_base_t)args; + int mask = O_NONBLOCK | O_APPEND; + + device = (rt_device_t)fd->data; + switch (cmd) + { + case FIONREAD: + break; + case FIONWRITE: + break; + case F_SETFL: + flags &= mask; + fd->flags &= ~mask; + fd->flags |= flags; + break; + } + + return rt_device_control(device, cmd, args); +} + +static int serial_fops_read(struct dfs_fd *fd, void *buf, size_t count) +{ + int size = 0; + rt_device_t device; + + device = (rt_device_t)fd->data; + + do + { + size = rt_device_read(device, -1, buf, count); + if (size <= 0) + { + if (fd->flags & O_NONBLOCK) + { + size = -EAGAIN; + break; + } + + rt_wqueue_wait(&(device->wait_queue), 0, RT_WAITING_FOREVER); + } + }while (size <= 0); + + return size; +} + +static int serial_fops_write(struct dfs_fd *fd, const void *buf, size_t count) +{ + rt_device_t device; + + device = (rt_device_t)fd->data; + return rt_device_write(device, -1, buf, count); +} + +static int serial_fops_poll(struct dfs_fd *fd, struct rt_pollreq *req) +{ + int mask = 0; + int flags = 0; + rt_device_t device; + struct rt_serial_device *serial; + + device = (rt_device_t)fd->data; + RT_ASSERT(device != RT_NULL); + + serial = (struct rt_serial_device *)device; + + /* only support POLLIN */ + flags = fd->flags & O_ACCMODE; + if (flags == O_RDONLY || flags == O_RDWR) + { + rt_base_t level; + struct rt_serial_rx_fifo* rx_fifo; + + rt_poll_add(&(device->wait_queue), req); + + rx_fifo = (struct rt_serial_rx_fifo*) serial->serial_rx; + + level = rt_hw_interrupt_disable(); + + if (rt_ringbuffer_data_len(&rx_fifo->rb)) + mask |= POLLIN; + rt_hw_interrupt_enable(level); + } + // mask|=POLLOUT; + return mask; +} + +const static struct dfs_file_ops _serial_fops = +{ + serial_fops_open, + serial_fops_close, + serial_fops_ioctl, + serial_fops_read, + serial_fops_write, + RT_NULL, /* flush */ + RT_NULL, /* lseek */ + RT_NULL, /* getdents */ + serial_fops_poll, +}; +#endif /* RT_USING_POSIX_STDIO */ + +static rt_size_t rt_serial_get_linear_buffer(struct rt_ringbuffer *rb, + rt_uint8_t **ptr) +{ + rt_size_t size; + + RT_ASSERT(rb != RT_NULL); + + *ptr = RT_NULL; + + /* whether has enough data */ + size = rt_ringbuffer_data_len(rb); + + /* no data */ + if (size == 0) + return 0; + + *ptr = &rb->buffer_ptr[rb->read_index]; + + if(rb->buffer_size - rb->read_index > size) + { + return size; + } + + return rb->buffer_size - rb->read_index; +} + +static rt_size_t rt_serial_update_read_index(struct rt_ringbuffer *rb, + rt_uint16_t read_index) +{ + rt_size_t size; + + RT_ASSERT(rb != RT_NULL); + + /* whether has enough data */ + size = rt_ringbuffer_data_len(rb); + + /* no data */ + if (size == 0) + return 0; + + /* less data */ + if(size < read_index) + read_index = size; + + if(rb->buffer_size - rb->read_index > read_index) + { + rb->read_index += read_index; + return read_index; + } + + read_index = rb->buffer_size - rb->read_index; + + /* we are going into the other side of the mirror */ + rb->read_mirror = ~rb->read_mirror; + rb->read_index = 0; + + return read_index; +} + +static rt_size_t rt_serial_update_write_index(struct rt_ringbuffer *rb, + rt_uint16_t write_size) +{ + rt_uint16_t size; + RT_ASSERT(rb != RT_NULL); + + /* whether has enough space */ + size = rt_ringbuffer_space_len(rb); + + /* no space, drop some data */ + if (size < write_size) + { + write_size = size; +#if !defined(RT_USING_ULOG) || defined(ULOG_USING_ISR_LOG) + LOG_W("The serial buffer (len %d) is overflow.", rb->buffer_size); +#endif + } + + if (rb->buffer_size - rb->write_index > write_size) + { + /* this should not cause overflow because there is enough space for + * length of data in current mirror */ + rb->write_index += write_size; + return write_size; + } + + /* we are going into the other side of the mirror */ + rb->write_mirror = ~rb->write_mirror; + rb->write_index = write_size - (rb->buffer_size - rb->write_index); + + return write_size; +} + + +/** + * @brief Serial polling receive data routine, This function will receive data + * in a continuous loop by one by one byte. + * @param dev The pointer of device driver structure + * @param pos Empty parameter. + * @param buffer Receive data buffer. + * @param size Receive data buffer length. + * @return Return the final length of data received. + */ +rt_size_t _serial_poll_rx(struct rt_device *dev, + rt_off_t pos, + void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + rt_size_t getc_size; + int getc_element; /* Gets one byte of data received */ + rt_uint8_t *getc_buffer; /* Pointer to the receive data buffer */ + + RT_ASSERT(dev != RT_NULL); + + serial = (struct rt_serial_device *)dev; + RT_ASSERT(serial != RT_NULL); + getc_buffer = (rt_uint8_t *)buffer; + getc_size = size; + + while(size) + { + getc_element = serial->ops->getc(serial); + if (getc_element == -1) break; + + *getc_buffer = getc_element; + + ++ getc_buffer; + -- size; + + if (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM) + { + /* If open_flag satisfies RT_DEVICE_FLAG_STREAM + * and the received character is '\n', exit the loop directly */ + if (getc_element == '\n') break; + } + } + + return getc_size - size; +} + +/** + * @brief Serial polling transmit data routines, This function will transmit + * data in a continuous loop by one by one byte. + * @param dev The pointer of device driver structure + * @param pos Empty parameter. + * @param buffer Transmit data buffer. + * @param size Transmit data buffer length. + * @return Return the final length of data received. + */ +rt_size_t _serial_poll_tx(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + rt_size_t putc_size; + rt_uint8_t *putc_buffer; /* Pointer to the transmit data buffer */ + RT_ASSERT(dev != RT_NULL); + + serial = (struct rt_serial_device *)dev; + RT_ASSERT(serial != RT_NULL); + + putc_buffer = (rt_uint8_t *)buffer; + putc_size = size; + + while (size) + { + if (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM) + { + /* If open_flag satisfies RT_DEVICE_FLAG_STREAM and the received character is '\n', + * inserts '\r' character before '\n' character for the effect of carriage return newline */ + if (*putc_buffer == '\n') + serial->ops->putc(serial, '\r'); + } + serial->ops->putc(serial, *putc_buffer); + + ++ putc_buffer; + -- size; + } + + return putc_size - size; +} + +/** + * @brief Serial receive data routines, This function will receive + * data by using fifo + * @param dev The pointer of device driver structure + * @param pos Empty parameter. + * @param buffer Receive data buffer. + * @param size Receive data buffer length. + * @return Return the final length of data received. + */ +static rt_size_t _serial_fifo_rx(struct rt_device *dev, + rt_off_t pos, + void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + struct rt_serial_rx_fifo *rx_fifo; + rt_base_t level; + rt_size_t recv_len; /* The length of data from the ringbuffer */ + + RT_ASSERT(dev != RT_NULL); + if (size == 0) return 0; + + serial = (struct rt_serial_device *)dev; + + RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); + + rx_fifo = (struct rt_serial_rx_fifo *) serial->serial_rx; + + if (dev->open_flag & RT_SERIAL_RX_BLOCKING) + { + if (size > serial->config.rx_bufsz) + { + LOG_W("(%s) serial device received data:[%d] larger than " + "rx_bufsz:[%d], please increase the BSP_UARTx_RX_BUFSIZE option", + dev->parent.name, size, serial->config.rx_bufsz); + + return 0; + } + /* Get the length of the data from the ringbuffer */ + recv_len = rt_ringbuffer_data_len(&(rx_fifo->rb)); + + if (recv_len < size) + { + /* When recv_len is less than size, rx_cpt_index is updated to the size + * and rt_current_thread is suspend until rx_cpt_index is equal to 0 */ + rx_fifo->rx_cpt_index = size; + rt_completion_wait(&(rx_fifo->rx_cpt), RT_WAITING_FOREVER); + } + } + + /* This part of the code is open_flag as RT_SERIAL_RX_NON_BLOCKING */ + + level = rt_hw_interrupt_disable(); + /* When open_flag is RT_SERIAL_RX_NON_BLOCKING, + * the data is retrieved directly from the ringbuffer and returned */ + recv_len = rt_ringbuffer_get(&(rx_fifo->rb), buffer, size); + + rt_hw_interrupt_enable(level); + + return recv_len; +} + +/** + * @brief Serial transmit data routines, This function will transmit + * data by using blocking_nbuf. + * @param dev The pointer of device driver structure + * @param pos Empty parameter. + * @param buffer Transmit data buffer. + * @param size Transmit data buffer length. + * @return Return the final length of data transmit. + */ +static rt_size_t _serial_fifo_tx_blocking_nbuf(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + struct rt_serial_tx_fifo *tx_fifo = RT_NULL; + + RT_ASSERT(dev != RT_NULL); + if (size == 0) return 0; + + serial = (struct rt_serial_device *)dev; + RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); + tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; + RT_ASSERT(tx_fifo != RT_NULL); + + if (rt_thread_self() == RT_NULL || (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM)) + { + /* using poll tx when the scheduler not startup or in stream mode */ + return _serial_poll_tx(dev, pos, buffer, size); + } + + /* When serial transmit in tx_blocking mode, + * if the activated mode is RT_TRUE, it will return directly */ + if (tx_fifo->activated == RT_TRUE) return 0; + + tx_fifo->activated = RT_TRUE; + /* Call the transmit interface for transmission */ + serial->ops->transmit(serial, + (rt_uint8_t *)buffer, + size, + RT_SERIAL_TX_BLOCKING); + /* Waiting for the transmission to complete */ + rt_completion_wait(&(tx_fifo->tx_cpt), RT_WAITING_FOREVER); + + return size; +} + +/** + * @brief Serial transmit data routines, This function will transmit + * data by using blocking_buf. + * @param dev The pointer of device driver structure + * @param pos Empty parameter. + * @param buffer Transmit data buffer. + * @param size Transmit data buffer length. + * @return Return the final length of data transmit. + */ +static rt_size_t _serial_fifo_tx_blocking_buf(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + struct rt_serial_tx_fifo *tx_fifo = RT_NULL; + rt_size_t length = size; + rt_size_t offset = 0; + + if (size == 0) return 0; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); + + tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; + RT_ASSERT(tx_fifo != RT_NULL); + + if (rt_thread_self() == RT_NULL || (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM)) + { + /* using poll tx when the scheduler not startup or in stream mode */ + return _serial_poll_tx(dev, pos, buffer, size); + } + /* When serial transmit in tx_blocking mode, + * if the activated mode is RT_TRUE, it will return directly */ + if (tx_fifo->activated == RT_TRUE) return 0; + tx_fifo->activated = RT_TRUE; + + while (size) + { + /* Copy one piece of data into the ringbuffer at a time + * until the length of the data is equal to size */ + tx_fifo->put_size = rt_ringbuffer_put(&(tx_fifo->rb), + (rt_uint8_t *)buffer + offset, + size); + + offset += tx_fifo->put_size; + size -= tx_fifo->put_size; + /* Call the transmit interface for transmission */ + serial->ops->transmit(serial, + (rt_uint8_t *)buffer + offset, + tx_fifo->put_size, + RT_SERIAL_TX_BLOCKING); + /* Waiting for the transmission to complete */ + rt_completion_wait(&(tx_fifo->tx_cpt), RT_WAITING_FOREVER); + } + + return length; +} + +/** + * @brief Serial transmit data routines, This function will transmit + * data by using nonblocking. + * @param dev The pointer of device driver structure + * @param pos Empty parameter. + * @param buffer Transmit data buffer. + * @param size Transmit data buffer length. + * @return Return the final length of data transmit. + */ +static rt_size_t _serial_fifo_tx_nonblocking(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + struct rt_serial_tx_fifo *tx_fifo; + rt_base_t level; + rt_size_t length; + + RT_ASSERT(dev != RT_NULL); + if (size == 0) return 0; + + serial = (struct rt_serial_device *)dev; + RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); + tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; + + level = rt_hw_interrupt_disable(); + + if (tx_fifo->activated == RT_FALSE) + { + /* When serial transmit in tx_non_blocking mode, if the activated mode is RT_FALSE, + * start copying data into the ringbuffer */ + tx_fifo->activated = RT_TRUE; + /* Copying data into the ringbuffer */ + length = rt_ringbuffer_put(&(tx_fifo->rb), buffer, size); + + rt_hw_interrupt_enable(level); + + rt_uint8_t *put_ptr = RT_NULL; + /* Get the linear length buffer from rinbuffer */ + tx_fifo->put_size = rt_serial_get_linear_buffer(&(tx_fifo->rb), &put_ptr); + /* Call the transmit interface for transmission */ + serial->ops->transmit(serial, + put_ptr, + tx_fifo->put_size, + RT_SERIAL_TX_NON_BLOCKING); + /* In tx_nonblocking mode, there is no need to call rt_completion_wait() APIs to wait + * for the rt_current_thread to resume */ + return length; + } + + /* If the activated mode is RT_FALSE, it means that serial device is transmitting, + * where only the data in the ringbuffer and there is no need to call the transmit() API. + * Note that this part of the code requires disable interrupts + * to prevent multi thread reentrant */ + + /* Copying data into the ringbuffer */ + length = rt_ringbuffer_put(&(tx_fifo->rb), buffer, size); + + rt_hw_interrupt_enable(level); + + return length; +} + + +/** + * @brief Enable serial transmit mode. + * @param dev The pointer of device driver structure + * @param rx_oflag The flag of that the serial port opens. + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_tx_enable(struct rt_device *dev, + rt_uint16_t tx_oflag) +{ + struct rt_serial_device *serial; + struct rt_serial_tx_fifo *tx_fifo = RT_NULL; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + + if (serial->config.tx_bufsz == 0) + { + /* Cannot use RT_SERIAL_TX_NON_BLOCKING when tx_bufsz is 0 */ + if (tx_oflag == RT_SERIAL_TX_NON_BLOCKING) + { + LOG_E("(%s) serial device with misconfigure: tx_bufsz = 0", + dev->parent.name); + return -RT_EINVAL; + } + +#ifndef RT_USING_DEVICE_OPS + dev->write = _serial_poll_tx; +#endif + + dev->open_flag |= RT_SERIAL_TX_BLOCKING; + return RT_EOK; + } + /* Limits the minimum value of tx_bufsz */ + if (serial->config.tx_bufsz < RT_SERIAL_TX_MINBUFSZ) + serial->config.tx_bufsz = RT_SERIAL_TX_MINBUFSZ; + + if (tx_oflag == RT_SERIAL_TX_BLOCKING) + { + /* When using RT_SERIAL_TX_BLOCKING, it is necessary to determine + * whether serial device needs to use buffer */ + rt_err_t optmode; /* The operating mode used by serial device */ + /* Call the Control() API to get the operating mode */ + optmode = serial->ops->control(serial, + RT_DEVICE_CHECK_OPTMODE, + (void *)RT_DEVICE_FLAG_TX_BLOCKING); + if (optmode == RT_SERIAL_TX_BLOCKING_BUFFER) + { + /* If use RT_SERIAL_TX_BLOCKING_BUFFER, the ringbuffer is initialized */ + tx_fifo = (struct rt_serial_tx_fifo *) rt_malloc + (sizeof(struct rt_serial_tx_fifo) + serial->config.tx_bufsz); + RT_ASSERT(tx_fifo != RT_NULL); + + rt_ringbuffer_init(&(tx_fifo->rb), + tx_fifo->buffer, + serial->config.tx_bufsz); + serial->serial_tx = tx_fifo; + +#ifndef RT_USING_DEVICE_OPS + dev->write = _serial_fifo_tx_blocking_buf; +#endif + } + else + { + /* If not use RT_SERIAL_TX_BLOCKING_BUFFER, + * the control() API is called to configure the serial device */ + tx_fifo = (struct rt_serial_tx_fifo*) rt_malloc + (sizeof(struct rt_serial_tx_fifo)); + RT_ASSERT(tx_fifo != RT_NULL); + + serial->serial_tx = tx_fifo; + +#ifndef RT_USING_DEVICE_OPS + dev->write = _serial_fifo_tx_blocking_nbuf; +#endif + + /* Call the control() API to configure the serial device by RT_SERIAL_TX_BLOCKING*/ + serial->ops->control(serial, + RT_DEVICE_CTRL_CONFIG, + (void *)RT_SERIAL_TX_BLOCKING); + } + + tx_fifo->activated = RT_FALSE; + tx_fifo->put_size = 0; + rt_completion_init(&(tx_fifo->tx_cpt)); + dev->open_flag |= RT_SERIAL_TX_BLOCKING; + + return RT_EOK; + } + /* When using RT_SERIAL_TX_NON_BLOCKING, ringbuffer needs to be initialized, + * and initialize the tx_fifo->activated value is RT_FALSE. + */ + tx_fifo = (struct rt_serial_tx_fifo *) rt_malloc + (sizeof(struct rt_serial_tx_fifo) + serial->config.tx_bufsz); + RT_ASSERT(tx_fifo != RT_NULL); + + tx_fifo->activated = RT_FALSE; + tx_fifo->put_size = 0; + rt_ringbuffer_init(&(tx_fifo->rb), + tx_fifo->buffer, + serial->config.tx_bufsz); + serial->serial_tx = tx_fifo; + +#ifndef RT_USING_DEVICE_OPS + dev->write = _serial_fifo_tx_nonblocking; +#endif + + dev->open_flag |= RT_SERIAL_TX_NON_BLOCKING; + /* Call the control() API to configure the serial device by RT_SERIAL_TX_NON_BLOCKING*/ + serial->ops->control(serial, + RT_DEVICE_CTRL_CONFIG, + (void *)RT_SERIAL_TX_NON_BLOCKING); + + return RT_EOK; +} + + +/** + * @brief Enable serial receive mode. + * @param dev The pointer of device driver structure + * @param rx_oflag The flag of that the serial port opens. + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_rx_enable(struct rt_device *dev, + rt_uint16_t rx_oflag) +{ + struct rt_serial_device *serial; + struct rt_serial_rx_fifo *rx_fifo = RT_NULL; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + + if (serial->config.rx_bufsz == 0) + { + /* Cannot use RT_SERIAL_RX_NON_BLOCKING when rx_bufsz is 0 */ + if (rx_oflag == RT_SERIAL_RX_NON_BLOCKING) + { + LOG_E("(%s) serial device with misconfigure: rx_bufsz = 0", + dev->parent.name); + return -RT_EINVAL; + } + +#ifndef RT_USING_DEVICE_OPS + dev->read = _serial_poll_rx; +#endif + + dev->open_flag |= RT_SERIAL_RX_BLOCKING; + return RT_EOK; + } + /* Limits the minimum value of rx_bufsz */ + if (serial->config.rx_bufsz < RT_SERIAL_RX_MINBUFSZ) + serial->config.rx_bufsz = RT_SERIAL_RX_MINBUFSZ; + + rx_fifo = (struct rt_serial_rx_fifo *) rt_malloc + (sizeof(struct rt_serial_rx_fifo) + serial->config.rx_bufsz); + + RT_ASSERT(rx_fifo != RT_NULL); + rt_ringbuffer_init(&(rx_fifo->rb), rx_fifo->buffer, serial->config.rx_bufsz); + + serial->serial_rx = rx_fifo; + +#ifndef RT_USING_DEVICE_OPS + dev->read = _serial_fifo_rx; +#endif + + if (rx_oflag == RT_SERIAL_RX_NON_BLOCKING) + { + dev->open_flag |= RT_SERIAL_RX_NON_BLOCKING; + /* Call the control() API to configure the serial device by RT_SERIAL_RX_NON_BLOCKING*/ + serial->ops->control(serial, + RT_DEVICE_CTRL_CONFIG, + (void *) RT_SERIAL_RX_NON_BLOCKING); + + return RT_EOK; + } + /* When using RT_SERIAL_RX_BLOCKING, rt_completion_init() and rx_cpt_index are initialized */ + rx_fifo->rx_cpt_index = 0; + rt_completion_init(&(rx_fifo->rx_cpt)); + dev->open_flag |= RT_SERIAL_RX_BLOCKING; + /* Call the control() API to configure the serial device by RT_SERIAL_RX_BLOCKING*/ + serial->ops->control(serial, + RT_DEVICE_CTRL_CONFIG, + (void *) RT_SERIAL_RX_BLOCKING); + + return RT_EOK; +} + +/** + * @brief Disable serial receive mode. + * @param dev The pointer of device driver structure + * @param rx_oflag The flag of that the serial port opens. + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_rx_disable(struct rt_device *dev, + rt_uint16_t rx_oflag) +{ + struct rt_serial_device *serial; + struct rt_serial_rx_fifo *rx_fifo; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + +#ifndef RT_USING_DEVICE_OPS + dev->read = RT_NULL; +#endif + + if (serial->serial_rx == RT_NULL) return RT_EOK; + + do + { + if (rx_oflag == RT_SERIAL_RX_NON_BLOCKING) + { + dev->open_flag &= ~ RT_SERIAL_RX_NON_BLOCKING; + serial->ops->control(serial, + RT_DEVICE_CTRL_CLR_INT, + (void *)RT_SERIAL_RX_NON_BLOCKING); + break; + } + + dev->open_flag &= ~ RT_SERIAL_RX_BLOCKING; + serial->ops->control(serial, + RT_DEVICE_CTRL_CLR_INT, + (void *)RT_SERIAL_RX_BLOCKING); + } while (0); + + rx_fifo = (struct rt_serial_rx_fifo *)serial->serial_rx; + RT_ASSERT(rx_fifo != RT_NULL); + rt_free(rx_fifo); + serial->serial_rx = RT_NULL; + + return RT_EOK; +} + +/** + * @brief Disable serial tranmit mode. + * @param dev The pointer of device driver structure + * @param rx_oflag The flag of that the serial port opens. + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_tx_disable(struct rt_device *dev, + rt_uint16_t tx_oflag) +{ + struct rt_serial_device *serial; + struct rt_serial_tx_fifo *tx_fifo; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + +#ifndef RT_USING_DEVICE_OPS + dev->write = RT_NULL; +#endif + + if (serial->serial_tx == RT_NULL) return RT_EOK; + + tx_fifo = (struct rt_serial_tx_fifo *)serial->serial_tx; + RT_ASSERT(tx_fifo != RT_NULL); + + do + { + if (tx_oflag == RT_SERIAL_TX_NON_BLOCKING) + { + dev->open_flag &= ~ RT_SERIAL_TX_NON_BLOCKING; + + serial->ops->control(serial, + RT_DEVICE_CTRL_CLR_INT, + (void *)RT_SERIAL_TX_NON_BLOCKING); + break; + } + + rt_completion_done(&(tx_fifo->tx_cpt)); + dev->open_flag &= ~ RT_SERIAL_TX_BLOCKING; + serial->ops->control(serial, + RT_DEVICE_CTRL_CLR_INT, + (void *)RT_SERIAL_TX_BLOCKING); + } while (0); + + rt_free(tx_fifo); + serial->serial_tx = RT_NULL; + + return RT_EOK; +} + +/** + * @brief Initialize the serial device. + * @param dev The pointer of device driver structure + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_init(struct rt_device *dev) +{ + rt_err_t result = RT_EOK; + struct rt_serial_device *serial; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + RT_ASSERT(serial->ops->transmit != RT_NULL); + + /* initialize rx/tx */ + serial->serial_rx = RT_NULL; + serial->serial_tx = RT_NULL; + + rt_memset(&serial->rx_notify, 0, sizeof(struct rt_device_notify)); + + /* apply configuration */ + if (serial->ops->configure) + result = serial->ops->configure(serial, &serial->config); + + return result; +} + +/** + * @brief Open the serial device. + * @param dev The pointer of device driver structure + * @param oflag The flag of that the serial port opens. + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag) +{ + struct rt_serial_device *serial; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + + /* Check that the device has been turned on */ + if ((dev->open_flag) & (15 << 12)) + { + LOG_D("(%s) serial device has already been opened, it will run in its original configuration", dev->parent.name); + return RT_EOK; + } + + LOG_D("open serial device: 0x%08x with open flag: 0x%04x", + dev, oflag); + + /* By default, the receive mode of a serial devide is RT_SERIAL_RX_NON_BLOCKING */ + if ((oflag & RT_SERIAL_RX_BLOCKING) == RT_SERIAL_RX_BLOCKING) + dev->open_flag |= RT_SERIAL_RX_BLOCKING; + else + dev->open_flag |= RT_SERIAL_RX_NON_BLOCKING; + + /* By default, the transmit mode of a serial devide is RT_SERIAL_TX_BLOCKING */ + if ((oflag & RT_SERIAL_TX_NON_BLOCKING) == RT_SERIAL_TX_NON_BLOCKING) + dev->open_flag |= RT_SERIAL_TX_NON_BLOCKING; + else + dev->open_flag |= RT_SERIAL_TX_BLOCKING; + + /* set steam flag */ + if ((oflag & RT_DEVICE_FLAG_STREAM) || + (dev->open_flag & RT_DEVICE_FLAG_STREAM)) + dev->open_flag |= RT_DEVICE_FLAG_STREAM; + + /* initialize the Rx structure according to open flag */ + if (serial->serial_rx == RT_NULL) + rt_serial_rx_enable(dev, dev->open_flag & + (RT_SERIAL_RX_BLOCKING | RT_SERIAL_RX_NON_BLOCKING)); + + /* initialize the Tx structure according to open flag */ + if (serial->serial_tx == RT_NULL) + rt_serial_tx_enable(dev, dev->open_flag & + (RT_SERIAL_TX_BLOCKING | RT_SERIAL_TX_NON_BLOCKING)); + + return RT_EOK; +} + + +/** + * @brief Close the serial device. + * @param dev The pointer of device driver structure + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_close(struct rt_device *dev) +{ + struct rt_serial_device *serial; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + + /* this device has more reference count */ + if (dev->ref_count > 1) return -RT_ERROR; + /* Disable serial receive mode. */ + rt_serial_rx_disable(dev, dev->open_flag & + (RT_SERIAL_RX_BLOCKING | RT_SERIAL_RX_NON_BLOCKING)); + /* Disable serial tranmit mode. */ + rt_serial_tx_disable(dev, dev->open_flag & + (RT_SERIAL_TX_BLOCKING | RT_SERIAL_TX_NON_BLOCKING)); + + /* Clear the callback function */ + serial->parent.rx_indicate = RT_NULL; + serial->parent.tx_complete = RT_NULL; + + /* Call the control() API to close the serial device */ + serial->ops->control(serial, RT_DEVICE_CTRL_CLOSE, RT_NULL); + dev->flag &= ~RT_DEVICE_FLAG_ACTIVATED; + + return RT_EOK; +} + +#ifdef RT_USING_POSIX_TERMIOS +struct speed_baudrate_item +{ + speed_t speed; + int baudrate; +}; + +const static struct speed_baudrate_item _tbl[] = +{ + {B2400, BAUD_RATE_2400}, + {B4800, BAUD_RATE_4800}, + {B9600, BAUD_RATE_9600}, + {B19200, BAUD_RATE_19200}, + {B38400, BAUD_RATE_38400}, + {B57600, BAUD_RATE_57600}, + {B115200, BAUD_RATE_115200}, + {B230400, BAUD_RATE_230400}, + {B460800, BAUD_RATE_460800}, + {B921600, BAUD_RATE_921600}, + {B2000000, BAUD_RATE_2000000}, + {B3000000, BAUD_RATE_3000000}, +}; + +static speed_t _get_speed(int baudrate) +{ + int index; + + for (index = 0; index < sizeof(_tbl)/sizeof(_tbl[0]); index ++) + { + if (_tbl[index].baudrate == baudrate) + return _tbl[index].speed; + } + + return B0; +} + +static int _get_baudrate(speed_t speed) +{ + int index; + + for (index = 0; index < sizeof(_tbl)/sizeof(_tbl[0]); index ++) + { + if (_tbl[index].speed == speed) + return _tbl[index].baudrate; + } + + return 0; +} + +static void _tc_flush(struct rt_serial_device *serial, int queue) +{ + rt_base_t level; + int ch = -1; + struct rt_serial_rx_fifo *rx_fifo = RT_NULL; + struct rt_device *device = RT_NULL; + + RT_ASSERT(serial != RT_NULL); + + device = &(serial->parent); + rx_fifo = (struct rt_serial_rx_fifo *) serial->serial_rx; + + switch(queue) + { + case TCIFLUSH: + case TCIOFLUSH: + RT_ASSERT(rx_fifo != RT_NULL); + + if((device->open_flag & RT_DEVICE_FLAG_INT_RX) || (device->open_flag & RT_DEVICE_FLAG_DMA_RX)) + { + RT_ASSERT(RT_NULL != rx_fifo); + level = rt_hw_interrupt_disable(); + rx_fifo->rx_cpt_index = 0; + rt_hw_interrupt_enable(level); + } + else + { + while (1) + { + ch = serial->ops->getc(serial); + if (ch == -1) break; + } + } + + break; + + case TCOFLUSH: + break; + } + +} +#endif /* RT_USING_POSIX_TERMIOS */ + +/** + * @brief Control the serial device. + * @param dev The pointer of device driver structure + * @param cmd The command value that controls the serial device + * @param args The parameter value that controls the serial device + * @return Return the status of the operation. + */ +static rt_err_t rt_serial_control(struct rt_device *dev, + int cmd, + void *args) +{ + rt_err_t ret = RT_EOK; + struct rt_serial_device *serial; + + RT_ASSERT(dev != RT_NULL); + serial = (struct rt_serial_device *)dev; + + switch (cmd) + { + case RT_DEVICE_CTRL_SUSPEND: + /* suspend device */ + dev->flag |= RT_DEVICE_FLAG_SUSPENDED; + break; + + case RT_DEVICE_CTRL_RESUME: + /* resume device */ + dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED; + break; + + case RT_DEVICE_CTRL_CONFIG: + if (args != RT_NULL) + { + struct serial_configure *pconfig = (struct serial_configure *) args; + if (((pconfig->rx_bufsz != serial->config.rx_bufsz) || (pconfig->tx_bufsz != serial->config.tx_bufsz)) + && serial->parent.ref_count) + { + /*can not change buffer size*/ + return -RT_EBUSY; + } + /* set serial configure */ + serial->config = *pconfig; + serial->ops->configure(serial, (struct serial_configure *) args); + } + + break; + case RT_DEVICE_CTRL_NOTIFY_SET: + if (args) + { + rt_memcpy(&serial->rx_notify, args, sizeof(struct rt_device_notify)); + } + break; + + case RT_DEVICE_CTRL_CONSOLE_OFLAG: + if (args) + { + *(rt_uint16_t*)args = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM; + } + break; +#ifdef RT_USING_POSIX_STDIO +#ifdef RT_USING_POSIX_TERMIOS + case TCGETA: + { + struct termios *tio = (struct termios*)args; + if (tio == RT_NULL) return -RT_EINVAL; + + tio->c_iflag = 0; + tio->c_oflag = 0; + tio->c_lflag = 0; + + /* update oflag for console device */ + if (rt_console_get_device() == dev) + tio->c_oflag = OPOST | ONLCR; + + /* set cflag */ + tio->c_cflag = 0; + if (serial->config.data_bits == DATA_BITS_5) + tio->c_cflag = CS5; + else if (serial->config.data_bits == DATA_BITS_6) + tio->c_cflag = CS6; + else if (serial->config.data_bits == DATA_BITS_7) + tio->c_cflag = CS7; + else if (serial->config.data_bits == DATA_BITS_8) + tio->c_cflag = CS8; + + if (serial->config.stop_bits == STOP_BITS_2) + tio->c_cflag |= CSTOPB; + + if (serial->config.parity == PARITY_EVEN) + tio->c_cflag |= PARENB; + else if (serial->config.parity == PARITY_ODD) + tio->c_cflag |= (PARODD | PARENB); + + if (serial->config.flowcontrol == RT_SERIAL_FLOWCONTROL_CTSRTS) + tio->c_cflag |= CRTSCTS; + + cfsetospeed(tio, _get_speed(serial->config.baud_rate)); + } + break; + + case TCSETAW: + case TCSETAF: + case TCSETA: + { + int baudrate; + struct serial_configure config; + + struct termios *tio = (struct termios*)args; + if (tio == RT_NULL) return -RT_EINVAL; + + config = serial->config; + + baudrate = _get_baudrate(cfgetospeed(tio)); + config.baud_rate = baudrate; + + switch (tio->c_cflag & CSIZE) + { + case CS5: + config.data_bits = DATA_BITS_5; + break; + case CS6: + config.data_bits = DATA_BITS_6; + break; + case CS7: + config.data_bits = DATA_BITS_7; + break; + default: + config.data_bits = DATA_BITS_8; + break; + } + + if (tio->c_cflag & CSTOPB) config.stop_bits = STOP_BITS_2; + else config.stop_bits = STOP_BITS_1; + + if (tio->c_cflag & PARENB) + { + if (tio->c_cflag & PARODD) config.parity = PARITY_ODD; + else config.parity = PARITY_EVEN; + } + else config.parity = PARITY_NONE; + + if (tio->c_cflag & CRTSCTS) config.flowcontrol = RT_SERIAL_FLOWCONTROL_CTSRTS; + else config.flowcontrol = RT_SERIAL_FLOWCONTROL_NONE; + + /* set serial configure */ + serial->config = config; + serial->ops->configure(serial, &config); + } + break; + case TCFLSH: + { + int queue = (int)args; + + _tc_flush(serial, queue); + } + + break; + case TCXONC: + break; +#endif /*RT_USING_POSIX_TERMIOS*/ + case TIOCSWINSZ: + { + struct winsize* p_winsize; + + p_winsize = (struct winsize*)args; + rt_kprintf("\x1b[8;%d;%dt", p_winsize->ws_col, p_winsize->ws_row); + } + break; + case TIOCGWINSZ: + { + struct winsize* p_winsize; + p_winsize = (struct winsize*)args; + + if(rt_thread_self() != rt_thread_find(FINSH_THREAD_NAME)) + { + /* only can be used in tshell thread; otherwise, return default size */ + p_winsize->ws_col = 80; + p_winsize->ws_row = 24; + } + else + { + #include + #define _TIO_BUFLEN 20 + char _tio_buf[_TIO_BUFLEN]; + unsigned char cnt1, cnt2, cnt3, i; + char row_s[4], col_s[4]; + char *p; + + rt_memset(_tio_buf, 0, _TIO_BUFLEN); + + /* send the command to terminal for getting the window size of the terminal */ + rt_kprintf("\033[18t"); + + /* waiting for the response from the terminal */ + i = 0; + while(i < _TIO_BUFLEN) + { + _tio_buf[i] = finsh_getchar(); + if(_tio_buf[i] != 't') + { + i ++; + } + else + { + break; + } + } + if(i == _TIO_BUFLEN) + { + /* buffer overloaded, and return default size */ + p_winsize->ws_col = 80; + p_winsize->ws_row = 24; + break; + } + + /* interpreting data eg: "\033[8;1;15t" which means row is 1 and col is 15 (unit: size of ONE character) */ + rt_memset(row_s,0,4); + rt_memset(col_s,0,4); + cnt1 = 0; + while(_tio_buf[cnt1] != ';' && cnt1 < _TIO_BUFLEN) + { + cnt1++; + } + cnt2 = ++cnt1; + while(_tio_buf[cnt2] != ';' && cnt2 < _TIO_BUFLEN) + { + cnt2++; + } + p = row_s; + while(cnt1 < cnt2) + { + *p++ = _tio_buf[cnt1++]; + } + p = col_s; + cnt2++; + cnt3 = rt_strlen(_tio_buf) - 1; + while(cnt2 < cnt3) + { + *p++ = _tio_buf[cnt2++]; + } + + /* load the window size date */ + p_winsize->ws_col = atoi(col_s); + p_winsize->ws_row = atoi(row_s); + #undef _TIO_BUFLEN + } + + p_winsize->ws_xpixel = 0;/* unused */ + p_winsize->ws_ypixel = 0;/* unused */ + } + break; + case FIONREAD: + { + rt_size_t recved = 0; + rt_base_t level; + struct rt_serial_rx_fifo * rx_fifo = (struct rt_serial_rx_fifo *) serial->serial_rx; + + level = rt_hw_interrupt_disable(); + recved = rt_ringbuffer_data_len(&(rx_fifo->rb)); + rt_hw_interrupt_enable(level); + + *(rt_size_t *)args = recved; + } + break; +#endif /* RT_USING_POSIX_STDIO */ + default : + /* control device */ + ret = serial->ops->control(serial, cmd, args); + break; + } + + return ret; +} + +#ifdef RT_USING_DEVICE_OPS +static rt_size_t rt_serial_read(struct rt_device *dev, + rt_off_t pos, + void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + + RT_ASSERT(dev != RT_NULL); + if (size == 0) return 0; + + serial = (struct rt_serial_device *)dev; + + if (serial->config.rx_bufsz) + { + return _serial_fifo_rx(dev, pos, buffer, size); + } + + return _serial_poll_rx(dev, pos, buffer, size); +} + + +static rt_size_t rt_serial_write(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct rt_serial_device *serial; + struct rt_serial_tx_fifo *tx_fifo; + + RT_ASSERT(dev != RT_NULL); + if (size == 0) return 0; + + serial = (struct rt_serial_device *)dev; + RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); + tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; + + if (serial->config.tx_bufsz == 0) + { + return _serial_poll_tx(dev, pos, buffer, size); + } + + if (dev->open_flag & RT_SERIAL_TX_BLOCKING) + { + if ((tx_fifo->rb.buffer_ptr) == RT_NULL) + { + return _serial_fifo_tx_blocking_nbuf(dev, pos, buffer, size); + } + + return _serial_fifo_tx_blocking_buf(dev, pos, buffer, size); + } + + return _serial_fifo_tx_nonblocking(dev, pos, buffer, size); +} + +const static struct rt_device_ops serial_ops = +{ + rt_serial_init, + rt_serial_open, + rt_serial_close, + rt_serial_read, + rt_serial_write, + rt_serial_control +}; +#endif + +/** + * @brief Register the serial device. + * @param serial RT-thread serial device. + * @param name The device driver's name + * @param flag The capabilities flag of device. + * @param data The device driver's data. + * @return Return the status of the operation. + */ +rt_err_t rt_hw_serial_register(struct rt_serial_device *serial, + const char *name, + rt_uint32_t flag, + void *data) +{ + rt_err_t ret; + struct rt_device *device; + RT_ASSERT(serial != RT_NULL); + + device = &(serial->parent); + + device->type = RT_Device_Class_Char; + device->rx_indicate = RT_NULL; + device->tx_complete = RT_NULL; + +#ifdef RT_USING_DEVICE_OPS + device->ops = &serial_ops; +#else + device->init = rt_serial_init; + device->open = rt_serial_open; + device->close = rt_serial_close; + device->read = RT_NULL; + device->write = RT_NULL; + device->control = rt_serial_control; +#endif + device->user_data = data; + + /* register a character device */ + ret = rt_device_register(device, name, flag); + +#ifdef RT_USING_POSIX_STDIO + /* set fops */ + device->fops = &_serial_fops; +#endif + return ret; +} + +/** + * @brief ISR for serial interrupt + * @param serial RT-thread serial device. + * @param event ISR event type. + */ +void rt_hw_serial_isr(struct rt_serial_device *serial, int event) +{ + RT_ASSERT(serial != RT_NULL); + + switch (event & 0xff) + { + /* Interrupt receive event */ + case RT_SERIAL_EVENT_RX_IND: + case RT_SERIAL_EVENT_RX_DMADONE: + { + struct rt_serial_rx_fifo *rx_fifo; + rt_size_t rx_length = 0; + rx_fifo = (struct rt_serial_rx_fifo *)serial->serial_rx; + rt_base_t level; + RT_ASSERT(rx_fifo != RT_NULL); + + /* If the event is RT_SERIAL_EVENT_RX_IND, rx_length is equal to 0 */ + rx_length = (event & (~0xff)) >> 8; + + if (rx_length) + { /* RT_SERIAL_EVENT_RX_DMADONE MODE */ + level = rt_hw_interrupt_disable(); + rt_serial_update_write_index(&(rx_fifo->rb), rx_length); + rt_hw_interrupt_enable(level); + } + + /* Get the length of the data from the ringbuffer */ + rx_length = rt_ringbuffer_data_len(&rx_fifo->rb); + if (rx_length == 0) break; + + if (serial->parent.open_flag & RT_SERIAL_RX_BLOCKING) + { + if (rx_fifo->rx_cpt_index && rx_length >= rx_fifo->rx_cpt_index ) + { + rx_fifo->rx_cpt_index = 0; + rt_completion_done(&(rx_fifo->rx_cpt)); + } + } + /* Trigger the receiving completion callback */ + if (serial->parent.rx_indicate != RT_NULL) + serial->parent.rx_indicate(&(serial->parent), rx_length); + + if (serial->rx_notify.notify) + { + serial->rx_notify.notify(serial->rx_notify.dev); + } + break; + } + + /* Interrupt transmit event */ + case RT_SERIAL_EVENT_TX_DONE: + { + struct rt_serial_tx_fifo *tx_fifo; + rt_size_t tx_length = 0; + tx_fifo = (struct rt_serial_tx_fifo *)serial->serial_tx; + RT_ASSERT(tx_fifo != RT_NULL); + + /* Get the length of the data from the ringbuffer */ + tx_length = rt_ringbuffer_data_len(&tx_fifo->rb); + /* If there is no data in tx_ringbuffer, + * then the transmit completion callback is triggered*/ + if (tx_length == 0) + { + tx_fifo->activated = RT_FALSE; + /* Trigger the transmit completion callback */ + if (serial->parent.tx_complete != RT_NULL) + serial->parent.tx_complete(&serial->parent, RT_NULL); + + if (serial->parent.open_flag & RT_SERIAL_TX_BLOCKING) + rt_completion_done(&(tx_fifo->tx_cpt)); + + break; + } + + /* Call the transmit interface for transmission again */ + /* Note that in interrupt mode, tx_fifo->buffer and tx_length + * are inactive parameters */ + serial->ops->transmit(serial, + tx_fifo->buffer, + tx_length, + serial->parent.open_flag & ( \ + RT_SERIAL_TX_BLOCKING | \ + RT_SERIAL_TX_NON_BLOCKING)); + break; + } + + case RT_SERIAL_EVENT_TX_DMADONE: + { + struct rt_serial_tx_fifo *tx_fifo; + tx_fifo = (struct rt_serial_tx_fifo *)serial->serial_tx; + RT_ASSERT(tx_fifo != RT_NULL); + + tx_fifo->activated = RT_FALSE; + + /* Trigger the transmit completion callback */ + if (serial->parent.tx_complete != RT_NULL) + serial->parent.tx_complete(&serial->parent, RT_NULL); + + if (serial->parent.open_flag & RT_SERIAL_TX_BLOCKING) + { + rt_completion_done(&(tx_fifo->tx_cpt)); + break; + } + + rt_serial_update_read_index(&tx_fifo->rb, tx_fifo->put_size); + /* Get the length of the data from the ringbuffer. + * If there is some data in tx_ringbuffer, + * then call the transmit interface for transmission again */ + if (rt_ringbuffer_data_len(&tx_fifo->rb)) + { + tx_fifo->activated = RT_TRUE; + + rt_uint8_t *put_ptr = RT_NULL; + /* Get the linear length buffer from rinbuffer */ + tx_fifo->put_size = rt_serial_get_linear_buffer(&(tx_fifo->rb), &put_ptr); + /* Call the transmit interface for transmission again */ + serial->ops->transmit(serial, + put_ptr, + tx_fifo->put_size, + RT_SERIAL_TX_NON_BLOCKING); + } + + break; + } + + default: + break; + } +} diff --git a/components/drivers/src/dataqueue.c b/components/drivers/src/dataqueue.c index 3f481a308e..bef770264a 100644 --- a/components/drivers/src/dataqueue.c +++ b/components/drivers/src/dataqueue.c @@ -264,7 +264,7 @@ __exit: } RTM_EXPORT(rt_data_queue_pop); -rt_err_t rt_data_queue_peak(struct rt_data_queue *queue, +rt_err_t rt_data_queue_peek(struct rt_data_queue *queue, const void** data_ptr, rt_size_t *size) { @@ -287,7 +287,7 @@ rt_err_t rt_data_queue_peak(struct rt_data_queue *queue, return RT_EOK; } -RTM_EXPORT(rt_data_queue_peak); +RTM_EXPORT(rt_data_queue_peek); void rt_data_queue_reset(struct rt_data_queue *queue) { diff --git a/components/drivers/touch/touch.c b/components/drivers/touch/touch.c index 80ba6378bb..2b292e3ec0 100644 --- a/components/drivers/touch/touch.c +++ b/components/drivers/touch/touch.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -16,12 +16,9 @@ #include /* ISR for touch interrupt */ -static void irq_callback(void *args) +void rt_hw_touch_isr(rt_touch_t touch) { - rt_touch_t touch; - - touch = (rt_touch_t)args; - + RT_ASSERT(touch); if (touch->parent.rx_indicate == RT_NULL) { return; @@ -35,9 +32,17 @@ static void irq_callback(void *args) touch->parent.rx_indicate(&touch->parent, 1); } +#ifdef RT_TOUCH_PIN_IRQ +static void touch_irq_callback(void *param) +{ + rt_hw_touch_isr((rt_touch_t)param); +} +#endif + /* touch interrupt initialization function */ static rt_err_t rt_touch_irq_init(rt_touch_t touch) { +#ifdef RT_TOUCH_PIN_IRQ if (touch->config.irq_pin.pin == RT_PIN_NONE) { return -RT_EINVAL; @@ -47,18 +52,19 @@ static rt_err_t rt_touch_irq_init(rt_touch_t touch) if (touch->config.irq_pin.mode == PIN_MODE_INPUT_PULLDOWN) { - rt_pin_attach_irq(touch->config.irq_pin.pin, PIN_IRQ_MODE_RISING, irq_callback, (void *)touch); + rt_pin_attach_irq(touch->config.irq_pin.pin, PIN_IRQ_MODE_RISING, touch_irq_callback, (void *)touch); } else if (touch->config.irq_pin.mode == PIN_MODE_INPUT_PULLUP) { - rt_pin_attach_irq(touch->config.irq_pin.pin, PIN_IRQ_MODE_FALLING, irq_callback, (void *)touch); + rt_pin_attach_irq(touch->config.irq_pin.pin, PIN_IRQ_MODE_FALLING, touch_irq_callback, (void *)touch); } else if (touch->config.irq_pin.mode == PIN_MODE_INPUT) { - rt_pin_attach_irq(touch->config.irq_pin.pin, PIN_IRQ_MODE_RISING_FALLING, irq_callback, (void *)touch); + rt_pin_attach_irq(touch->config.irq_pin.pin, PIN_IRQ_MODE_RISING_FALLING, touch_irq_callback, (void *)touch); } rt_pin_irq_enable(touch->config.irq_pin.pin, PIN_IRQ_ENABLE); +#endif return RT_EOK; } @@ -66,19 +72,27 @@ static rt_err_t rt_touch_irq_init(rt_touch_t touch) /* touch interrupt enable */ static void rt_touch_irq_enable(rt_touch_t touch) { +#ifdef RT_TOUCH_PIN_IRQ if (touch->config.irq_pin.pin != RT_PIN_NONE) { rt_pin_irq_enable(touch->config.irq_pin.pin, RT_TRUE); } +#else + touch->ops->touch_control(touch, RT_TOUCH_CTRL_ENABLE_INT, RT_NULL); +#endif } /* touch interrupt disable */ static void rt_touch_irq_disable(rt_touch_t touch) { +#ifdef RT_TOUCH_PIN_IRQ if (touch->config.irq_pin.pin != RT_PIN_NONE) { rt_pin_irq_enable(touch->config.irq_pin.pin, RT_FALSE); } +#else + touch->ops->touch_control(touch, RT_TOUCH_CTRL_DISABLE_INT, RT_NULL); +#endif } static rt_err_t rt_touch_open(rt_device_t dev, rt_uint16_t oflag) @@ -134,28 +148,6 @@ static rt_err_t rt_touch_control(rt_device_t dev, int cmd, void *args) switch (cmd) { - case RT_TOUCH_CTRL_GET_ID: - if (args) - { - result = touch->ops->touch_control(touch, RT_TOUCH_CTRL_GET_ID, args); - } - else - { - result = -RT_ERROR; - } - - break; - case RT_TOUCH_CTRL_GET_INFO: - if (args) - { - result = touch->ops->touch_control(touch, RT_TOUCH_CTRL_GET_INFO, args); - } - else - { - result = -RT_ERROR; - } - - break; case RT_TOUCH_CTRL_SET_MODE: result = touch->ops->touch_control(touch, RT_TOUCH_CTRL_SET_MODE, args); @@ -196,8 +188,11 @@ static rt_err_t rt_touch_control(rt_device_t dev, int cmd, void *args) case RT_TOUCH_CTRL_ENABLE_INT: rt_touch_irq_enable(touch); break; + + case RT_TOUCH_CTRL_GET_ID: + case RT_TOUCH_CTRL_GET_INFO: default: - return -RT_ERROR; + return touch->ops->touch_control(touch, cmd, args); } return result; @@ -223,7 +218,7 @@ int rt_hw_touch_register(rt_touch_t touch, rt_uint32_t flag, void *data) { - rt_int8_t result; + rt_err_t result; rt_device_t device; RT_ASSERT(touch != RT_NULL); diff --git a/components/drivers/touch/touch.h b/components/drivers/touch/touch.h index f9a3abb23f..ed920c0121 100644 --- a/components/drivers/touch/touch.h +++ b/components/drivers/touch/touch.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2018, RT-Thread Development Team + * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -8,104 +8,5 @@ * 2019-05-20 tyustli the first version */ -#ifndef __TOUCH_H__ -#define __TOUCH_H__ - #include #include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef RT_USING_RTC -#define rt_touch_get_ts() time(RT_NULL) /* API for the touch to get the timestamp */ -#else -#define rt_touch_get_ts() rt_tick_get() /* API for the touch to get the timestamp */ -#endif - -#define RT_PIN_NONE 0xFFFF /* RT PIN NONE */ - -/* Touch vendor types */ -#define RT_TOUCH_VENDOR_UNKNOWN (0) /* unknown */ -#define RT_TOUCH_VENDOR_GT (1) /* GTxx series */ -#define RT_TOUCH_VENDOR_FT (2) /* FTxx series */ - -/* Touch ic type*/ -#define RT_TOUCH_TYPE_NONE (0) /* touch ic none */ -#define RT_TOUCH_TYPE_CAPACITANCE (1) /* capacitance ic */ -#define RT_TOUCH_TYPE_RESISTANCE (2) /* resistance ic */ - -/* Touch control cmd types */ -#define RT_TOUCH_CTRL_GET_ID (0) /* Get device id */ -#define RT_TOUCH_CTRL_GET_INFO (1) /* Get touch info */ -#define RT_TOUCH_CTRL_SET_MODE (2) /* Set touch's work mode. ex. RT_TOUCH_MODE_POLLING,RT_TOUCH_MODE_INT */ -#define RT_TOUCH_CTRL_SET_X_RANGE (3) /* Set x coordinate range */ -#define RT_TOUCH_CTRL_SET_Y_RANGE (4) /* Set y coordinate range */ -#define RT_TOUCH_CTRL_SET_X_TO_Y (5) /* Set X Y coordinate exchange */ -#define RT_TOUCH_CTRL_DISABLE_INT (6) /* Disable interrupt */ -#define RT_TOUCH_CTRL_ENABLE_INT (7) /* Enable interrupt */ -#define RT_TOUCH_CTRL_POWER_ON (8) /* Touch Power On */ -#define RT_TOUCH_CTRL_POWER_OFF (9) /* Touch Power Off */ -#define RT_TOUCH_CTRL_GET_STATUS (10) /* Get Touch Power Status */ - -/* Touch event */ -#define RT_TOUCH_EVENT_NONE (0) /* Touch none */ -#define RT_TOUCH_EVENT_UP (1) /* Touch up event */ -#define RT_TOUCH_EVENT_DOWN (2) /* Touch down event */ -#define RT_TOUCH_EVENT_MOVE (3) /* Touch move event */ - -struct rt_touch_info -{ - rt_uint8_t type; /* The touch type */ - rt_uint8_t vendor; /* Vendor of touchs */ - rt_uint8_t point_num; /* Support point num */ - rt_int32_t range_x; /* X coordinate range */ - rt_int32_t range_y; /* Y coordinate range */ -}; - -struct rt_touch_config -{ - struct rt_device_pin_mode irq_pin; /* Interrupt pin, The purpose of this pin is to notification read data */ - char *dev_name; /* The name of the communication device */ - void *user_data; -}; - -typedef struct rt_touch_device *rt_touch_t; -struct rt_touch_device -{ - struct rt_device parent; /* The standard device */ - struct rt_touch_info info; /* The touch info data */ - struct rt_touch_config config; /* The touch config data */ - - const struct rt_touch_ops *ops; /* The touch ops */ - rt_err_t (*irq_handle)(rt_touch_t touch); /* Called when an interrupt is generated, registered by the driver */ -}; - -struct rt_touch_data -{ - rt_uint8_t event; /* The touch event of the data */ - rt_uint8_t track_id; /* Track id of point */ - rt_uint8_t width; /* Point of width */ - rt_uint16_t x_coordinate; /* Point of x coordinate */ - rt_uint16_t y_coordinate; /* Point of y coordinate */ - rt_tick_t timestamp; /* The timestamp when the data was received */ -}; - -struct rt_touch_ops -{ - rt_size_t (*touch_readpoint)(struct rt_touch_device *touch, void *buf, rt_size_t touch_num); - rt_err_t (*touch_control)(struct rt_touch_device *touch, int cmd, void *arg); -}; - -int rt_hw_touch_register(rt_touch_t touch, - const char *name, - rt_uint32_t flag, - void *data); - -#ifdef __cplusplus -} -#endif - -#endif /* __TOUCH_H__ */ diff --git a/components/fal/Kconfig b/components/fal/Kconfig new file mode 100644 index 0000000000..4eb967b937 --- /dev/null +++ b/components/fal/Kconfig @@ -0,0 +1,55 @@ + +# Kconfig file for package fal +menuconfig RT_USING_FAL + bool "FAL: flash abstraction layer" + default n + +if RT_USING_FAL + config FAL_DEBUG_CONFIG + bool "Enable debug log output" + default y + + config FAL_DEBUG + int + default 1 if FAL_DEBUG_CONFIG + default 0 + + config FAL_PART_HAS_TABLE_CFG + bool "FAL partition table config has defined on 'fal_cfg.h'" + default y + help + If defined partition table on 'fal_cfg.h' please enable this option. + When this option is disable, it will auto find and load the partition table + on a specified location in flash partition. + + if !FAL_PART_HAS_TABLE_CFG + + config FAL_PART_TABLE_FLASH_DEV_NAME + string "The flash device which saving partition table" + default "onchip" + help + It will auto find the partition table on this flash device. + + config FAL_PART_TABLE_END_OFFSET + int "The patition table end address relative to flash device offset." + default 65536 + help + The auto find and load the partition table process is forward from this + offset address on flash. + + endif + + config FAL_USING_SFUD_PORT + bool "FAL uses SFUD drivers" + default n + help + The fal_flash_sfud_port.c in the samples\porting directory will be used. + + if FAL_USING_SFUD_PORT + config FAL_USING_NOR_FLASH_DEV_NAME + string "The name of the device used by FAL" + default "norflash0" + endif + +endif + diff --git a/components/fal/SConscript b/components/fal/SConscript new file mode 100644 index 0000000000..0d0835abdb --- /dev/null +++ b/components/fal/SConscript @@ -0,0 +1,14 @@ + +from building import * +import rtconfig + +cwd = GetCurrentDir() +src = Glob('src/*.c') +CPPPATH = [cwd + '/inc'] + +if GetDepend(['FAL_USING_SFUD_PORT']): + src += Glob('samples/porting/fal_flash_sfud_port.c') + +group = DefineGroup('Fal', src, depend = ['RT_USING_FAL'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/fal/docs/fal_api.md b/components/fal/docs/fal_api.md new file mode 100644 index 0000000000..05d5ea0ce0 --- /dev/null +++ b/components/fal/docs/fal_api.md @@ -0,0 +1,145 @@ +# FAL API + +## 查找 Flash 设备 + +```C +const struct fal_flash_dev *fal_flash_device_find(const char *name) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| name | Flash 设备名称 | +| return | 如果查找成功,将返回 Flash 设备对象,查找失败返回 NULL | + +## 查找 Flash 分区 + +```C +const struct fal_partition *fal_partition_find(const char *name) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| name | Flash 分区名称 | +| return | 如果查找成功,将返回 Flash 分区对象,查找失败返回 NULL | + +## 获取分区表 + +```C +const struct fal_partition *fal_get_partition_table(size_t *len) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| len | 分区表的长度 | +| return | 分区表 | + +## 临时设置分区表 + +FAL 初始化时会自动装载默认分区表。使用该设置将临时修改分区表,重启后会 **丢失** 该设置 + +```C +void fal_set_partition_table_temp(struct fal_partition *table, size_t len) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| table | 分区表 | +| len | 分区表的长度 | + +## 从分区读取数据 + +```C +int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| part | 分区对象 | +| addr | 相对分区的偏移地址 | +| buf | 存放待读取数据的缓冲区 | +| size | 待读取数据的大小 | +| return | 返回实际读取的数据大小 | + +## 往分区写入数据 + +```C +int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| part | 分区对象 | +| addr | 相对分区的偏移地址 | +| buf | 存放待写入数据的缓冲区 | +| size | 待写入数据的大小 | +| return | 返回实际写入的数据大小 | + +## 擦除分区数据 + +```C +int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| part | 分区对象 | +| addr | 相对分区的偏移地址 | +| size | 擦除区域的大小 | +| return | 返回实际擦除的区域大小 | + +## 擦除整个分区数据 + +```C +int fal_partition_erase_all(const struct fal_partition *part) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| part | 分区对象 | +| return | 返回实际擦除的区域大小 | + +## 打印分区表 + +```c +void fal_show_part_table(void) +``` + +## 创建块设备 + +该函数可以根据指定的分区名称,创建对应的块设备,以便于在指定的分区上挂载文件系统 + +```C +struct rt_device *fal_blk_device_create(const char *parition_name) +``` + +| 参数 | 描述 | +| :----- | :----------------------- | +| parition_name | 分区名称 | +| return | 创建成功,则返回对应的块设备,失败返回空 | + +## 创建 MTD Nor Flash 设备 + +该函数可以根据指定的分区名称,创建对应的 MTD Nor Flash 设备,以便于在指定的分区上挂载文件系统 + +```C +struct rt_device *fal_mtd_nor_device_create(const char *parition_name) +``` + +| 参数 | 描述 | +| :------------ | :---------------------------------------------------- | +| parition_name | 分区名称 | +| return | 创建成功,则返回对应的 MTD Nor Flash 设备,失败返回空 | + +## 创建字符设备 + +该函数可以根据指定的分区名称,创建对应的字符设备,以便于通过 deivice 接口或 devfs 接口操作分区,开启了 POSIX 后,还可以通过 open/read/write 函数操作分区。 + +```C +struct rt_device *fal_char_device_create(const char *parition_name) +``` + +| 参数 | 描述 | +| :------------ | :----------------------------------------- | +| parition_name | 分区名称 | +| return | 创建成功,则返回对应的字符设备,失败返回空 | + diff --git a/components/fal/docs/fal_api_en.md b/components/fal/docs/fal_api_en.md new file mode 100644 index 0000000000..df4b011679 --- /dev/null +++ b/components/fal/docs/fal_api_en.md @@ -0,0 +1,144 @@ +# FAL API + +## Find Flash device + +```C +const struct fal_flash_dev *fal_flash_device_find(const char *name) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| name | Flash device name | +| return | If the search is successful, the Flash device object will be returned, and if the search fails, it will return NULL | + +## Find Flash Partition + +```C +const struct fal_partition *fal_partition_find(const char *name) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| name | Flash partition name | +| return | If the search is successful, the Flash partition object will be returned, and if the search fails, it will return NULL | + +## Get the partition table + +```C +const struct fal_partition *fal_get_partition_table(size_t *len) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| len | The length of the partition table | +| return | Partition table | + +## Temporarily set the partition table + +The default partition table will be automatically loaded when FAL is initialized. Using this setting will temporarily modify the partition table and will **lost** this setting after restarting + +```C +void fal_set_partition_table_temp(struct fal_partition *table, size_t len) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| table | Partition table | +| len | Length of the partition table | + +## Read data from partition + +```C +int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| part | Partition object | +| addr | Relative partition offset address | +| buf | Buffer to store the data to be read | +| size | The size of the data to be read | +| return | Return the actual read data size | + +## Write data to partition + +```C +int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| part | Partition object | +| addr | Relative partition offset address | +| buf | Buffer to store data to be written | +| size | The size of the data to be written | +| return | Return the actual written data size | + +## Erase partition data + +```C +int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| part | Partition object | +| addr | Relative partition offset address | +| size | The size of the erased area | +| return | Return the actual erased area size | + +## Erase the entire partition data + +```C +int fal_partition_erase_all(const struct fal_partition *part) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| part | Partition object | +| return | Return the actual erased area size | + +## Print partition table + +```c +void fal_show_part_table(void) +``` + +## Create block device + +This function can create the corresponding block device according to the specified partition name, so as to mount the file system on the specified partition + +```C +struct rt_device *fal_blk_device_create(const char *parition_name) +``` + +| Parameters | Description | +| :----- | :----------------------- | +| parition_name | partition name | +| return | If the creation is successful, the corresponding block device will be returned, and if it fails, empty | + +## Create MTD Nor Flash device + +This function can create the corresponding MTD Nor Flash device according to the specified partition name, so as to mount the file system on the specified partition + +```C +struct rt_device *fal_mtd_nor_device_create(const char *parition_name) +``` + +| Parameters | Description | +| :------------ | :---------------------------------- ------------------ | +| parition_name | Partition name | +| return | If the creation is successful, the corresponding MTD Nor Flash device will be returned, otherwise empty | + +## Create a character device + +This function can create the corresponding character device according to the specified partition name to facilitate the operation of the partition through the deivice interface or the devfs interface. After POSIX is turned on, the partition can also be operated through the open/read/write function. + +```C +struct rt_device *fal_char_device_create(const char *parition_name) +``` + +| Parameters | Description | +| :------------ | :---------------------------------- ------- | +| parition_name | partition name | +| return | If the creation is successful, the corresponding character device will be returned, otherwise empty | \ No newline at end of file diff --git a/components/fal/docs/figures/fal-api-en.png b/components/fal/docs/figures/fal-api-en.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef1bd16d4147568bec72f20f5d06e87469b9e93 GIT binary patch literal 55328 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 IzW@Bc01zhVy8r+H literal 0 HcmV?d00001 diff --git a/components/fal/docs/figures/fal-api.png b/components/fal/docs/figures/fal-api.png new file mode 100644 index 0000000000000000000000000000000000000000..5885990fc30afe1e0bd76f100bcb514c4a627b85 GIT binary patch literal 47111 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 literal 0 HcmV?d00001 diff --git a/components/fal/docs/figures/fal-port-en.png b/components/fal/docs/figures/fal-port-en.png new file mode 100644 index 0000000000000000000000000000000000000000..311e7ed42bc157b37b29799167111049898761c0 GIT binary patch literal 24848 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

BFY^uUs-*c{`iwR0V*vR%K4;Y3!@x!+3gWxD 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 literal 0 HcmV?d00001 diff --git a/components/fal/docs/figures/fal-port.png b/components/fal/docs/figures/fal-port.png new file mode 100644 index 0000000000000000000000000000000000000000..35c1557a7afe5c517c59d31b8cf7247740a6b612 GIT binary patch literal 24465 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%0A#pR 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 G2~7Zk?}<(T literal 0 HcmV?d00001 diff --git a/components/fal/docs/figures/fal_framework.png b/components/fal/docs/figures/fal_framework.png new file mode 100644 index 0000000000000000000000000000000000000000..faceee59492ab9ef8f58b74d1aa9a90511d9c21b GIT binary patch literal 33974 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 +#include +#include "fal_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * FAL (Flash Abstraction Layer) initialization. + * It will initialize all flash device and all flash partition. + * + * @return >= 0: partitions total number + */ +int fal_init(void); + +/* =============== flash device operator API =============== */ +/** + * find flash device by name + * + * @param name flash device name + * + * @return != NULL: flash device + * NULL: not found + */ +const struct fal_flash_dev *fal_flash_device_find(const char *name); + +/* =============== partition operator API =============== */ +/** + * find the partition by name + * + * @param name partition name + * + * @return != NULL: partition + * NULL: not found + */ +const struct fal_partition *fal_partition_find(const char *name); + +/** + * get the partition table + * + * @param len return the partition table length + * + * @return partition table + */ +const struct fal_partition *fal_get_partition_table(size_t *len); + +/** + * set partition table temporarily + * This setting will modify the partition table temporarily, the setting will be lost after restart. + * + * @param table partition table + * @param len partition table length + */ +void fal_set_partition_table_temp(struct fal_partition *table, size_t len); + +/** + * read data from partition + * + * @param part partition + * @param addr relative address for partition + * @param buf read buffer + * @param size read size + * + * @return >= 0: successful read data size + * -1: error + */ +int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size); + +/** + * write data to partition + * + * @param part partition + * @param addr relative address for partition + * @param buf write buffer + * @param size write size + * + * @return >= 0: successful write data size + * -1: error + */ +int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size); + +/** + * erase partition data + * + * @param part partition + * @param addr relative address for partition + * @param size erase size + * + * @return >= 0: successful erased data size + * -1: error + */ +int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size); + +/** + * erase partition all data + * + * @param part partition + * + * @return >= 0: successful erased data size + * -1: error + */ +int fal_partition_erase_all(const struct fal_partition *part); + +/** + * print the partition table + */ +void fal_show_part_table(void); + +/* =============== API provided to RT-Thread =============== */ +/** + * create RT-Thread block device by specified partition + * + * @param parition_name partition name + * + * @return != NULL: created block device + * NULL: created failed + */ +struct rt_device *fal_blk_device_create(const char *parition_name); + +#if defined(RT_USING_MTD_NOR) +/** + * create RT-Thread MTD NOR device by specified partition + * + * @param parition_name partition name + * + * @return != NULL: created MTD NOR device + * NULL: created failed + */ +struct rt_device *fal_mtd_nor_device_create(const char *parition_name); +#endif /* defined(RT_USING_MTD_NOR) */ + +/** + * create RT-Thread char device by specified partition + * + * @param parition_name partition name + * + * @return != NULL: created char device + * NULL: created failed + */ +struct rt_device *fal_char_device_create(const char *parition_name); + +#ifdef __cplusplus +} +#endif + +#endif /* _FAL_H_ */ diff --git a/components/fal/inc/fal_def.h b/components/fal/inc/fal_def.h new file mode 100644 index 0000000000..32af32a057 --- /dev/null +++ b/components/fal/inc/fal_def.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-05-17 armink the first version + */ + +#ifndef _FAL_DEF_H_ +#define _FAL_DEF_H_ + +#include +#include +#include + +#define FAL_PRINTF rt_kprintf +#define FAL_MALLOC rt_malloc +#define FAL_CALLOC rt_calloc +#define FAL_REALLOC rt_realloc +#define FAL_FREE rt_free + +#ifndef FAL_DEBUG +#define FAL_DEBUG 0 +#endif + +#if FAL_DEBUG +#ifdef assert +#undef assert +#endif +#define assert(EXPR) \ +if (!(EXPR)) \ +{ \ + FAL_PRINTF("(%s) has assert failed at %s.\n", #EXPR, __FUNCTION__); \ + while (1); \ +} + +/* debug level log */ +#ifdef log_d +#undef log_d +#endif +#define log_d(...) FAL_PRINTF("[D/FAL] (%s:%d) ", __FUNCTION__, __LINE__); FAL_PRINTF(__VA_ARGS__);FAL_PRINTF("\n") + +#else + +#ifdef assert +#undef assert +#endif +#define assert(EXPR) ((void)0); + +/* debug level log */ +#ifdef log_d +#undef log_d +#endif +#define log_d(...) +#endif /* FAL_DEBUG */ + +/* error level log */ +#ifdef log_e +#undef log_e +#endif +#define log_e(...) FAL_PRINTF("\033[31;22m[E/FAL] (%s:%d) ", __FUNCTION__, __LINE__);FAL_PRINTF(__VA_ARGS__);FAL_PRINTF("\033[0m\n") + +/* info level log */ +#ifdef log_i +#undef log_i +#endif +#define log_i(...) FAL_PRINTF("\033[32;22m[I/FAL] "); FAL_PRINTF(__VA_ARGS__);FAL_PRINTF("\033[0m\n") + +/* FAL flash and partition device name max length */ +#ifndef FAL_DEV_NAME_MAX +#define FAL_DEV_NAME_MAX 24 +#endif + +struct fal_flash_dev +{ + char name[FAL_DEV_NAME_MAX]; + + /* flash device start address and len */ + uint32_t addr; + size_t len; + /* the block size in the flash for erase minimum granularity */ + size_t blk_size; + + struct + { + int (*init)(void); + int (*read)(long offset, uint8_t *buf, size_t size); + int (*write)(long offset, const uint8_t *buf, size_t size); + int (*erase)(long offset, size_t size); + } ops; + + /* write minimum granularity, unit: bit. + 1(nor flash)/ 8(stm32f2/f4)/ 32(stm32f1)/ 64(stm32l4) + 0 will not take effect. */ + size_t write_gran; +}; +typedef struct fal_flash_dev *fal_flash_dev_t; + +/** + * FAL partition + */ +struct fal_partition +{ + uint32_t magic_word; + + /* partition name */ + char name[FAL_DEV_NAME_MAX]; + /* flash device name for partition */ + char flash_name[FAL_DEV_NAME_MAX]; + + /* partition offset address on flash device */ + long offset; + size_t len; + + uint32_t reserved; +}; +typedef struct fal_partition *fal_partition_t; + +#endif /* _FAL_DEF_H_ */ diff --git a/components/fal/samples/README.md b/components/fal/samples/README.md new file mode 100644 index 0000000000..fcbb58a538 --- /dev/null +++ b/components/fal/samples/README.md @@ -0,0 +1,4 @@ +| 文件夹 | 说明 | +| :------ | :----------------------- | +| porting | 移植相关的示例代码及文档 | + diff --git a/components/fal/samples/porting/README.md b/components/fal/samples/porting/README.md new file mode 100644 index 0000000000..36c04e1843 --- /dev/null +++ b/components/fal/samples/porting/README.md @@ -0,0 +1,108 @@ +# Flash 设备及分区移植示例 + +本示例主要演示 Flash 设备及分区相关的移植。 + +## 1、Flash 设备 + +在定义 Flash 设备表前,需要先定义 Flash 设备,参考 [`fal_flash_sfud_port.c`](fal_flash_sfud_port.c) (基于 [SFUD](https://github.com/armink/SFUD) 万能 SPI Flash 驱动的 Flash 设备)与 [`fal_flash_stm32f2_port.c`](fal_flash_stm32f2_port.c) (STM32F2 片内 Flash)这两个文件。这里简介下 `fal_flash_stm32f2_port.c` 里的代码实现。 + +### 1.1 定义 Flash 设备 + +针对 Flash 的不同操作,这里定义了如下几个操作函数: + +- `static int init(void)`:**可选** 的初始化操作 + +- `static int read(long offset, uint8_t *buf, size_t size)`:读取操作 + +|参数 |描述| +|:----- |:----| +|offset |读取数据的 Flash 偏移地址| +|buf |存放待读取数据的缓冲区| +|size |待读取数据的大小| +|return |返回实际读取的数据大小| + +- `static int write(long offset, const uint8_t *buf, size_t size)` :写入操作 + +| 参数 | 描述 | +| :----- | :------------------------ | +| offset | 写入数据的 Flash 偏移地址 | +| buf | 存放待写入数据的缓冲区 | +| size | 待写入数据的大小 | +| return | 返回实际写入的数据大小 | + +- `static int erase(long offset, size_t size)` :擦除操作 + +| 参数 | 描述 | +| :----- | :------------------------ | +| offset | 擦除区域的 Flash 偏移地址 | +| size | 擦除区域的大小 | +| return | 返回实际擦除的区域大小 | + +用户需要根据自己的 Flash 情况分别实现这些操作函数。在文件最底部定义了具体的 Flash 设备对象(stm32f2_onchip_flash): + +`const struct fal_flash_dev stm32f2_onchip_flash = { "stm32_onchip", 0x08000000, 1024*1024, 128*1024, {init, read, write, erase} };` + +- `"stm32_onchip"` : Flash 设备的名字 +- 0x08000000: 对 Flash 操作的起始地址 +- 1024*1024:Flash 的总大小(1MB) +- 128*1024:Flash 块/扇区大小(因为 STM32F2 各块大小不均匀,所以擦除粒度为最大块的大小:128K) +- {init, read, write, erase} }:Flash 的操作函数。 如果没有 init 初始化过程,第一个操作函数位置可以置空。 + +### 1.2 定义 Flash 设备表 + +Flash 设备表定义在 `fal_cfg.h` 头文件中,定义分区表前需 **新建 `fal_cfg.h` 文件** 。 + +参考 [示例文件 samples/porting/fal_cfg.h](samples/porting/fal_cfg.h) 或如下代码: + +```c +/* ===================== Flash device Configuration ========================= */ +extern const struct fal_flash_dev stm32f2_onchip_flash; +extern struct fal_flash_dev nor_flash0; + +/* flash device table */ +#define FAL_FLASH_DEV_TABLE \ +{ \ + &stm32f2_onchip_flash, \ + &nor_flash0, \ +} +``` + +Flash 设备表中,有两个 Flash 对象,一个为 STM32F2 的片内 Flash ,一个为片外的 Nor Flash。 + +## 2、Flash 分区 + +Flash 分区基于 Flash 设备,每个 Flash 设备又可以有 N 个分区,这些分区的集合就是分区表。在配置分区表前,务必保证已定义好 Flash 设备及设备表。 + +分区表也定义在 `fal_cfg.h` 头文件中。参考 [示例文件 samples/porting/fal_cfg.h](samples/porting/fal_cfg.h) 或如下代码: + +```C +#define NOR_FLASH_DEV_NAME "norflash0" +/* ====================== Partition Configuration ========================== */ +#ifdef FAL_PART_HAS_TABLE_CFG +/* partition table */ +#define FAL_PART_TABLE \ +{ \ + {FAL_PART_MAGIC_WORD, "bl", "stm32_onchip", 0, 64*1024, 0}, \ + {FAL_PART_MAGIC_WORD, "app", "stm32_onchip", 64*1024, 704*1024, 0}, \ + {FAL_PART_MAGIC_WORD, "easyflash", NOR_FLASH_DEV_NAME, 0, 1024*1024, 0}, \ + {FAL_PART_MAGIC_WORD, "download", NOR_FLASH_DEV_NAME, 1024*1024, 1024*1024, 0}, \ +} +#endif /* FAL_PART_HAS_TABLE_CFG */ +``` + +上面这个分区表详细描述信息如下: + +| 分区名 | Flash 设备名 | 偏移地址 | 大小 | 说明 | +| :---------- | :------------- | :-------- | :---- | :----------------- | +| "bl" | "stm32_onchip" | 0 | 64KB | 引导程序 | +| "app" | "stm32_onchip" | 64*1024 | 704KB | 应用程序 | +| "easyflash" | "norflash0" | 0 | 1MB | EasyFlash 参数存储 | +| "download" | "norflash0" | 1024*1024 | 1MB | OTA 下载区 | + +用户需要修改的分区参数包括:分区名称、关联的 Flash 设备名、偏移地址(相对 Flash 设备内部)、大小,需要注意以下几点: + +- 分区名保证 **不能重复** +- 关联的 Flash 设备 **务必已经在 Flash 设备表中定义好** ,并且 **名称一致** ,否则会出现无法找到 Flash 设备的错误 +- 分区的起始地址和大小 **不能超过 Flash 设备的地址范围** ,否则会导致包初始化错误 + +> 注意:每个分区定义时,除了填写上面介绍的参数属性外,需在前面增加 `FAL_PART_MAGIC_WORD` 属性,末尾增加 `0` (目前用于保留功能) diff --git a/components/fal/samples/porting/fal_cfg.h b/components/fal/samples/porting/fal_cfg.h new file mode 100644 index 0000000000..71c84522e5 --- /dev/null +++ b/components/fal/samples/porting/fal_cfg.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-05-17 armink the first version + */ + +#ifndef _FAL_CFG_H_ +#define _FAL_CFG_H_ + +#include +#include + +#define NOR_FLASH_DEV_NAME "norflash0" + +/* ===================== Flash device Configuration ========================= */ +extern const struct fal_flash_dev stm32f2_onchip_flash; +extern struct fal_flash_dev nor_flash0; + +/* flash device table */ +#define FAL_FLASH_DEV_TABLE \ +{ \ + &stm32f2_onchip_flash, \ + &nor_flash0, \ +} +/* ====================== Partition Configuration ========================== */ +#ifdef FAL_PART_HAS_TABLE_CFG +/* partition table */ +#define FAL_PART_TABLE \ +{ \ + {FAL_PART_MAGIC_WORD, "bl", "stm32_onchip", 0, 64*1024, 0}, \ + {FAL_PART_MAGIC_WORD, "app", "stm32_onchip", 64*1024, 704*1024, 0}, \ + {FAL_PART_MAGIC_WORD, "easyflash", NOR_FLASH_DEV_NAME, 0, 1024*1024, 0}, \ + {FAL_PART_MAGIC_WORD, "download", NOR_FLASH_DEV_NAME, 1024*1024, 1024*1024, 0}, \ +} +#endif /* FAL_PART_HAS_TABLE_CFG */ + +#endif /* _FAL_CFG_H_ */ diff --git a/components/fal/samples/porting/fal_flash_sfud_port.c b/components/fal/samples/porting/fal_flash_sfud_port.c new file mode 100644 index 0000000000..35bbc0f276 --- /dev/null +++ b/components/fal/samples/porting/fal_flash_sfud_port.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-01-26 armink the first version + */ + +#include +#include + +#ifdef FAL_USING_SFUD_PORT +#ifdef RT_USING_SFUD +#include +#endif + +#ifndef FAL_USING_NOR_FLASH_DEV_NAME +#define FAL_USING_NOR_FLASH_DEV_NAME "norflash0" +#endif + +static int init(void); +static int read(long offset, uint8_t *buf, size_t size); +static int write(long offset, const uint8_t *buf, size_t size); +static int erase(long offset, size_t size); + +static sfud_flash_t sfud_dev = NULL; +struct fal_flash_dev nor_flash0 = +{ + .name = FAL_USING_NOR_FLASH_DEV_NAME, + .addr = 0, + .len = 8 * 1024 * 1024, + .blk_size = 4096, + .ops = {init, read, write, erase}, + .write_gran = 1 +}; + +static int init(void) +{ + +#ifdef RT_USING_SFUD + /* RT-Thread RTOS platform */ + sfud_dev = rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME); +#else + /* bare metal platform */ + extern sfud_flash sfud_norflash0; + sfud_dev = &sfud_norflash0; +#endif + + if (NULL == sfud_dev) + { + return -1; + } + + /* update the flash chip information */ + nor_flash0.blk_size = sfud_dev->chip.erase_gran; + nor_flash0.len = sfud_dev->chip.capacity; + + return 0; +} + +static int read(long offset, uint8_t *buf, size_t size) +{ + assert(sfud_dev); + assert(sfud_dev->init_ok); + sfud_read(sfud_dev, nor_flash0.addr + offset, size, buf); + + return size; +} + +static int write(long offset, const uint8_t *buf, size_t size) +{ + assert(sfud_dev); + assert(sfud_dev->init_ok); + if (sfud_write(sfud_dev, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS) + { + return -1; + } + + return size; +} + +static int erase(long offset, size_t size) +{ + assert(sfud_dev); + assert(sfud_dev->init_ok); + if (sfud_erase(sfud_dev, nor_flash0.addr + offset, size) != SFUD_SUCCESS) + { + return -1; + } + + return size; +} +#endif /* FAL_USING_SFUD_PORT */ + diff --git a/components/fal/samples/porting/fal_flash_stm32f2_port.c b/components/fal/samples/porting/fal_flash_stm32f2_port.c new file mode 100644 index 0000000000..ed2c2c045a --- /dev/null +++ b/components/fal/samples/porting/fal_flash_stm32f2_port.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-01-26 armink the first version + */ + +#include + +#include + +/* base address of the flash sectors */ +#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base address of Sector 0, 16 K bytes */ +#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base address of Sector 1, 16 K bytes */ +#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base address of Sector 2, 16 K bytes */ +#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 K bytes */ +#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base address of Sector 4, 64 K bytes */ +#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base address of Sector 5, 128 K bytes */ +#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base address of Sector 6, 128 K bytes */ +#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base address of Sector 7, 128 K bytes */ +#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base address of Sector 8, 128 K bytes */ +#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 K bytes */ +#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 K bytes */ +#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 K bytes */ + +/** + * Get the sector of a given address + * + * @param address flash address + * + * @return The sector of a given address + */ +static uint32_t stm32_get_sector(uint32_t address) +{ + uint32_t sector = 0; + + if ((address < ADDR_FLASH_SECTOR_1) && (address >= ADDR_FLASH_SECTOR_0)) + { + sector = FLASH_Sector_0; + } + else if ((address < ADDR_FLASH_SECTOR_2) && (address >= ADDR_FLASH_SECTOR_1)) + { + sector = FLASH_Sector_1; + } + else if ((address < ADDR_FLASH_SECTOR_3) && (address >= ADDR_FLASH_SECTOR_2)) + { + sector = FLASH_Sector_2; + } + else if ((address < ADDR_FLASH_SECTOR_4) && (address >= ADDR_FLASH_SECTOR_3)) + { + sector = FLASH_Sector_3; + } + else if ((address < ADDR_FLASH_SECTOR_5) && (address >= ADDR_FLASH_SECTOR_4)) + { + sector = FLASH_Sector_4; + } + else if ((address < ADDR_FLASH_SECTOR_6) && (address >= ADDR_FLASH_SECTOR_5)) + { + sector = FLASH_Sector_5; + } + else if ((address < ADDR_FLASH_SECTOR_7) && (address >= ADDR_FLASH_SECTOR_6)) + { + sector = FLASH_Sector_6; + } + else if ((address < ADDR_FLASH_SECTOR_8) && (address >= ADDR_FLASH_SECTOR_7)) + { + sector = FLASH_Sector_7; + } + else if ((address < ADDR_FLASH_SECTOR_9) && (address >= ADDR_FLASH_SECTOR_8)) + { + sector = FLASH_Sector_8; + } + else if ((address < ADDR_FLASH_SECTOR_10) && (address >= ADDR_FLASH_SECTOR_9)) + { + sector = FLASH_Sector_9; + } + else if ((address < ADDR_FLASH_SECTOR_11) && (address >= ADDR_FLASH_SECTOR_10)) + { + sector = FLASH_Sector_10; + } + else + { + sector = FLASH_Sector_11; + } + + return sector; +} + +/** + * Get the sector size + * + * @param sector sector + * + * @return sector size + */ +static uint32_t stm32_get_sector_size(uint32_t sector) { + assert(IS_FLASH_SECTOR(sector)); + + switch (sector) { + case FLASH_Sector_0: return 16 * 1024; + case FLASH_Sector_1: return 16 * 1024; + case FLASH_Sector_2: return 16 * 1024; + case FLASH_Sector_3: return 16 * 1024; + case FLASH_Sector_4: return 64 * 1024; + case FLASH_Sector_5: return 128 * 1024; + case FLASH_Sector_6: return 128 * 1024; + case FLASH_Sector_7: return 128 * 1024; + case FLASH_Sector_8: return 128 * 1024; + case FLASH_Sector_9: return 128 * 1024; + case FLASH_Sector_10: return 128 * 1024; + case FLASH_Sector_11: return 128 * 1024; + default : return 128 * 1024; + } +} +static int init(void) +{ + /* do nothing now */ +} + +static int read(long offset, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t addr = stm32f2_onchip_flash.addr + offset; + for (i = 0; i < size; i++, addr++, buf++) + { + *buf = *(uint8_t *) addr; + } + + return size; +} + +static int write(long offset, const uint8_t *buf, size_t size) +{ + size_t i; + uint32_t read_data; + uint32_t addr = stm32f2_onchip_flash.addr + offset; + + FLASH_Unlock(); + FLASH_ClearFlag( + FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR + | FLASH_FLAG_PGSERR); + for (i = 0; i < size; i++, buf++, addr++) + { + /* write data */ + FLASH_ProgramByte(addr, *buf); + read_data = *(uint8_t *) addr; + /* check data */ + if (read_data != *buf) + { + return -1; + } + } + FLASH_Lock(); + + return size; +} + +static int erase(long offset, size_t size) +{ + FLASH_Status flash_status; + size_t erased_size = 0; + uint32_t cur_erase_sector; + uint32_t addr = stm32f2_onchip_flash.addr + offset; + + /* start erase */ + FLASH_Unlock(); + FLASH_ClearFlag( + FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR + | FLASH_FLAG_PGSERR); + /* it will stop when erased size is greater than setting size */ + while (erased_size < size) + { + cur_erase_sector = stm32_get_sector(addr + erased_size); + flash_status = FLASH_EraseSector(cur_erase_sector, VoltageRange_3); + if (flash_status != FLASH_COMPLETE) + { + return -1; + } + erased_size += stm32_get_sector_size(cur_erase_sector); + } + FLASH_Lock(); + + return size; +} + +const struct fal_flash_dev stm32f2_onchip_flash = +{ + .name = "stm32_onchip", + .addr = 0x08000000, + .len = 1024*1024, + .blk_size = 128*1024, + .ops = {init, read, write, erase}, + .write_gran = 8 +}; + diff --git a/components/fal/src/fal.c b/components/fal/src/fal.c new file mode 100644 index 0000000000..292b492b34 --- /dev/null +++ b/components/fal/src/fal.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-05-17 armink the first version + */ + +#include + +static uint8_t init_ok = 0; + +/** + * FAL (Flash Abstraction Layer) initialization. + * It will initialize all flash device and all flash partition. + * + * @return >= 0: partitions total number + */ +int fal_init(void) +{ + extern int fal_flash_init(void); + extern int fal_partition_init(void); + + int result; + + /* initialize all flash device on FAL flash table */ + result = fal_flash_init(); + + if (result < 0) { + goto __exit; + } + + /* initialize all flash partition on FAL partition table */ + result = fal_partition_init(); + +__exit: + + if ((result > 0) && (!init_ok)) + { + init_ok = 1; + log_i("RT-Thread Flash Abstraction Layer initialize success."); + } + else if(result <= 0) + { + init_ok = 0; + log_e("RT-Thread Flash Abstraction Layer initialize failed."); + } + + return result; +} + +/** + * Check if the FAL is initialized successfully + * + * @return 0: not init or init failed; 1: init success + */ +int fal_init_check(void) +{ + return init_ok; +} diff --git a/components/fal/src/fal_flash.c b/components/fal/src/fal_flash.c new file mode 100644 index 0000000000..8cef82c2bb --- /dev/null +++ b/components/fal/src/fal_flash.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-05-17 armink the first version + */ + +#include +#include + +/* flash device table, must defined by user */ +#if !defined(FAL_FLASH_DEV_TABLE) +#error "You must defined flash device table (FAL_FLASH_DEV_TABLE) on 'fal_cfg.h'" +#endif + +static const struct fal_flash_dev * const device_table[] = FAL_FLASH_DEV_TABLE; +static const size_t device_table_len = sizeof(device_table) / sizeof(device_table[0]); +static uint8_t init_ok = 0; + +/** + * Initialize all flash device on FAL flash table + * + * @return result + */ +int fal_flash_init(void) +{ + size_t i; + + if (init_ok) + { + return 0; + } + + for (i = 0; i < device_table_len; i++) + { + assert(device_table[i]->ops.read); + assert(device_table[i]->ops.write); + assert(device_table[i]->ops.erase); + /* init flash device on flash table */ + if (device_table[i]->ops.init) + { + device_table[i]->ops.init(); + } + log_d("Flash device | %*.*s | addr: 0x%08lx | len: 0x%08x | blk_size: 0x%08x |initialized finish.", + FAL_DEV_NAME_MAX, FAL_DEV_NAME_MAX, device_table[i]->name, device_table[i]->addr, device_table[i]->len, + device_table[i]->blk_size); + } + + init_ok = 1; + return 0; +} + +/** + * find flash device by name + * + * @param name flash device name + * + * @return != NULL: flash device + * NULL: not found + */ +const struct fal_flash_dev *fal_flash_device_find(const char *name) +{ + assert(init_ok); + assert(name); + + size_t i; + + for (i = 0; i < device_table_len; i++) + { + if (!strncmp(name, device_table[i]->name, FAL_DEV_NAME_MAX)) { + return device_table[i]; + } + } + + return NULL; +} diff --git a/components/fal/src/fal_partition.c b/components/fal/src/fal_partition.c new file mode 100644 index 0000000000..92ae074303 --- /dev/null +++ b/components/fal/src/fal_partition.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-05-17 armink the first version + */ + +#include +#include +#include + +/* partition magic word */ +#define FAL_PART_MAGIC_WORD 0x45503130 +#define FAL_PART_MAGIC_WORD_H 0x4550L +#define FAL_PART_MAGIC_WORD_L 0x3130L +#define FAL_PART_MAGIC_WROD 0x45503130 + +struct part_flash_info +{ + const struct fal_flash_dev *flash_dev; +}; + +/** + * FAL partition table config has defined on 'fal_cfg.h'. + * When this option is disable, it will auto find the partition table on a specified location in flash partition. + */ +#ifdef FAL_PART_HAS_TABLE_CFG + +/* check partition table definition */ +#if !defined(FAL_PART_TABLE) +#error "You must defined FAL_PART_TABLE on 'fal_cfg.h'" +#endif + +/* partition table definition */ +static const struct fal_partition partition_table_def[] = FAL_PART_TABLE; +static const struct fal_partition *partition_table = NULL; +/* partition and flash object information cache table */ +static struct part_flash_info part_flash_cache[sizeof(partition_table_def) / sizeof(partition_table_def[0])] = { 0 }; + +#else /* FAL_PART_HAS_TABLE_CFG */ + +#if !defined(FAL_PART_TABLE_FLASH_DEV_NAME) +#error "You must defined FAL_PART_TABLE_FLASH_DEV_NAME on 'fal_cfg.h'" +#endif + +/* check partition table end offset address definition */ +#if !defined(FAL_PART_TABLE_END_OFFSET) +#error "You must defined FAL_PART_TABLE_END_OFFSET on 'fal_cfg.h'" +#endif + +static struct fal_partition *partition_table = NULL; +static struct part_flash_info *part_flash_cache = NULL; +#endif /* FAL_PART_HAS_TABLE_CFG */ + +static uint8_t init_ok = 0; +static size_t partition_table_len = 0; + +/** + * print the partition table + */ +void fal_show_part_table(void) +{ + char *item1 = "name", *item2 = "flash_dev"; + size_t i, part_name_max = strlen(item1), flash_dev_name_max = strlen(item2); + const struct fal_partition *part; + + if (partition_table_len) + { + for (i = 0; i < partition_table_len; i++) + { + part = &partition_table[i]; + if (strlen(part->name) > part_name_max) + { + part_name_max = strlen(part->name); + } + if (strlen(part->flash_name) > flash_dev_name_max) + { + flash_dev_name_max = strlen(part->flash_name); + } + } + } + log_i("==================== FAL partition table ===================="); + log_i("| %-*.*s | %-*.*s | offset | length |", part_name_max, FAL_DEV_NAME_MAX, item1, flash_dev_name_max, + FAL_DEV_NAME_MAX, item2); + log_i("-------------------------------------------------------------"); + for (i = 0; i < partition_table_len; i++) + { + +#ifdef FAL_PART_HAS_TABLE_CFG + part = &partition_table[i]; +#else + part = &partition_table[partition_table_len - i - 1]; +#endif + + log_i("| %-*.*s | %-*.*s | 0x%08lx | 0x%08x |", part_name_max, FAL_DEV_NAME_MAX, part->name, flash_dev_name_max, + FAL_DEV_NAME_MAX, part->flash_name, part->offset, part->len); + } + log_i("============================================================="); +} + +static int check_and_update_part_cache(const struct fal_partition *table, size_t len) +{ + const struct fal_flash_dev *flash_dev = NULL; + size_t i; + +#ifndef FAL_PART_HAS_TABLE_CFG + if (part_flash_cache) + { + FAL_FREE(part_flash_cache); + } + part_flash_cache = FAL_MALLOC(len * sizeof(struct part_flash_info)); + if (part_flash_cache == NULL) + { + log_e("Initialize failed! No memory for partition table cache"); + return -2; + } +#endif + + for (i = 0; i < len; i++) + { + flash_dev = fal_flash_device_find(table[i].flash_name); + if (flash_dev == NULL) + { + log_d("Warning: Do NOT found the flash device(%s).", table[i].flash_name); + continue; + } + + if (table[i].offset >= (long)flash_dev->len) + { + log_e("Initialize failed! Partition(%s) offset address(%ld) out of flash bound(<%d).", + table[i].name, table[i].offset, flash_dev->len); + partition_table_len = 0; + + return -1; + } + + part_flash_cache[i].flash_dev = flash_dev; + } + + return 0; +} + +/** + * Initialize all flash partition on FAL partition table + * + * @return partitions total number + */ +int fal_partition_init(void) +{ + + if (init_ok) + { + return partition_table_len; + } + +#ifdef FAL_PART_HAS_TABLE_CFG + partition_table = &partition_table_def[0]; + partition_table_len = sizeof(partition_table_def) / sizeof(partition_table_def[0]); +#else + /* load partition table from the end address FAL_PART_TABLE_END_OFFSET, error return 0 */ + long part_table_offset = FAL_PART_TABLE_END_OFFSET; + size_t table_num = 0, table_item_size = 0; + uint8_t part_table_find_ok = 0; + uint32_t read_magic_word; + fal_partition_t new_part = NULL; + size_t i; + const struct fal_flash_dev *flash_dev = NULL; + + flash_dev = fal_flash_device_find(FAL_PART_TABLE_FLASH_DEV_NAME); + if (flash_dev == NULL) + { + log_e("Initialize failed! Flash device (%s) NOT found.", FAL_PART_TABLE_FLASH_DEV_NAME); + goto _exit; + } + + /* check partition table offset address */ + if (part_table_offset < 0 || part_table_offset >= (long) flash_dev->len) + { + log_e("Setting partition table end offset address(%ld) out of flash bound(<%d).", part_table_offset, flash_dev->len); + goto _exit; + } + + table_item_size = sizeof(struct fal_partition); + new_part = (fal_partition_t)FAL_MALLOC(table_item_size); + if (new_part == NULL) + { + log_e("Initialize failed! No memory for table buffer."); + goto _exit; + } + + /* find partition table location */ + { + uint8_t read_buf[64]; + + part_table_offset -= sizeof(read_buf); + while (part_table_offset >= 0) + { + if (flash_dev->ops.read(part_table_offset, read_buf, sizeof(read_buf)) > 0) + { + /* find magic word in read buf */ + for (i = 0; i < sizeof(read_buf) - sizeof(read_magic_word) + 1; i++) + { + read_magic_word = read_buf[0 + i] + (read_buf[1 + i] << 8) + (read_buf[2 + i] << 16) + (read_buf[3 + i] << 24); + if (read_magic_word == ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L)) + { + part_table_find_ok = 1; + part_table_offset += i; + log_d("Find the partition table on '%s' offset @0x%08lx.", FAL_PART_TABLE_FLASH_DEV_NAME, + part_table_offset); + break; + } + } + } + else + { + /* read failed */ + break; + } + + if (part_table_find_ok) + { + break; + } + else + { + /* calculate next read buf position */ + if (part_table_offset >= (long)sizeof(read_buf)) + { + part_table_offset -= sizeof(read_buf); + part_table_offset += (sizeof(read_magic_word) - 1); + } + else if (part_table_offset != 0) + { + part_table_offset = 0; + } + else + { + /* find failed */ + break; + } + } + } + } + + /* load partition table */ + while (part_table_find_ok) + { + memset(new_part, 0x00, table_num); + if (flash_dev->ops.read(part_table_offset - table_item_size * (table_num), (uint8_t *) new_part, + table_item_size) < 0) + { + log_e("Initialize failed! Flash device (%s) read error!", flash_dev->name); + table_num = 0; + break; + } + + if (new_part->magic_word != ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L)) + { + break; + } + + partition_table = (fal_partition_t) FAL_REALLOC(partition_table, table_item_size * (table_num + 1)); + if (partition_table == NULL) + { + log_e("Initialize failed! No memory for partition table"); + table_num = 0; + break; + } + + memcpy(partition_table + table_num, new_part, table_item_size); + + table_num++; + }; + + if (table_num == 0) + { + log_e("Partition table NOT found on flash: %s (len: %d) from offset: 0x%08x.", FAL_PART_TABLE_FLASH_DEV_NAME, + FAL_DEV_NAME_MAX, FAL_PART_TABLE_END_OFFSET); + goto _exit; + } + else + { + partition_table_len = table_num; + } +#endif /* FAL_PART_HAS_TABLE_CFG */ + + /* check the partition table device exists */ + if (check_and_update_part_cache(partition_table, partition_table_len) != 0) + { + goto _exit; + } + + init_ok = 1; + +_exit: + +#if FAL_DEBUG + fal_show_part_table(); +#endif + +#ifndef FAL_PART_HAS_TABLE_CFG + if (new_part) + { + FAL_FREE(new_part); + } +#endif /* !FAL_PART_HAS_TABLE_CFG */ + + return partition_table_len; +} + +/** + * find the partition by name + * + * @param name partition name + * + * @return != NULL: partition + * NULL: not found + */ +const struct fal_partition *fal_partition_find(const char *name) +{ + assert(init_ok); + + size_t i; + + for (i = 0; i < partition_table_len; i++) + { + if (!strcmp(name, partition_table[i].name)) + { + return &partition_table[i]; + } + } + + return NULL; +} + +static const struct fal_flash_dev *flash_device_find_by_part(const struct fal_partition *part) +{ + assert(part >= partition_table); + assert(part <= &partition_table[partition_table_len - 1]); + + return part_flash_cache[part - partition_table].flash_dev; +} + +/** + * get the partition table + * + * @param len return the partition table length + * + * @return partition table + */ +const struct fal_partition *fal_get_partition_table(size_t *len) +{ + assert(init_ok); + assert(len); + + *len = partition_table_len; + + return partition_table; +} + +/** + * set partition table temporarily + * This setting will modify the partition table temporarily, the setting will be lost after restart. + * + * @param table partition table + * @param len partition table length + */ +void fal_set_partition_table_temp(struct fal_partition *table, size_t len) +{ + assert(init_ok); + assert(table); + + check_and_update_part_cache(table, len); + + partition_table_len = len; + partition_table = table; +} + +/** + * read data from partition + * + * @param part partition + * @param addr relative address for partition + * @param buf read buffer + * @param size read size + * + * @return >= 0: successful read data size + * -1: error + */ +int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size) +{ + int ret = 0; + const struct fal_flash_dev *flash_dev = NULL; + + assert(part); + assert(buf); + + if (addr + size > part->len) + { + log_e("Partition read error! Partition address out of bound."); + return -1; + } + + flash_dev = flash_device_find_by_part(part); + if (flash_dev == NULL) + { + log_e("Partition read error! Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name); + return -1; + } + + ret = flash_dev->ops.read(part->offset + addr, buf, size); + if (ret < 0) + { + log_e("Partition read error! Flash device(%s) read error!", part->flash_name); + } + + return ret; +} + +/** + * write data to partition + * + * @param part partition + * @param addr relative address for partition + * @param buf write buffer + * @param size write size + * + * @return >= 0: successful write data size + * -1: error + */ +int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size) +{ + int ret = 0; + const struct fal_flash_dev *flash_dev = NULL; + + assert(part); + assert(buf); + + if (addr + size > part->len) + { + log_e("Partition write error! Partition address out of bound."); + return -1; + } + + flash_dev = flash_device_find_by_part(part); + if (flash_dev == NULL) + { + log_e("Partition write error! Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name); + return -1; + } + + ret = flash_dev->ops.write(part->offset + addr, buf, size); + if (ret < 0) + { + log_e("Partition write error! Flash device(%s) write error!", part->flash_name); + } + + return ret; +} + +/** + * erase partition data + * + * @param part partition + * @param addr relative address for partition + * @param size erase size + * + * @return >= 0: successful erased data size + * -1: error + */ +int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size) +{ + int ret = 0; + const struct fal_flash_dev *flash_dev = NULL; + + assert(part); + + if (addr + size > part->len) + { + log_e("Partition erase error! Partition address out of bound."); + return -1; + } + + flash_dev = flash_device_find_by_part(part); + if (flash_dev == NULL) + { + log_e("Partition erase error! Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name); + return -1; + } + + ret = flash_dev->ops.erase(part->offset + addr, size); + if (ret < 0) + { + log_e("Partition erase error! Flash device(%s) erase error!", part->flash_name); + } + + return ret; +} + +/** + * erase partition all data + * + * @param part partition + * + * @return >= 0: successful erased data size + * -1: error + */ +int fal_partition_erase_all(const struct fal_partition *part) +{ + return fal_partition_erase(part, 0, part->len); +} diff --git a/components/fal/src/fal_rtt.c b/components/fal/src/fal_rtt.c new file mode 100644 index 0000000000..52d276d8c9 --- /dev/null +++ b/components/fal/src/fal_rtt.c @@ -0,0 +1,934 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-06-23 armink the first version + * 2019-08-22 MurphyZhao adapt to none rt-thread case + */ + +#include + +#ifdef RT_VER_NUM +#include +#include +#include + +/* ========================== block device ======================== */ +struct fal_blk_device +{ + struct rt_device parent; + struct rt_device_blk_geometry geometry; + const struct fal_partition *fal_part; +}; + +/* RT-Thread device interface */ +#if RTTHREAD_VERSION >= 30000 +static rt_err_t blk_dev_control(rt_device_t dev, int cmd, void *args) +#else +static rt_err_t blk_dev_control(rt_device_t dev, rt_uint8_t cmd, void *args) +#endif +{ + struct fal_blk_device *part = (struct fal_blk_device*) dev; + + assert(part != RT_NULL); + + if (cmd == RT_DEVICE_CTRL_BLK_GETGEOME) + { + struct rt_device_blk_geometry *geometry; + + geometry = (struct rt_device_blk_geometry *) args; + if (geometry == RT_NULL) + { + return -RT_ERROR; + } + + memcpy(geometry, &part->geometry, sizeof(struct rt_device_blk_geometry)); + } + else if (cmd == RT_DEVICE_CTRL_BLK_ERASE) + { + rt_uint32_t *addrs = (rt_uint32_t *) args, start_addr = addrs[0], end_addr = addrs[1], phy_start_addr; + rt_size_t phy_size; + + if (addrs == RT_NULL || start_addr > end_addr) + { + return -RT_ERROR; + } + + if (end_addr == start_addr) + { + end_addr++; + } + + phy_start_addr = start_addr * part->geometry.bytes_per_sector; + phy_size = (end_addr - start_addr) * part->geometry.bytes_per_sector; + + if (fal_partition_erase(part->fal_part, phy_start_addr, phy_size) < 0) + { + return -RT_ERROR; + } + } + + return RT_EOK; +} + +static rt_size_t blk_dev_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size) +{ + int ret = 0; + struct fal_blk_device *part = (struct fal_blk_device*) dev; + + assert(part != RT_NULL); + + ret = fal_partition_read(part->fal_part, pos * part->geometry.block_size, buffer, size * part->geometry.block_size); + + if (ret != (int)(size * part->geometry.block_size)) + { + ret = 0; + } + else + { + ret = size; + } + + return ret; +} + +static rt_size_t blk_dev_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size) +{ + int ret = 0; + struct fal_blk_device *part; + rt_off_t phy_pos; + rt_size_t phy_size; + + part = (struct fal_blk_device*) dev; + assert(part != RT_NULL); + + /* change the block device's logic address to physical address */ + phy_pos = pos * part->geometry.bytes_per_sector; + phy_size = size * part->geometry.bytes_per_sector; + + ret = fal_partition_erase(part->fal_part, phy_pos, phy_size); + + if (ret == (int) phy_size) + { + ret = fal_partition_write(part->fal_part, phy_pos, buffer, phy_size); + } + + if (ret != (int) phy_size) + { + ret = 0; + } + else + { + ret = size; + } + + return ret; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops blk_dev_ops = +{ + RT_NULL, + RT_NULL, + RT_NULL, + blk_dev_read, + blk_dev_write, + blk_dev_control +}; +#endif + +/** + * create RT-Thread block device by specified partition + * + * @param parition_name partition name + * + * @return != NULL: created block device + * NULL: created failed + */ +struct rt_device *fal_blk_device_create(const char *parition_name) +{ + struct fal_blk_device *blk_dev; + const struct fal_partition *fal_part = fal_partition_find(parition_name); + const struct fal_flash_dev *fal_flash = NULL; + + if (!fal_part) + { + log_e("Error: the partition name (%s) is not found.", parition_name); + return NULL; + } + + if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL) + { + log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name); + return NULL; + } + + blk_dev = (struct fal_blk_device*) rt_malloc(sizeof(struct fal_blk_device)); + if (blk_dev) + { + blk_dev->fal_part = fal_part; + blk_dev->geometry.bytes_per_sector = fal_flash->blk_size; + blk_dev->geometry.block_size = fal_flash->blk_size; + blk_dev->geometry.sector_count = fal_part->len / fal_flash->blk_size; + + /* register device */ + blk_dev->parent.type = RT_Device_Class_Block; + +#ifdef RT_USING_DEVICE_OPS + blk_dev->parent.ops = &blk_dev_ops; +#else + blk_dev->parent.init = NULL; + blk_dev->parent.open = NULL; + blk_dev->parent.close = NULL; + blk_dev->parent.read = blk_dev_read; + blk_dev->parent.write = blk_dev_write; + blk_dev->parent.control = blk_dev_control; +#endif + + /* no private */ + blk_dev->parent.user_data = RT_NULL; + + log_i("The FAL block device (%s) created successfully", fal_part->name); + rt_device_register(RT_DEVICE(blk_dev), fal_part->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE); + } + else + { + log_e("Error: no memory for create FAL block device"); + } + + return RT_DEVICE(blk_dev); +} + +/* ========================== MTD nor device ======================== */ +#if defined(RT_USING_MTD_NOR) + +struct fal_mtd_nor_device +{ + struct rt_mtd_nor_device parent; + const struct fal_partition *fal_part; +}; + +static rt_size_t mtd_nor_dev_read(struct rt_mtd_nor_device* device, rt_off_t offset, rt_uint8_t* data, rt_uint32_t length) +{ + int ret = 0; + struct fal_mtd_nor_device *part = (struct fal_mtd_nor_device*) device; + + assert(part != RT_NULL); + + ret = fal_partition_read(part->fal_part, offset, data, length); + + if (ret != (int)length) + { + ret = 0; + } + else + { + ret = length; + } + + return ret; +} + +static rt_size_t mtd_nor_dev_write(struct rt_mtd_nor_device* device, rt_off_t offset, const rt_uint8_t* data, rt_uint32_t length) +{ + int ret = 0; + struct fal_mtd_nor_device *part; + + part = (struct fal_mtd_nor_device*) device; + assert(part != RT_NULL); + + ret = fal_partition_write(part->fal_part, offset, data, length); + + if (ret != (int) length) + { + ret = 0; + } + else + { + ret = length; + } + + return ret; +} + +static rt_err_t mtd_nor_dev_erase(struct rt_mtd_nor_device* device, rt_off_t offset, rt_uint32_t length) +{ + int ret = 0; + struct fal_mtd_nor_device *part; + + part = (struct fal_mtd_nor_device*) device; + assert(part != RT_NULL); + + ret = fal_partition_erase(part->fal_part, offset, length); + + if ((rt_uint32_t)ret != length || ret < 0) + { + return -RT_ERROR; + } + else + { + return RT_EOK; + } +} + +static const struct rt_mtd_nor_driver_ops _ops = +{ + RT_NULL, + mtd_nor_dev_read, + mtd_nor_dev_write, + mtd_nor_dev_erase, +}; + +/** + * create RT-Thread MTD NOR device by specified partition + * + * @param parition_name partition name + * + * @return != NULL: created MTD NOR device + * NULL: created failed + */ +struct rt_device *fal_mtd_nor_device_create(const char *parition_name) +{ + struct fal_mtd_nor_device *mtd_nor_dev; + const struct fal_partition *fal_part = fal_partition_find(parition_name); + const struct fal_flash_dev *fal_flash = NULL; + + if (!fal_part) + { + log_e("Error: the partition name (%s) is not found.", parition_name); + return NULL; + } + + if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL) + { + log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name); + return NULL; + } + + mtd_nor_dev = (struct fal_mtd_nor_device*) rt_malloc(sizeof(struct fal_mtd_nor_device)); + if (mtd_nor_dev) + { + mtd_nor_dev->fal_part = fal_part; + + mtd_nor_dev->parent.block_start = 0; + mtd_nor_dev->parent.block_end = fal_part->len / fal_flash->blk_size; + mtd_nor_dev->parent.block_size = fal_flash->blk_size; + + /* set ops */ + mtd_nor_dev->parent.ops = &_ops; + + log_i("The FAL MTD NOR device (%s) created successfully", fal_part->name); + rt_mtd_nor_register_device(fal_part->name, &mtd_nor_dev->parent); + } + else + { + log_e("Error: no memory for create FAL MTD NOR device"); + } + + return RT_DEVICE(&mtd_nor_dev->parent); +} + +#endif /* defined(RT_USING_MTD_NOR) */ + + +/* ========================== char device ======================== */ +struct fal_char_device +{ + struct rt_device parent; + const struct fal_partition *fal_part; +}; + +/* RT-Thread device interface */ +static rt_size_t char_dev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) +{ + int ret = 0; + struct fal_char_device *part = (struct fal_char_device *) dev; + + assert(part != RT_NULL); + + if (pos + size > part->fal_part->len) + size = part->fal_part->len - pos; + + ret = fal_partition_read(part->fal_part, pos, buffer, size); + + if (ret != (int)(size)) + ret = 0; + + return ret; +} + +static rt_size_t char_dev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + int ret = 0; + struct fal_char_device *part; + + part = (struct fal_char_device *) dev; + assert(part != RT_NULL); + + if (pos == 0) + { + fal_partition_erase_all(part->fal_part); + } + else if (pos + size > part->fal_part->len) + { + size = part->fal_part->len - pos; + } + + ret = fal_partition_write(part->fal_part, pos, buffer, size); + + if (ret != (int) size) + ret = 0; + + return ret; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops char_dev_ops = +{ + RT_NULL, + RT_NULL, + RT_NULL, + char_dev_read, + char_dev_write, + RT_NULL +}; +#endif + +#ifdef RT_USING_POSIX +#include + +/* RT-Thread device filesystem interface */ +static int char_dev_fopen(struct dfs_fd *fd) +{ + struct fal_char_device *part = (struct fal_char_device *) fd->data; + + assert(part != RT_NULL); + + switch (fd->flags & O_ACCMODE) + { + case O_RDONLY: + break; + case O_WRONLY: + case O_RDWR: + /* erase partition when device file open */ + fal_partition_erase_all(part->fal_part); + break; + default: + break; + } + fd->pos = 0; + + return RT_EOK; +} + +static int char_dev_fread(struct dfs_fd *fd, void *buf, size_t count) +{ + int ret = 0; + struct fal_char_device *part = (struct fal_char_device *) fd->data; + + assert(part != RT_NULL); + + if (fd->pos + count > part->fal_part->len) + count = part->fal_part->len - fd->pos; + + ret = fal_partition_read(part->fal_part, fd->pos, buf, count); + + if (ret != (int)(count)) + return 0; + + fd->pos += ret; + + return ret; +} + +static int char_dev_fwrite(struct dfs_fd *fd, const void *buf, size_t count) +{ + int ret = 0; + struct fal_char_device *part = (struct fal_char_device *) fd->data; + + assert(part != RT_NULL); + + if (fd->pos + count > part->fal_part->len) + count = part->fal_part->len - fd->pos; + + ret = fal_partition_write(part->fal_part, fd->pos, buf, count); + + if (ret != (int) count) + return 0; + + fd->pos += ret; + + return ret; +} + +static const struct dfs_file_ops char_dev_fops = +{ + char_dev_fopen, + RT_NULL, + RT_NULL, + char_dev_fread, + char_dev_fwrite, + RT_NULL, /* flush */ + RT_NULL, /* lseek */ + RT_NULL, /* getdents */ + RT_NULL, +}; +#endif /* defined(RT_USING_POSIX) */ + +/** + * create RT-Thread char device by specified partition + * + * @param parition_name partition name + * + * @return != NULL: created char device + * NULL: created failed + */ +struct rt_device *fal_char_device_create(const char *parition_name) +{ + struct fal_char_device *char_dev; + const struct fal_partition *fal_part = fal_partition_find(parition_name); + + if (!fal_part) + { + log_e("Error: the partition name (%s) is not found.", parition_name); + return NULL; + } + + if ((fal_flash_device_find(fal_part->flash_name)) == NULL) + { + log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name); + return NULL; + } + + char_dev = (struct fal_char_device *) rt_malloc(sizeof(struct fal_char_device)); + if (char_dev) + { + char_dev->fal_part = fal_part; + + /* register device */ + char_dev->parent.type = RT_Device_Class_Char; + +#ifdef RT_USING_DEVICE_OPS + char_dev->parent.ops = &char_dev_ops; +#else + char_dev->parent.init = NULL; + char_dev->parent.open = NULL; + char_dev->parent.close = NULL; + char_dev->parent.read = char_dev_read; + char_dev->parent.write = char_dev_write; + char_dev->parent.control = NULL; + /* no private */ + char_dev->parent.user_data = NULL; +#endif + + rt_device_register(RT_DEVICE(char_dev), fal_part->name, RT_DEVICE_FLAG_RDWR); + log_i("The FAL char device (%s) created successfully", fal_part->name); + +#ifdef RT_USING_POSIX + /* set fops */ + char_dev->parent.fops = &char_dev_fops; +#endif + + } + else + { + log_e("Error: no memory for create FAL char device"); + } + + return RT_DEVICE(char_dev); +} + +#if defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) + +#include +extern int fal_init_check(void); + +static void fal(uint8_t argc, char **argv) { + +#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ') +#define HEXDUMP_WIDTH 16 +#define CMD_PROBE_INDEX 0 +#define CMD_READ_INDEX 1 +#define CMD_WRITE_INDEX 2 +#define CMD_ERASE_INDEX 3 +#define CMD_BENCH_INDEX 4 + + int result = 0; + static const struct fal_flash_dev *flash_dev = NULL; + static const struct fal_partition *part_dev = NULL; + size_t i = 0, j = 0; + + const char* help_info[] = + { + [CMD_PROBE_INDEX] = "fal probe [dev_name|part_name] - probe flash device or partition by given name", + [CMD_READ_INDEX] = "fal read addr size - read 'size' bytes starting at 'addr'", + [CMD_WRITE_INDEX] = "fal write addr data1 ... dataN - write some bytes 'data' starting at 'addr'", + [CMD_ERASE_INDEX] = "fal erase addr size - erase 'size' bytes starting at 'addr'", + [CMD_BENCH_INDEX] = "fal bench - benchmark test with per block size", + }; + + if (fal_init_check() != 1) + { + rt_kprintf("\n[Warning] FAL is not initialized or failed to initialize!\n\n"); + return; + } + + if (argc < 2) + { + rt_kprintf("Usage:\n"); + for (i = 0; i < sizeof(help_info) / sizeof(char*); i++) + { + rt_kprintf("%s\n", help_info[i]); + } + rt_kprintf("\n"); + } + else + { + const char *operator = argv[1]; + uint32_t addr, size; + + if (!strcmp(operator, "probe")) + { + if (argc >= 3) + { + char *dev_name = argv[2]; + if ((flash_dev = fal_flash_device_find(dev_name)) != NULL) + { + part_dev = NULL; + } + else if ((part_dev = fal_partition_find(dev_name)) != NULL) + { + flash_dev = NULL; + } + else + { + rt_kprintf("Device %s NOT found. Probe failed.\n", dev_name); + flash_dev = NULL; + part_dev = NULL; + } + } + + if (flash_dev) + { + rt_kprintf("Probed a flash device | %s | addr: %ld | len: %d |.\n", flash_dev->name, + flash_dev->addr, flash_dev->len); + } + else if (part_dev) + { + rt_kprintf("Probed a flash partition | %s | flash_dev: %s | offset: %ld | len: %d |.\n", + part_dev->name, part_dev->flash_name, part_dev->offset, part_dev->len); + } + else + { + rt_kprintf("No flash device or partition was probed.\n"); + rt_kprintf("Usage: %s.\n", help_info[CMD_PROBE_INDEX]); + fal_show_part_table(); + } + } + else + { + if (!flash_dev && !part_dev) + { + rt_kprintf("No flash device or partition was probed. Please run 'fal probe'.\n"); + return; + } + if (!rt_strcmp(operator, "read")) + { + if (argc < 4) + { + rt_kprintf("Usage: %s.\n", help_info[CMD_READ_INDEX]); + return; + } + else + { + addr = strtol(argv[2], NULL, 0); + size = strtol(argv[3], NULL, 0); + uint8_t *data = rt_malloc(size); + if (data) + { + if (flash_dev) + { + result = flash_dev->ops.read(addr, data, size); + } + else if (part_dev) + { + result = fal_partition_read(part_dev, addr, data, size); + } + if (result >= 0) + { + rt_kprintf("Read data success. Start from 0x%08X, size is %ld. The data is:\n", addr, + size); + rt_kprintf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + for (i = 0; i < size; i += HEXDUMP_WIDTH) + { + rt_kprintf("[%08X] ", addr + i); + /* dump hex */ + for (j = 0; j < HEXDUMP_WIDTH; j++) + { + if (i + j < size) + { + rt_kprintf("%02X ", data[i + j]); + } + else + { + rt_kprintf(" "); + } + } + /* dump char for hex */ + for (j = 0; j < HEXDUMP_WIDTH; j++) + { + if (i + j < size) + { + rt_kprintf("%c", __is_print(data[i + j]) ? data[i + j] : '.'); + } + } + rt_kprintf("\n"); + } + rt_kprintf("\n"); + } + rt_free(data); + } + else + { + rt_kprintf("Low memory!\n"); + } + } + } + else if (!strcmp(operator, "write")) + { + if (argc < 4) + { + rt_kprintf("Usage: %s.\n", help_info[CMD_WRITE_INDEX]); + return; + } + else + { + addr = strtol(argv[2], NULL, 0); + size = argc - 3; + uint8_t *data = rt_malloc(size); + if (data) + { + for (i = 0; i < size; i++) + { + data[i] = strtol(argv[3 + i], NULL, 0); + } + if (flash_dev) + { + result = flash_dev->ops.write(addr, data, size); + } + else if (part_dev) + { + result = fal_partition_write(part_dev, addr, data, size); + } + if (result >= 0) + { + rt_kprintf("Write data success. Start from 0x%08X, size is %ld.\n", addr, size); + rt_kprintf("Write data: "); + for (i = 0; i < size; i++) + { + rt_kprintf("%d ", data[i]); + } + rt_kprintf(".\n"); + } + rt_free(data); + } + else + { + rt_kprintf("Low memory!\n"); + } + } + } + else if (!rt_strcmp(operator, "erase")) + { + if (argc < 4) + { + rt_kprintf("Usage: %s.\n", help_info[CMD_ERASE_INDEX]); + return; + } + else + { + addr = strtol(argv[2], NULL, 0); + size = strtol(argv[3], NULL, 0); + if (flash_dev) + { + result = flash_dev->ops.erase(addr, size); + } + else if (part_dev) + { + result = fal_partition_erase(part_dev, addr, size); + } + if (result >= 0) + { + rt_kprintf("Erase data success. Start from 0x%08X, size is %ld.\n", addr, size); + } + } + } + else if (!strcmp(operator, "bench")) + { + if (argc < 3) + { + rt_kprintf("Usage: %s.\n", help_info[CMD_BENCH_INDEX]); + return; + } + else if ((argc > 3 && strcmp(argv[3], "yes")) || argc < 4) + { + rt_kprintf("DANGER: It will erase full chip or partition! Please run 'fal bench %d yes'.\n", strtol(argv[2], NULL, 0)); + return; + } + /* full chip benchmark test */ + uint32_t start_time, time_cast; + size_t write_size = strtol(argv[2], NULL, 0), read_size = strtol(argv[2], NULL, 0), cur_op_size; + uint8_t *write_data = (uint8_t *)rt_malloc(write_size), *read_data = (uint8_t *)rt_malloc(read_size); + + if (write_data && read_data) + { + for (i = 0; i < write_size; i ++) { + write_data[i] = i & 0xFF; + } + if (flash_dev) + { + size = flash_dev->len; + } + else if (part_dev) + { + size = part_dev->len; + } + /* benchmark testing */ + rt_kprintf("Erasing %ld bytes data, waiting...\n", size); + start_time = rt_tick_get(); + if (flash_dev) + { + result = flash_dev->ops.erase(0, size); + } + else if (part_dev) + { + result = fal_partition_erase(part_dev, 0, size); + } + if (result >= 0) + { + time_cast = rt_tick_get() - start_time; + rt_kprintf("Erase benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND, + time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000)); + } + else + { + rt_kprintf("Erase benchmark has an error. Error code: %d.\n", result); + } + /* write test */ + rt_kprintf("Writing %ld bytes data, waiting...\n", size); + start_time = rt_tick_get(); + for (i = 0; i < size; i += write_size) + { + if (i + write_size <= size) + { + cur_op_size = write_size; + } + else + { + cur_op_size = size - i; + } + if (flash_dev) + { + result = flash_dev->ops.write(i, write_data, cur_op_size); + } + else if (part_dev) + { + result = fal_partition_write(part_dev, i, write_data, cur_op_size); + } + if (result < 0) + { + break; + } + } + if (result >= 0) + { + time_cast = rt_tick_get() - start_time; + rt_kprintf("Write benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND, + time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000)); + } + else + { + rt_kprintf("Write benchmark has an error. Error code: %d.\n", result); + } + /* read test */ + rt_kprintf("Reading %ld bytes data, waiting...\n", size); + start_time = rt_tick_get(); + for (i = 0; i < size; i += read_size) + { + if (i + read_size <= size) + { + cur_op_size = read_size; + } + else + { + cur_op_size = size - i; + } + if (flash_dev) + { + result = flash_dev->ops.read(i, read_data, cur_op_size); + } + else if (part_dev) + { + result = fal_partition_read(part_dev, i, read_data, cur_op_size); + } + /* data check */ + for (size_t index = 0; index < cur_op_size; index ++) + { + if (write_data[index] != read_data[index]) + { + rt_kprintf("%d %d %02x %02x.\n", i, index, write_data[index], read_data[index]); + } + } + + if (memcmp(write_data, read_data, cur_op_size)) + { + result = -RT_ERROR; + rt_kprintf("Data check ERROR! Please check you flash by other command.\n"); + } + /* has an error */ + if (result < 0) + { + break; + } + } + if (result >= 0) + { + time_cast = rt_tick_get() - start_time; + rt_kprintf("Read benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND, + time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000)); + } + else + { + rt_kprintf("Read benchmark has an error. Error code: %d.\n", result); + } + } + else + { + rt_kprintf("Low memory!\n"); + } + rt_free(write_data); + rt_free(read_data); + } + else + { + rt_kprintf("Usage:\n"); + for (i = 0; i < sizeof(help_info) / sizeof(char*); i++) + { + rt_kprintf("%s\n", help_info[i]); + } + rt_kprintf("\n"); + return; + } + if (result < 0) { + rt_kprintf("This operate has an error. Error code: %d.\n", result); + } + } + } +} +MSH_CMD_EXPORT(fal, FAL (Flash Abstraction Layer) operate.); + +#endif /* defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) */ +#endif /* RT_VER_NUM */ diff --git a/components/finsh/Kconfig b/components/finsh/Kconfig index 6f42aa704f..2d9d768e23 100644 --- a/components/finsh/Kconfig +++ b/components/finsh/Kconfig @@ -1,76 +1,79 @@ -menu "Command shell" - -config RT_USING_FINSH - bool - default n - -config RT_USING_MSH - bool "msh shell" - select RT_USING_FINSH +menuconfig RT_USING_MSH + bool "MSH: command shell" default y if RT_USING_MSH -config FINSH_USING_MSH - bool - default y + config RT_USING_FINSH + bool + default y -config FINSH_THREAD_NAME - string "The msh thread name" - default "tshell" -config FINSH_USING_HISTORY - bool "Enable command history feature" - default y -if FINSH_USING_HISTORY -config FINSH_HISTORY_LINES - int "The command history line number" - default 5 -endif + config FINSH_USING_MSH + bool + default y -config FINSH_USING_SYMTAB - bool "Using symbol table for commands" - default y + config FINSH_THREAD_NAME + string "The msh thread name" + default "tshell" -config FINSH_USING_DESCRIPTION - bool "Keeping description in symbol table" - default y + config FINSH_THREAD_PRIORITY + int "The priority level value of thread" + default 20 -config FINSH_ECHO_DISABLE_DEFAULT - bool "Disable the echo mode in default" - default n - -config FINSH_THREAD_PRIORITY - int "The priority level value of thread" - default 20 - -config FINSH_THREAD_STACK_SIZE - int "The stack size for thread" - default 4096 - -config FINSH_CMD_SIZE - int "The command line size for shell" - default 80 - -config FINSH_USING_AUTH - bool "shell support authentication" - default n - -if FINSH_USING_AUTH -config FINSH_DEFAULT_PASSWORD - string "The default password for shell authentication" - default "rtthread" -config FINSH_PASSWORD_MIN - int "The password min length" - default 6 -config FINSH_PASSWORD_MAX - int "The password max length" - default RT_NAME_MAX -endif + config FINSH_THREAD_STACK_SIZE + int "The stack size for thread" + default 4096 -config FINSH_ARG_MAX - int "The number of arguments for a shell command" - default 10 + config FINSH_USING_HISTORY + bool "Enable command history feature" + default y -endif + if FINSH_USING_HISTORY + config FINSH_HISTORY_LINES + int "The command history line number" + default 5 + endif + + config FINSH_USING_SYMTAB + bool "Using symbol table for commands" + default y -endmenu + config FINSH_CMD_SIZE + int "The command line size for shell" + default 80 + + config MSH_USING_BUILT_IN_COMMANDS + bool "Enable built-in commands, such as list_thread" + default y + + config FINSH_USING_DESCRIPTION + bool "Keeping description in symbol table" + default y + + config FINSH_ECHO_DISABLE_DEFAULT + bool "Disable the echo mode in default" + default n + + config FINSH_USING_AUTH + bool "shell support authentication" + default n + + if FINSH_USING_AUTH + config FINSH_DEFAULT_PASSWORD + string "The default password for shell authentication" + default "rtthread" + + config FINSH_PASSWORD_MIN + int "The password min length" + default 6 + + config FINSH_PASSWORD_MAX + int "The password max length" + default RT_NAME_MAX + endif + + config FINSH_ARG_MAX + int "The number of arguments for a shell command" + default 10 + +endif diff --git a/components/finsh/cmd.c b/components/finsh/cmd.c index 2d136814e8..1991698bb2 100644 --- a/components/finsh/cmd.c +++ b/components/finsh/cmd.c @@ -238,13 +238,13 @@ long list_thread(void) ptr = (rt_uint8_t *)thread->stack_addr; while (*ptr == '#')ptr ++; - rt_kprintf(" 0x%08x 0x%08x %02d%% 0x%08x %03d\n", + rt_kprintf(" 0x%08x 0x%08x %02d%% 0x%08x %s\n", thread->stack_size + ((rt_ubase_t)thread->stack_addr - (rt_ubase_t)thread->sp), thread->stack_size, (thread->stack_size - ((rt_ubase_t) ptr - (rt_ubase_t) thread->stack_addr)) * 100 / thread->stack_size, thread->remaining_tick, - thread->error); + rt_strerror(thread->error)); #endif } } diff --git a/include/rtdef.h b/include/rtdef.h index df739e7aad..08d37092a8 100644 --- a/include/rtdef.h +++ b/include/rtdef.h @@ -986,6 +986,7 @@ enum rt_device_class_type RT_Device_Class_I2CBUS, /**< I2C bus device */ RT_Device_Class_USBDevice, /**< USB slave device */ RT_Device_Class_USBHost, /**< USB host bus */ + RT_Device_Class_USBOTG, /**< USB OTG bus */ RT_Device_Class_SPIBUS, /**< SPI bus device */ RT_Device_Class_SPIDevice, /**< SPI device */ RT_Device_Class_SDIO, /**< SDIO bus device */ @@ -997,9 +998,15 @@ enum rt_device_class_type RT_Device_Class_Sensor, /**< Sensor device */ RT_Device_Class_Touch, /**< Touch device */ RT_Device_Class_PHY, /**< PHY device */ + RT_Device_Class_Security, /**< Security device */ + RT_Device_Class_WLAN, /**< WLAN device */ + RT_Device_Class_Pin, /**< Pin device */ + RT_Device_Class_ADC, /**< ADC device */ + RT_Device_Class_DAC, /**< DAC device */ + RT_Device_Class_WDT, /**< WDT device */ + RT_Device_Class_PWM, /**< PWM device */ RT_Device_Class_Unknown /**< unknown device */ }; - /** * device flags defitions */ @@ -1043,6 +1050,11 @@ enum rt_device_class_type #define RT_DEVICE_CTRL_GET_INT 0x08 /**< get interrupt status */ #define RT_DEVICE_CTRL_MASK 0x1f /**< mask for contrl commands */ +/** + * device control + */ +#define RT_DEVICE_CTRL_BASE(Type) (RT_Device_Class_##Type * 0x100) + /** * special device commands */ @@ -1213,12 +1225,18 @@ struct rt_device_blk_sectors /** * graphic device control command */ -#define RTGRAPHIC_CTRL_RECT_UPDATE 0 -#define RTGRAPHIC_CTRL_POWERON 1 -#define RTGRAPHIC_CTRL_POWEROFF 2 -#define RTGRAPHIC_CTRL_GET_INFO 3 -#define RTGRAPHIC_CTRL_SET_MODE 4 -#define RTGRAPHIC_CTRL_GET_EXT 5 +#define RTGRAPHIC_CTRL_RECT_UPDATE (RT_DEVICE_CTRL_BASE(Graphic) + 0) +#define RTGRAPHIC_CTRL_POWERON (RT_DEVICE_CTRL_BASE(Graphic) + 1) +#define RTGRAPHIC_CTRL_POWEROFF (RT_DEVICE_CTRL_BASE(Graphic) + 2) +#define RTGRAPHIC_CTRL_GET_INFO (RT_DEVICE_CTRL_BASE(Graphic) + 3) +#define RTGRAPHIC_CTRL_SET_MODE (RT_DEVICE_CTRL_BASE(Graphic) + 4) +#define RTGRAPHIC_CTRL_GET_EXT (RT_DEVICE_CTRL_BASE(Graphic) + 5) +#define RTGRAPHIC_CTRL_SET_BRIGHTNESS (RT_DEVICE_CTRL_BASE(Graphic) + 6) +#define RTGRAPHIC_CTRL_GET_BRIGHTNESS (RT_DEVICE_CTRL_BASE(Graphic) + 7) +#define RTGRAPHIC_CTRL_GET_MODE (RT_DEVICE_CTRL_BASE(Graphic) + 8) +#define RTGRAPHIC_CTRL_GET_STATUS (RT_DEVICE_CTRL_BASE(Graphic) + 9) +#define RTGRAPHIC_CTRL_PAN_DISPLAY (RT_DEVICE_CTRL_BASE(Graphic) + 10) +#define RTGRAPHIC_CTRL_WAIT_VSYNC (RT_DEVICE_CTRL_BASE(Graphic) + 11) /* graphic deice */ enum diff --git a/include/rtthread.h b/include/rtthread.h index 44768567cc..8ab88f1700 100644 --- a/include/rtthread.h +++ b/include/rtthread.h @@ -596,6 +596,7 @@ rt_device_t rt_console_get_device(void); rt_err_t rt_get_errno(void); void rt_set_errno(rt_err_t no); int *_rt_errno(void); +const char *rt_strerror(rt_err_t error); #if !defined(RT_USING_NEWLIB) && !defined(_WIN32) #ifndef errno #define errno *_rt_errno() diff --git a/src/clock.c b/src/clock.c index 7ac68c469b..c79264fa90 100644 --- a/src/clock.c +++ b/src/clock.c @@ -145,3 +145,24 @@ RTM_EXPORT(rt_tick_from_millisecond); /**@}*/ +/** + * @brief This function will return the passed millisecond from boot. + * + * @note if the value of RT_TICK_PER_SECOND is lower than 1000 or + * is not an integral multiple of 1000, this function will not + * provide the correct 1ms-based tick. + * + * @return Return passed millisecond from boot. + */ +RT_WEAK rt_tick_t rt_tick_get_millisecond(void) +{ +#if 1000 % RT_TICK_PER_SECOND == 0u + return rt_tick_get() * (1000u / RT_TICK_PER_SECOND); +#else + #warning "rt-thread cannot provide a correct 1ms-based tick any longer,\ + please redefine this function in another file by using a high-precision hard-timer." + return 0; +#endif /* 1000 % RT_TICK_PER_SECOND == 0u */ +} + +/**@}*/ diff --git a/src/kservice.c b/src/kservice.c index a33774b698..1d5a7801c8 100644 --- a/src/kservice.c +++ b/src/kservice.c @@ -48,6 +48,40 @@ static volatile int __rt_errno; static rt_device_t _console_device = RT_NULL; #endif +static const char* rt_errno_strs[] = +{ + "OK", + "ERROR", + "ETIMOUT", + "ERSFULL", + "ERSEPTY", + "ENOMEM", + "ENOSYS", + "EBUSY", + "EIO", + "EINTRPT", + "EINVAL", + "EUNKNOW" +}; + +/** + * This function return a pointer to a string that contains the + * message of error. + * + * @param error the errorno code + * @return a point to error message string + */ +const char *rt_strerror(rt_err_t error) +{ + if (error < 0) + error = -error; + + return (error > RT_EINVAL + 1) ? + rt_errno_strs[RT_EINVAL + 1] : + rt_errno_strs[error]; +} +RTM_EXPORT(rt_strerror); + /* * This function will get errno * -- Gitee From f37455cbf38b1c81b804aa0345e2685dbbe409ef Mon Sep 17 00:00:00 2001 From: Rbb666 Date: Mon, 31 Oct 2022 12:36:07 +0800 Subject: [PATCH 2/2] update device_type_str --- components/finsh/cmd.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/components/finsh/cmd.c b/components/finsh/cmd.c index 7ad55f8d78..79a38227f1 100644 --- a/components/finsh/cmd.c +++ b/components/finsh/cmd.c @@ -787,7 +787,7 @@ long list_timer(void) MSH_CMD_EXPORT(list_timer, list timer in system); #ifdef RT_USING_DEVICE -static char *const device_type_str[] = +static char *const device_type_str[RT_Device_Class_Unknown] = { "Character Device", "Block Device", @@ -800,6 +800,7 @@ static char *const device_type_str[] = "I2C Bus", "USB Slave Device", "USB Host Bus", + "USB OTG Bus", "SPI Bus", "SPI Device", "SDIO Bus", @@ -811,8 +812,14 @@ static char *const device_type_str[] = "Sensor Device", "Touch Device", "Phy Device", - "Bus Device", - "Unknown" + "Security Device", + "WLAN Device", + "Pin Device", + "ADC Device", + "DAC Device", + "WDT Device", + "PWM Device", + "Bus Device" }; long list_device(void) @@ -821,6 +828,7 @@ long list_device(void) list_get_next_t find_arg; rt_list_t *obj_list[LIST_FIND_OBJ_NR]; rt_list_t *next = (rt_list_t*)RT_NULL; + const char *device_type; int maxlen; const char *item_title = "device"; @@ -829,7 +837,8 @@ long list_device(void) maxlen = RT_NAME_MAX; - rt_kprintf("%-*.s type ref count\n", maxlen, item_title); object_split(maxlen); + rt_kprintf("%-*.s type ref count\n", maxlen, item_title); + object_split(maxlen); rt_kprintf( " -------------------- ----------\n"); do { @@ -852,13 +861,17 @@ long list_device(void) rt_hw_interrupt_enable(level); device = (struct rt_device *)obj; + device_type = "Unknown"; + if (device->type < RT_Device_Class_Unknown && + device_type_str[device->type] != RT_NULL) + { + device_type = device_type_str[device->type]; + } rt_kprintf("%-*.*s %-20s %-8d\n", - maxlen, RT_NAME_MAX, - device->parent.name, - (device->type <= RT_Device_Class_Unknown) ? - device_type_str[device->type] : - device_type_str[RT_Device_Class_Unknown], - device->ref_count); + maxlen, RT_NAME_MAX, + device->parent.name, + device_type, + device->ref_count); } } -- Gitee