diff --git a/drivers/hdlc_rcp_if/CMakeLists.txt b/drivers/hdlc_rcp_if/CMakeLists.txt index abae0200ad9b1..eb325d2550e60 100644 --- a/drivers/hdlc_rcp_if/CMakeLists.txt +++ b/drivers/hdlc_rcp_if/CMakeLists.txt @@ -3,3 +3,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_HDLC_RCP_IF_NXP hdlc_rcp_if_nxp.c) +zephyr_library_sources_ifdef(CONFIG_HDLC_RCP_IF_UART hdlc_rcp_if_uart.c) diff --git a/drivers/hdlc_rcp_if/Kconfig b/drivers/hdlc_rcp_if/Kconfig index c2b77cd42a65e..fd7eec80e0b93 100644 --- a/drivers/hdlc_rcp_if/Kconfig +++ b/drivers/hdlc_rcp_if/Kconfig @@ -14,6 +14,7 @@ menuconfig HDLC_RCP_IF if HDLC_RCP_IF source "drivers/hdlc_rcp_if/Kconfig.nxp" +source "drivers/hdlc_rcp_if/Kconfig.uart" config HDLC_RCP_IF_DRV_NAME string "HDLC RCP Interface Driver's name" diff --git a/drivers/hdlc_rcp_if/Kconfig.uart b/drivers/hdlc_rcp_if/Kconfig.uart new file mode 100644 index 0000000000000..38b180d022535 --- /dev/null +++ b/drivers/hdlc_rcp_if/Kconfig.uart @@ -0,0 +1,26 @@ +# Configuration options for NXP HDLC RCP UART communication Interface + +# Copyright (c) 2024 DENX Software Engineering GmbH +# Lukasz Majewski +# SPDX-License-Identifier: Apache-2.0 + +# +# HDLC UART communication Interface used by Zephyr running Openthread RCP host +# + +config HDLC_RCP_IF_UART + bool "UART HDLC interface for Zephyr Openthread RCP host" + default y + depends on DT_HAS_UART_HDLC_RCP_IF_ENABLED + +config OPENTHREAD_HDLC_RCP_IF_UART_RX_RING_BUFFER_SIZE + int "Set HDLC RCP IF UART RX ring buffer size" + default 4096 + help + RX buffer size for the OpenThread HDLC host UART. + +config OPENTHREAD_HDLC_RCP_IF_UART_TX_RING_BUFFER_SIZE + int "Set HDLC RCP IF UART TX ring buffer size" + default 1344 + help + TX buffer size for the OpenThread HDLC host UART. diff --git a/drivers/hdlc_rcp_if/hdlc_rcp_if_uart.c b/drivers/hdlc_rcp_if/hdlc_rcp_if_uart.c new file mode 100644 index 0000000000000..f14f1f792c667 --- /dev/null +++ b/drivers/hdlc_rcp_if/hdlc_rcp_if_uart.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2024 DENX Software Engineering GmbH + * Lukasz Majewski + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * IEEE 802.15.4 HDLC RCP interface - serial communication interface (UART) + */ + +/* -------------------------------------------------------------------------- */ +/* Includes */ +/* -------------------------------------------------------------------------- */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ +/* Definitions */ +/* -------------------------------------------------------------------------- */ + +#define DT_DRV_COMPAT uart_hdlc_rcp_if + +#define LOG_MODULE_NAME hdlc_rcp_if_uart +#define LOG_LEVEL CONFIG_HDLC_RCP_IF_DRIVER_LOG_LEVEL +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +struct openthread_uart { + struct k_work work; + struct ring_buf *rx_ringbuf; + struct ring_buf *tx_ringbuf; + const struct device *dev; + atomic_t tx_busy; + + hdlc_rx_callback_t cb; + void *param; +}; + +#define OT_UART_DEFINE(_name, _ringbuf_rx_size, _ringbuf_tx_size) \ + RING_BUF_DECLARE(_name##_rx_ringbuf, _ringbuf_rx_size); \ + RING_BUF_DECLARE(_name##_tx_ringbuf, _ringbuf_tx_size); \ + static struct openthread_uart _name = { \ + .rx_ringbuf = &_name##_rx_ringbuf, \ + .tx_ringbuf = &_name##_tx_ringbuf, \ + } +OT_UART_DEFINE(ot_uart, CONFIG_OPENTHREAD_HDLC_RCP_IF_UART_RX_RING_BUFFER_SIZE, + CONFIG_OPENTHREAD_HDLC_RCP_IF_UART_TX_RING_BUFFER_SIZE); + +struct ot_hdlc_rcp_context { + struct net_if *iface; + struct openthread_context *ot_context; +}; + +/* -------------------------------------------------------------------------- */ +/* Private functions */ +/* -------------------------------------------------------------------------- */ + +static void ot_uart_rx_cb(struct k_work *item) +{ + struct openthread_uart *otuart = + CONTAINER_OF(item, struct openthread_uart, work); + uint8_t *data; + uint32_t len; + + len = ring_buf_get_claim(otuart->rx_ringbuf, &data, + otuart->rx_ringbuf->size); + if (len > 0) { + otuart->cb(data, len, otuart->param); + ring_buf_get_finish(otuart->rx_ringbuf, len); + } +} + +static void uart_tx_handle(const struct device *dev) +{ + uint32_t tx_len = 0, len; + uint8_t *data; + + len = ring_buf_get_claim( + ot_uart.tx_ringbuf, &data, + ot_uart.tx_ringbuf->size); + if (len > 0) { + tx_len = uart_fifo_fill(dev, data, len); + int err = ring_buf_get_finish(ot_uart.tx_ringbuf, tx_len); + (void)err; + __ASSERT_NO_MSG(err == 0); + } else { + uart_irq_tx_disable(dev); + } +} + +static void uart_rx_handle(const struct device *dev) +{ + uint32_t rd_len = 0, len; + uint8_t *data; + + len = ring_buf_put_claim( + ot_uart.rx_ringbuf, &data, + ot_uart.rx_ringbuf->size); + if (len > 0) { + rd_len = uart_fifo_read(dev, data, len); + + int err = ring_buf_put_finish(ot_uart.rx_ringbuf, rd_len); + (void)err; + __ASSERT_NO_MSG(err == 0); + } +} + +static void uart_callback(const struct device *dev, void *user_data) +{ + ARG_UNUSED(user_data); + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_rx_ready(dev)) { + uart_rx_handle(dev); + } + + if (uart_irq_tx_ready(dev)) { + uart_tx_handle(dev); + } + } + + if (ring_buf_size_get(ot_uart.rx_ringbuf) > 0) { + k_work_submit(&ot_uart.work); + } +} + +static void hdlc_iface_init(struct net_if *iface) +{ + struct ot_hdlc_rcp_context *ctx = net_if_get_device(iface)->data; + otExtAddress eui64; + + ot_uart.dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_ot_uart)); + + if (!device_is_ready(ot_uart.dev)) { + LOG_ERR("UART device not ready"); + } + + uart_irq_callback_user_data_set(ot_uart.dev, + uart_callback, + (void *)&ot_uart); + + ctx->iface = iface; + ieee802154_init(iface); + ctx->ot_context = net_if_l2_data(iface); + + otPlatRadioGetIeeeEui64(ctx->ot_context->instance, eui64.m8); + net_if_set_link_addr(iface, eui64.m8, OT_EXT_ADDRESS_SIZE, + NET_LINK_IEEE802154); +} + +static int hdlc_register_rx_cb(hdlc_rx_callback_t hdlc_rx_callback, void *param) +{ + ot_uart.cb = hdlc_rx_callback; + ot_uart.param = param; + + k_work_init(&ot_uart.work, ot_uart_rx_cb); + uart_irq_rx_enable(ot_uart.dev); + + return 0; +} + +static int hdlc_send(const uint8_t *frame, uint16_t length) +{ + uint32_t ret; + + if (frame == NULL) { + return -EIO; + } + + ret = ring_buf_put(ot_uart.tx_ringbuf, frame, length); + uart_irq_tx_enable(ot_uart.dev); + + if (ret < length) { + LOG_WRN("Cannot store full frame to RB (%d < %d)", ret, length); + return -EIO; + } + + return 0; +} + +static int hdlc_deinit(void) +{ + uart_irq_tx_disable(ot_uart.dev); + uart_irq_rx_disable(ot_uart.dev); + + ring_buf_reset(ot_uart.rx_ringbuf); + ring_buf_reset(ot_uart.tx_ringbuf); + + return 0; +} + +static const struct hdlc_api uart_hdlc_api = { + .iface_api.init = hdlc_iface_init, + .register_rx_cb = hdlc_register_rx_cb, + .send = hdlc_send, + .deinit = hdlc_deinit, +}; + +#define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2) + +#define MTU 1280 + +NET_DEVICE_DT_INST_DEFINE(0, NULL, /* Initialization Function */ + NULL, /* No PM API support */ + NULL, /* No context data */ + NULL, /* Configuration info */ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, /* Initial priority */ + &uart_hdlc_api, /* API interface functions */ + OPENTHREAD_L2, /* Openthread L2 */ + NET_L2_GET_CTX_TYPE(OPENTHREAD_L2), /* Openthread L2 context type */ + MTU); /* MTU size */ diff --git a/dts/bindings/hdlc_rcp_if/uart,hdlc-rcp-if.yaml b/dts/bindings/hdlc_rcp_if/uart,hdlc-rcp-if.yaml new file mode 100644 index 0000000000000..bb8d96596403b --- /dev/null +++ b/dts/bindings/hdlc_rcp_if/uart,hdlc-rcp-if.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2024, DENX Software Engineering GmbH +# Lukasz Majewski +# SPDX-License-Identifier: Apache-2.0 + +description: UART HDLC RCP interface node + +compatible: "uart,hdlc-rcp-if" + +include: base.yaml diff --git a/samples/net/sockets/echo_client/README.rst b/samples/net/sockets/echo_client/README.rst index 289a90acc39b8..02cc74b302df7 100644 --- a/samples/net/sockets/echo_client/README.rst +++ b/samples/net/sockets/echo_client/README.rst @@ -179,3 +179,161 @@ on how to test TLS with Linux host samples. See the :zephyr:code-sample:`sockets-echo-server` documentation for an alternate way of running, with the echo-client on the Linux host and the echo-server in QEMU. + +OpenThread RCP+Zephyr HOST (SPINEL connection via UART) +======================================================= + +Prerequisites: +-------------- + +- Build ``echo-server`` for HOST PC (x86_64) + (https://github.com/zephyrproject-rtos/net-tools) SHA1:1c4fdba + +.. code-block:: console + + $ make echo-server + +- Program nRF RCP from Nordic nrf SDK (v2.7.0): + +.. code-block:: console + + (v2.7.0) ~/ncs$ west build -p always -b nrf21540dk/nrf52840 -S logging nrf/samples/openthread/coprocessor + + +- Build mimxrt1020_evk HOST (Zephyr): + +.. zephyr-app-commands:: + :zephyr-app: samples/net/sockets/echo_client + :board: mimxrt1020_evk + :conf: "prj.conf overlay-ot-rcp-host-uart.conf" + :goals: build + :compact: + +And flash + +.. code-block:: console + + $ west flash -r pyocd -i 0226000047784e4500439004d9170013e56100009796990 + + +- Connect the nRF RCP with IMXRT1020 (HOST) via UART + +.. code-block:: c + + /* + * imxrt1020_evk -> HOST + * nRF21540-DK -> RCP (nrf/samples/openthread/coprocessor) + * LPUART2 used for communication: + * nRF21540 (P6) P0.08 RXD -> IMXRT1020-EVK (J17) D1 (GPIO B1 08) (TXD) + * nRF21540 (P6) P0.07 CTS -> IMXRT1020-EVK (J19) D8 (GPIO B1 07) (RTS) + * nRF21540 (P6) P0.06 TXD -> IMXRT1020-EVK (J17) D0 (GPIO B1 09) (RXD) + * nRF21540 (P6) P0.05 RTS -> IMXRT1020-EVK (J17) D7 (GPIO B1 06) (CTS) + */ + + +- Install the OTBR (OpenThread Border Router) docker container on your HOST PC (x86_64) + Follow steps from https://docs.nordicsemi.com/bundle/ncs-2.5.1/page/nrf/protocols/thread/tools.html#running_otbr_using_docker + +**Most notable ones:** + + 1. Create ``otbr0`` network bridge to have access to OT network from HOST + Linux PC + + .. code-block:: console + + sudo docker network create --ipv6 --subnet fd11:db8:1::/64 -o com.docker.network.bridge.name=otbr0 otbr + + + 2. Pull docker container for OTBR: + + .. code-block:: console + + docker pull nrfconnect/otbr:84c6aff + + + 3. Start the docker image: + + .. code-block:: console + + sudo modprobe ip6table_filter + sudo docker run -it --rm --privileged --name otbr --network otbr -p 8080:80 --sysctl "net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1" --volume /dev/ttyACM5:/dev/radio nrfconnect/otbr:84c6aff --radio-url spinel+hdlc+uart:///dev/radio?uart-baudrate=1000000 + + + 4. Add proper routing (``fd11:22::/64`` are the IPv6 addresses - On-Mesh - which allow accessing the OT devices) on HOST PC (x86_64) + + .. code-block:: console + + sudo ip -6 route add fd11:22::/64 dev otbr0 via fd11:db8:1::2 + + + And the output for on-OT address: + + .. code-block:: console + + ip route get fd11:22:0:0:5188:1678:d0c0:6893 + fd11:22::5188:1678:d0c0:6893 from :: via fd11:db8:1::2 dev otbr0 src fd11:db8:1::1 metric 1024 pref medium + + + 5. Start the console to the docker image: + + .. code-block:: console + + sudo docker exec -it otbr /bin/bash + + + Test with e.g. + + .. code-block:: console + + ot-ctl router table + ot-ctl ipaddr + + + +Configure OTBR +-------------- + +On the HOST PC's webbrowser: http://localhost:8080/ + +Go to ``Form`` and leave default values - e.g: + + * Network Key: ``00112233445566778899aabbccddeeff`` + * On-Mesh Prefix: ``fd11:22::`` + * Channel: ``15`` + + +to "FORM" the OT network. + +*Note:* +The "On-Mesh Prefix" shall match the one setup in ``otbr0`` routing. + + +Configure RCP (nRF21540-DK) + OT HOST (mimxrt1020) +-------------------------------------------------- + +.. code-block:: console + + ot factoryreset + ot dataset networkkey 00112233445566778899aabbccddeeff + ot ifconfig up + + +In the HOST PC www webpage interface please: +Commission -> Joiner PSKd* set to ``J01NME`` -> START COMMISSION + +.. code-block:: console + + ot joiner start J01NME + ot thread start + + +The ``ot ipaddr`` shall show IPv6 address starting from ``fd11:22:0:0:``. +This one can be accessed from HOST's PC network (via e.g. +``ping -6 fd11:22:0:0:e8bf:266b:63ca:eff4``). + +Start ``echo-server`` on HOST PC (x86-64) +----------------------------------------- + +.. code-block:: console + + ./echo-server -i otbr0 diff --git a/samples/net/sockets/echo_client/boards/mimxrt1020_evk.overlay b/samples/net/sockets/echo_client/boards/mimxrt1020_evk.overlay new file mode 100644 index 0000000000000..09743d6d61880 --- /dev/null +++ b/samples/net/sockets/echo_client/boards/mimxrt1020_evk.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 DENX Software Engineering GmbH + * Lukasz Majewski + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * imxrt1020_evk -> HOST + * nRF21540-DK -> RCP (nrf/samples/openthread/coprocessor) + * LPUART2 used for communication: + * nRF21540 (P6) P0.08 RXD -> IMXRT1020-EVK (J17) D1 (GPIO B1 08) (TXD) + * nRF21540 (P6) P0.07 CTS -> IMXRT1020-EVK (J19) D8 (GPIO B1 07) (RTS) + * nRF21540 (P6) P0.06 TXD -> IMXRT1020-EVK (J17) D0 (GPIO B1 09) (RXD) + * nRF21540 (P6) P0.05 RTS -> IMXRT1020-EVK (J17) D7 (GPIO B1 06) (CTS) + */ + +/** + * Overlay to enable support for OpenThread's RCP UART communication + * on the imxrt1020_evk board. + */ + +/ { + chosen { + zephyr,hdlc-rcp-if = &hdlc_rcp_if; + zephyr,ot-uart = &lpuart2; + }; + + hdlc_rcp_if: hdlc_rcp_if { + compatible = "uart,hdlc-rcp-if"; + }; +}; + +&lpuart2 { + status = "okay"; + current-speed = <1000000>; + pinctrl-0 = <&pinmux_lpuart2_flowcontrol>; + pinctrl-1 = <&pinmux_lpuart2_sleep>; + pinctrl-names = "default", "sleep"; + hw-flow-control; +}; + +/* + * The lpuart2's CTS pin is mapped to PHY's int-gpio or to SDRAM EMC 20. + * As SPINEL UART's implementation required CTS/RTS flow control - + * the PHY's mdio interrupt needs to be disabled. + */ +&phy { + /delete-property/ int-gpios; +}; + +&pinctrl { + pinmux_lpuart2_flowcontrol: pinmux_lpuart2_flowcontrol { + group0 { + pinmux = <&iomuxc_gpio_ad_b1_09_lpuart2_rx>, + <&iomuxc_gpio_ad_b1_08_lpuart2_tx>, + <&iomuxc_gpio_ad_b1_06_lpuart2_cts_b>, + <&iomuxc_gpio_ad_b1_07_lpuart2_rts_b>; + drive-strength = "r0-6"; + slew-rate = "slow"; + nxp,speed = "100-mhz"; + }; + }; +}; + +/delete-node/ &{/soc/iomuxc@401f8000/pinctrl/pinmux_enet_mdio/group1}; diff --git a/samples/net/sockets/echo_client/overlay-ot-rcp-host-uart.conf b/samples/net/sockets/echo_client/overlay-ot-rcp-host-uart.conf new file mode 100644 index 0000000000000..cb63accbe78a0 --- /dev/null +++ b/samples/net/sockets/echo_client/overlay-ot-rcp-host-uart.conf @@ -0,0 +1,59 @@ +CONFIG_REQUIRES_FULL_LIBC=y + +# CPP library +CONFIG_CPP=y + +# Disable TCP and IPv4 (TCP disabled to avoid heavy traffic) +CONFIG_NET_TCP=n +CONFIG_NET_IPV4=n + +CONFIG_NET_IPV6_NBR_CACHE=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_CONFIG_MY_IPV4_ADDR="" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="" + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=32768 +CONFIG_INIT_STACKS=y + +# Enable OpenThread shell +CONFIG_SHELL=y +CONFIG_OPENTHREAD_SHELL=y +CONFIG_SHELL_STACK_SIZE=8192 + +CONFIG_NET_L2_OPENTHREAD=y + +# Use NVS as settings backend +CONFIG_NVS=y + +# Enable Openthread rcp host +CONFIG_HDLC_RCP_IF=y + +# Enable OpenThread features set +CONFIG_OPENTHREAD_THREAD_STACK_SIZE=8192 +CONFIG_OPENTHREAD_THREAD_VERSION_1_3=y +#CONFIG_OPENTHREAD_DEBUG=y +#CONFIG_OPENTHREAD_L2_DEBUG=y +#CONFIG_OPENTHREAD_L2_LOG_LEVEL_INF=y + +# mbedTLS tweaks +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=768 + +# A sample configuration to enable Thread Joiner, uncomment if needed +CONFIG_OPENTHREAD_JOINER=y + +# Enable diagnostic module, uncomment if needed +#CONFIG_OPENTHREAD_DIAG=y + +# To have OTBR On-Mesh address assigned (fd11:22:0:0:) +CONFIG_OPENTHREAD_SLAAC=y + +# Setup the communication to echo-server +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="fd11:db8:1::1" +CONFIG_NET_CONFIG_INIT_TIMEOUT=60 + +# Configure the OpenThread manually +CONFIG_OPENTHREAD_MANUAL_START=y + +CONFIG_EVENTS=y