diff --git a/Documentation/README.html b/Documentation/README.html index 8287cadb283..348a2278cb4 100644 --- a/Documentation/README.html +++ b/Documentation/README.html @@ -29,9 +29,13 @@ nuttx/ |- arch/ | | | |- arm/ - | | |- src - | | |- lpc214x/README.txt - | | `- stm32l4/README.txt + | | `- src + | | |- common + | | | `- README_lwl_console.txt + | | |- lpc214x + | | | `- README.txt + | | `- stm32l4 + | | `- stm32l4/README.txt | |- renesas/ | | |- include/ | | | `-README.txt diff --git a/README.txt b/README.txt index b95e425525a..a5f6c41d98d 100644 --- a/README.txt +++ b/README.txt @@ -1707,8 +1707,12 @@ nuttx/ | | | |- arm/ | | `- src - | | |- lpc214x/README.txt - | | `- stm32l4/README.txt + | | |- common + | | | `- README_lwl_console.txt + | | |- lpc214x + | | | `-README.txt + | | `- stm32l4 + | | `- README.txt | |- renesas/ | | |- include/ | | | `-README.txt diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 471315d6810..1a0e67c6881 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -813,6 +813,19 @@ config ARM_SEMIHOSTING_HOSTFS ---help--- Mount HostFS through semihosting. +config ARM_LWL_CONSOLE + bool "Lightweight Link Console Support" + default n + depends on DEV_CONSOLE && ARCH_CHIP_STM32 + ---help--- + Use the lightweight link console which provides console over a + debug channel by means of shared memory. A terminal application + for openocd as the debugger is available in tools/ocdconsole.py. + + Currently only available for STM32 architectures, but easily + added to other ARM architectures be addd up_low_console.c to the + architecture Make.defs file. + if ARCH_CORTEXM0 source arch/arm/src/armv6-m/Kconfig endif diff --git a/arch/arm/src/common/README_lwl_console.txt b/arch/arm/src/common/README_lwl_console.txt new file mode 100644 index 00000000000..3e2cd3dfa43 --- /dev/null +++ b/arch/arm/src/common/README_lwl_console.txt @@ -0,0 +1,59 @@ +The file up_lwl_console.c implements a 'Lightweight Link' protocol between +a target and debugger for use when you need a console but the target doesn't +have a spare serial port or other available resource. This implements a +new console type which uses two words of memory for data exchange. + +It is not particularly efficient because of the various compromises that are +made (polling in busy loops, mostly) but it works well enough to give you +something where you previously had nothing...typically the case when you're +bring up a new CPU, or when the hardware designer thought the softies could +cope without a logging port. It has an advantage over semi-hosting in that +it doesn't put the target into debug mode while it's running, so you've got +some hope of maintaining real time semantics. To be clear, for output only +use you'd be better off with SWO if you've got it available! + +There is a terminal program in python(*) for the host side in +tools/ocdconsole.py for use with openocd...the NuttX side functionality is +not dependent on a specific debugger, the only requirement on it being that +the debugger can watch and modify a memory location on the target while it is executing. + +Typical use is; + +$ tools/ocdconsole.py +==Link Activated + +NuttShell (NSH) +nsh> help +help usage: help [-v] [] + + ? echo exit hexdump ls mh sleep xd + cat exec help kill mb mw usleep +nsh> + +On the target side it's transparent, and is just a console; + +nsh> ls /dev +/dev: + console + null + ttyS0 +nsh> echo "Hello World" > /dev/console +Hello World +nsh> + +CPU load on the host is surprisingly low given that the polling loop is +continuous (probably due to the fact that openocd is spending most of it's +time waiting for messages to/from the debug port on the target). When not +actively doing anything there's no load on the target, but waiting for input +is done in a busy polled loop (so the thread is effectively busy-locked) +and output busy-waits for the previous message to be collected before it +sends the next one. + +For now I've only made it available on stm32, but it should only be a case +of changing the Kconfig and Make.defs for other arm CPUs to make it +available for them too. Moving beyond arm needs knowledge of the targets +that I don't have. + +If anyone fancies extending this proof-of-concept to full Segger-RTT-style +functionality then drop me a note, there are plenty of ways to improve +performance. diff --git a/arch/arm/src/common/up_initialize.c b/arch/arm/src/common/up_initialize.c index b951a7dca52..7c0d1dcc0ea 100644 --- a/arch/arm/src/common/up_initialize.c +++ b/arch/arm/src/common/up_initialize.c @@ -204,7 +204,9 @@ void up_initialize(void) * serial driver). */ -#if defined(CONFIG_DEV_LOWCONSOLE) +#if defined (CONFIG_ARM_LWL_CONSOLE) + lwlconsole_init(); +#elif defined(CONFIG_DEV_LOWCONSOLE) lowconsole_init(); #elif defined(CONFIG_CONSOLE_SYSLOG) syslog_console_init(); diff --git a/arch/arm/src/common/up_internal.h b/arch/arm/src/common/up_internal.h index 51ea73f4a4e..64290551729 100644 --- a/arch/arm/src/common/up_internal.h +++ b/arch/arm/src/common/up_internal.h @@ -63,7 +63,11 @@ # undef CONFIG_DEV_LOWCONSOLE # undef CONFIG_RAMLOG_CONSOLE #else -# if defined(CONFIG_RAMLOG_CONSOLE) +# if defined(CONFIG_ARM_LWL_CONSOLE) +# undef USE_SERIALDRIVER +# undef USE_EARLYSERIALINIT +# undef CONFIG_DEV_LOWCONSOLE +# elif defined(CONFIG_RAMLOG_CONSOLE) # undef USE_SERIALDRIVER # undef USE_EARLYSERIALINIT # undef CONFIG_DEV_LOWCONSOLE @@ -299,7 +303,7 @@ EXTERN uint32_t _eramfuncs; /* Copy destination end address in RAM */ ****************************************************************************/ /**************************************************************************** - * Public Functions + * Public Function Prototypes ****************************************************************************/ #ifndef __ASSEMBLY__ @@ -449,10 +453,18 @@ void up_earlyserialinit(void); # define up_earlyserialinit() #endif +#ifdef CONFIG_ARM_LWL_CONSOLE + +/* Defined in src/common/up_lwl_console.c */ + +void lwlconsole_init(void); + +#elif defined(CONFIG_DEV_LOWCONSOLE) + /* Defined in drivers/lowconsole.c */ -#ifdef CONFIG_DEV_LOWCONSOLE void lowconsole_init(void); + #else # define lowconsole_init() #endif diff --git a/arch/arm/src/common/up_lwl_console.c b/arch/arm/src/common/up_lwl_console.c new file mode 100644 index 00000000000..a915a3879f5 --- /dev/null +++ b/arch/arm/src/common/up_lwl_console.c @@ -0,0 +1,327 @@ +/**************************************************************************** + * drivers/serial/lwlconsole.c + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * Author: Dave Marples + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Lightweight Link (lwl) + * ====================== + * + * Lightweight bidirectional communication between target and debug host + * without any need for additional hardware. + * + * Works with openOCD and other debuggers that are capable of reading and + * writing memory while the target is running. + * + * Principle of operation is simple; An 'upword' of 32 bits communicates + * from the target to the host, a 'downword' of the same size runs in the + * opposite direction. These two words can be in any memory that is + * read/write access for both the target and the debug host. A simple ping + * pong handshake protocol over these words allows up/down link + * communication. On the upside no additional integration is needed. On + * the downside it may be necessary to feed lwl with cycles to poll for + * changes in the downword, depending on the use case. + * + * Bit configuration + * ----------------- + * + * Downword (Host to target); + * + * A D U VV XXX + * + * A 31 1 - Service Active (Set by host) + * D 30 1 - Downsense (Toggled when there is data) + * U 29 1 - Upsense ack (Toggled to acknowledge receipt of uplink data) + * VV 28-27 2 - Valid Octets (Number of octets valid in the message) + * XXX 26-24 3 - Port in use (Type of the message) + * O2 23-16 8 - Octet 2 + * O1 15-08 8 - Octet 1 + * O0 07-00 8 - Octet 0 + * + * Upword (Target to Host); + * + * A 31 1 - Service Active (Set by device) + * D 30 1 - Downsense ack (Toggled to acknowledge receipt of downlink + * data) + * U 29 1 - Upsense (Toggled when there is data) + * VV 28-27 2 - Valid upword octets + * XXX 26-24 3 - Port in use (Type of the message) + * O2 23-16 8 - Octet 2 + * O1 15-08 8 - Octet 1 + * O0 07-00 8 - Octet 0 + * + */ + +/* Protocol bits */ + +#define LWL_GETACTIVE(x) (((x) & (1 << 31)) != 0) +#define LWL_ACTIVE(x) (((x)&1) << 31) + +#define LWL_DNSENSEBIT (1 << 30) +#define LWL_DNSENSE(x) ((x)&LWL_DNSENSEBIT) +#define LWL_UPSENSEBIT (1 << 29) +#define LWL_UPSENSE(x) ((x)&LWL_UPSENSEBIT) +#define LWL_SENSEMASK (3 << 29) + +#define LWL_GETOCTVAL(x) (((x) >> 27) & 3) +#define LWL_OCTVAL(x) (((x)&3) << 27) +#define LWL_GETPORT(x) (((x) >> 24) & 7) +#define LWL_PORT(x) (((x)&7) << 24) + +#define LWL_PORT_CONSOLE 1 +#define ID_SIG 0x7216A318 + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static ssize_t lwlconsole_read(struct file *filep, char *buffer, + size_t buflen); +static ssize_t lwlconsole_write(struct file *filep, const char *buffer, + size_t buflen); +static int lwlconsole_ioctl(struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct +{ + uint32_t sig; /* Location signature */ + volatile uint32_t downword; /* Host to Target word */ + uint32_t upword; /* Target to Host word */ +} g_d = +{ + .sig = ID_SIG +}; + +static const struct file_operations g_consoleops = +{ + NULL, /* open */ + NULL, /* close */ + lwlconsole_read, /* read */ + lwlconsole_write, /* write */ + NULL, /* seek */ + lwlconsole_ioctl, /* ioctl */ + NULL /* poll */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , + NULL /* unlink */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool linkactive(void) +{ + return (LWL_GETACTIVE(g_d.downword) != 0); +} + +static bool writeword(uint32_t newupword) +{ + /* Check link is active */ + + if (!linkactive()) + { + return false; + } + + /* Spin waiting for previous data to be collected */ + + while (LWL_UPSENSE(g_d.downword) != LWL_UPSENSE(g_d.upword)) + { + } + + /* Load new data, toggling UPSENSE bit to show it is new */ + + g_d.upword = LWL_DNSENSE(g_d.upword) | newupword | + (LWL_UPSENSE(g_d.upword) ? 0 : LWL_UPSENSEBIT); + + return true; +} + +static bool write8bits(uint8_t port, uint8_t val) +{ + /* Prepare new word */ + + uint32_t newupword = LWL_ACTIVE(true) | LWL_OCTVAL(1) | + LWL_PORT(port) | (val & 0xff); + + return writeword(newupword); +} + +static bool write16bits(uint8_t port, uint32_t val) +{ + /* Prepare new word */ + + uint32_t newupword = LWL_ACTIVE(true) | LWL_OCTVAL(2) | + LWL_PORT(port) | (val & 0xffff); + + return writeword(newupword); +} + +static bool write24bits(uint8_t port, uint32_t val) +{ + /* Prepare new word */ + + uint32_t newupword = LWL_ACTIVE(true) | LWL_OCTVAL(3) | + LWL_PORT(port) | (val & 0xffffff); + + return writeword(newupword); +} + +static bool read8bits(uint8_t port, uint8_t * store) +{ + if (LWL_DNSENSE(g_d.downword) == LWL_DNSENSE(g_d.upword)) + { + return false; + } + + *store = g_d.downword & 255; + + /* Flip the bit to indicate the datum is read */ + + g_d.upword = (g_d.upword & ~LWL_DNSENSEBIT) | LWL_DNSENSE(g_d.downword); + + return true; +} + +/**************************************************************************** + * Name: lwlconsole_ioctl + ****************************************************************************/ + +static int lwlconsole_ioctl(struct file *filep, int cmd, unsigned long arg) +{ + return -ENOTTY; +} + +/**************************************************************************** + * Name: lwlconsole_read + ****************************************************************************/ + +static ssize_t lwlconsole_read(struct file *filep, char *buffer, + size_t buflen) +{ + if (buflen == 0 || !linkactive()) + { + return 0; + } + + while (!read8bits(LWL_PORT_CONSOLE, (uint8_t *) buffer)) + { + } + + return 1; +} + +/**************************************************************************** + * Name: lwlconsole_write + ****************************************************************************/ + +static ssize_t lwlconsole_write(struct file *filep, const char *buffer, + size_t buflen) +{ + uint32_t oc = 0; + + while (buflen) + { + switch (buflen) + { + case 0: + return oc; + + case 1: + if (write8bits(LWL_PORT_CONSOLE, buffer[0])) + { + oc++; + buffer++; + buflen--; + } + break; + + case 2: + if (write16bits(LWL_PORT_CONSOLE, buffer[0] | (buffer[1] << 8))) + { + oc += 2; + buffer += 2; + buflen -= 2; + } + break; + + default: + if (write24bits(LWL_PORT_CONSOLE, buffer[0] | + (buffer[1] << 8) | (buffer[2] << 16))) + { + oc += 3; + buffer += 3; + buflen -= 3; + } + break; + } + } + + return oc; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lwlconsole_init + ****************************************************************************/ + +void lwlconsole_init(void) +{ + g_d.upword = 0; + (void)register_driver("/dev/console", &g_consoleops, 0666, NULL); +} diff --git a/arch/arm/src/stm32/Make.defs b/arch/arm/src/stm32/Make.defs index 7cddffac7b6..45a182cb7a1 100644 --- a/arch/arm/src/stm32/Make.defs +++ b/arch/arm/src/stm32/Make.defs @@ -54,6 +54,10 @@ ifeq ($(CONFIG_ARMV7M_STACKCHECK),y) CMN_CSRCS += up_stackcheck.c endif +ifeq ($(CONFIG_ARM_LWL_CONSOLE),y) +CMN_CSRCS += up_lwl_console.c +endif + ifeq ($(CONFIG_ARMV7M_LAZYFPU),y) CMN_ASRCS += up_lazyexception.S else diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 39b1d457e6a..2aa8311d97c 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -6,8 +6,7 @@ config DEV_LOWCONSOLE bool "Low-level console support" default n - depends on ARCH_LOWPUTC - depends on DEV_CONSOLE + depends on ARCH_LOWPUTC && DEV_CONSOLE ---help--- Use the simple, low-level, write-only serial console driver (minimal support) diff --git a/tools/ocdconsole.py b/tools/ocdconsole.py new file mode 100755 index 00000000000..ae6badf7bbd --- /dev/null +++ b/tools/ocdconsole.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 Dave Marples. All rights reserved. +# Author: Dave Marples +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# +# Console over Lightweight Link +# ============================= +# +# LWL is a Lightweight bidirectional communication between target and debug host +# without any need for additional hardware. +# +# It works with openOCD and other debuggers that are capable of reading and +# writing memory while the target is running...it should run with JLink +# for example, if you've got the SDK and modify this file accordingly. +# +# Principle of operation is simple; An 'upword' of 32 bits communicates +# from the target to the host, a 'downword' of the same size runs in the +# opposite direction. These two words can be in any memory that is +# read/write access for both the target and the debug host. A simple ping +# pong handshake protocol over these words allows up/down link communication. +# On the upside no additional integration is needed. On the downside it may be +# nessessary to feed lwl with cycles to poll for changes in the downword, +# depending on the use case. For the case of a simple console, that's not +# needed. +# +# For convinence these communication locations are automatically discovered +# from the RAM by searching through it. Just define downwordaddr and +# upwordaddr if you want to work with fixed locations. +# +# +# Bit configuration +# ----------------- +# +# Downword (Host to target); +# +# A D U VV XXX O2 O1 O0 +# +# A 31 1 - Service Active (Set by host) +# D 30 1 - Downsense (Toggled when there is data) +# U 29 1 - Upsense ack (Toggled to acknowledge receipt of uplink data) +# VV 28-27 2 - Valid Octets (Number of octets valid in the message) +# XXX 26-24 3 - Port in use (Type of the message) +# O2 23-16 8 - Octet 2 +# O1 15-08 8 - Octet 1 +# O0 07-00 8 - Octet 0 +# +# Upword (Target to Host); +# +# A 31 1 - Service Active (Set by device) +# D 30 1 - Downsense ack (Toggled to acknowledge receipt of downlink data) +# U 29 1 - Upsense (Toggled when there is data) +# VV 28-27 2 - Valid upword octets +# XXX 26-24 3 - Port in use (Type of the message) +# O2 23-16 8 - Octet 2 +# O1 15-08 8 - Octet 1 +# O0 07-00 8 - Octet 0 +# +# Port 1 is used for Console. No other ports are currently defined. +# +# Use +# === +# +# No special python modules are needed, it should be possible to run the +# application simply as shown below; +# +# ------------------------------------------ +# $ ./ocdconsole.py +# ==Link Activated +# +# nsh> +# nsh> help +# help usage: help [-v] [] +# +# ? echo exit hexdump ls mh sleep xd +# cat exec help kill mb mw usleep +# nsh> +# ------------------------------------------ +# +# This code is designed to be 'hardy' and will survive a shutdown and +# restart of the openocd process. When your target application +# changes then the location of the upword and downword may change, +# so they are re-searched for again. To speed up the start process +# consider putting those words at fixed locations (e.g. via the +# linker file) and referencing them directly. +# + +LWL_ACTIVESHIFT = 31 +LWL_DNSENSESHIFT = 30 +LWL_UPSENSESHIFT = 29 +LWL_OCTVALSHIFT = 27 +LWL_PORTSHIFT = 24 + +LWL_PORTMASK = (7<=length): + print("ERROR: Cannot find signature\r") + exit(1) + + # We have the base address, so get the variables themselves + # ========================================================= + downwordaddr=baseaddr+downwordaddr+4 + upwordaddr=downwordaddr+4 + downword=LWL_ACTIVE + + # Now wake up the link...keep on trying if it goes down + # ===================================================== + while True: + ocd.writeVariable(downwordaddr, downword) + upword = ocd.readVariable(upwordaddr) + if (upword&LWL_ACTIVE!=0): + print("==Link Activated\r") + break + except (BrokenPipeError, ConnectionRefusedError, ConnectionResetError) as e: + raise e + + # Now run the comms loop until something fails + # ============================================ + try: + while True: + ocd.writeVariable(downwordaddr, downword) + upword = ocd.readVariable(upwordaddr) + if (upword&LWL_ACTIVE==0): + print("\r==Link Deactivated\r") + break + if kbhit(): + charin = sys.stdin.read(1) + if (ord(charin)==3): + sys.exit(0) + if (downword&LWL_DNSENSEBIT): + downword=(downword&LWL_UPSENSEBIT) + else: + downword=(downword&LWL_UPSENSEBIT)|LWL_DNSENSEBIT + downword|=(LWL_PORT_CONSOLE<>LWL_PORTSHIFT + if (incomingPort==LWL_PORT_CONSOLE): + incomingBytes=(upword&LWL_OCTVALMASK)>>LWL_OCTVALSHIFT + if (incomingBytes>=1): dooutput(upword&255); + if (incomingBytes>=2): dooutput((upword>>8)&255); + if (incomingBytes==3): dooutput((upword>>16)&255); + + if (downword&LWL_UPSENSEBIT): + downword = downword&~LWL_UPSENSEBIT + else: + downword = downword|LWL_UPSENSEBIT + except (ConnectionResetError, ConnectionResetError, BrokenPipeError) as e: + print("\r==Link Lost\r") + raise e + + except (BrokenPipeError, ConnectionRefusedError, ConnectionResetError) as e: + time.sleep(1) + continue + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) +