From be038ce406beb630efb2f29e2837eac33beebc85 Mon Sep 17 00:00:00 2001 From: fangpeina Date: Tue, 16 Dec 2025 23:27:53 +0800 Subject: [PATCH] system: Add stty utility for terminal control This commit adds a system utily 'stty' to NuttX, providing a standard Unix-like interface for configuring terminal (TTY) device settings. The command allows runtime configuration of termios attributes for serial ports and other character devices. Usage examples: # Set raw mode on ttyS0 (for binary communication) nsh> stty -F /dev/ttyS0 raw -echo # Set console mode for interactive terminal nsh> stty -F /dev/ttyS0 cooked # Display current settings nsh> stty -F /dev/ttyS0 # Configure stdin (if it's a TTY) nsh> stty echo icanon # Set baudrate (if driver support) nsh> stty -F /dev/ttyS1 speed 115200 Signed-off-by: fangpeina --- system/stty/CMakeLists.txt | 33 ++++ system/stty/Kconfig | 26 +++ system/stty/Make.defs | 24 +++ system/stty/Makefile | 32 +++ system/stty/stty_main.c | 390 +++++++++++++++++++++++++++++++++++++ 5 files changed, 505 insertions(+) create mode 100644 system/stty/CMakeLists.txt create mode 100644 system/stty/Kconfig create mode 100644 system/stty/Make.defs create mode 100644 system/stty/Makefile create mode 100644 system/stty/stty_main.c diff --git a/system/stty/CMakeLists.txt b/system/stty/CMakeLists.txt new file mode 100644 index 000000000..55108d2e1 --- /dev/null +++ b/system/stty/CMakeLists.txt @@ -0,0 +1,33 @@ +# ############################################################################## +# apps/system/stty/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_SYSTEM_STTY) + nuttx_add_application( + MODULE + ${CONFIG_SYSTEM_STTY} + NAME + stty + STACKSIZE + ${CONFIG_SYSTEM_STTY_STACKSIZE} + PRIORITY + ${CONFIG_SYSTEM_STTY_PRIORITY} + SRCS + stty_main.c) +endif() diff --git a/system/stty/Kconfig b/system/stty/Kconfig new file mode 100644 index 000000000..29bb3045b --- /dev/null +++ b/system/stty/Kconfig @@ -0,0 +1,26 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig SYSTEM_STTY + tristate "stty - set terminal options" + default n + depends on SERIAL_TERMIOS + ---help--- + Enable the stty command to configure terminal (TTY) settings. + This command allows you to view and change terminal attributes + such as echo, canonical mode, and line endings. + +if SYSTEM_STTY + +config SYSTEM_STTY_PRIORITY + int "stty task priority" + default 100 + +config SYSTEM_STTY_STACKSIZE + int "stty stack size" + default DEFAULT_TASK_STACKSIZE + +endif # SYSTEM_STTY + diff --git a/system/stty/Make.defs b/system/stty/Make.defs new file mode 100644 index 000000000..b149ed6d5 --- /dev/null +++ b/system/stty/Make.defs @@ -0,0 +1,24 @@ +############################################################################ +# apps/system/stty/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_STTY),) +CONFIGURED_APPS += $(APPDIR)/system/stty +endif + diff --git a/system/stty/Makefile b/system/stty/Makefile new file mode 100644 index 000000000..2e6cd2ee1 --- /dev/null +++ b/system/stty/Makefile @@ -0,0 +1,32 @@ +############################################################################ +# apps/system/stty/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# stty - set terminal options + +PROGNAME = stty +PRIORITY = $(CONFIG_SYSTEM_STTY_PRIORITY) +STACKSIZE = $(CONFIG_SYSTEM_STTY_STACKSIZE) +MODULE = $(CONFIG_SYSTEM_STTY) + +MAINSRC = stty_main.c + +include $(APPDIR)/Application.mk diff --git a/system/stty/stty_main.c b/system/stty/stty_main.c new file mode 100644 index 000000000..57d0e6052 --- /dev/null +++ b/system/stty/stty_main.c @@ -0,0 +1,390 @@ +/**************************************************************************** + * apps/system/stty/stty_main.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define stty_error(...) dprintf(STDERR_FILENO, __VA_ARGS__) +#define FLAG_STR(flags, flag) ((flags) & (flag) ? "" : "-") + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void print_usage(FAR const char *progname) +{ + printf("Usage: %s [-F device] [settings...]\n", progname); + printf("\nSettings:\n"); + printf(" raw - Set raw mode (no processing)\n"); + printf(" cooked - Set cooked mode (canonical)\n"); + printf(" echo - Enable echo\n"); + printf(" -echo - Disable echo\n"); + printf(" icanon - Enable canonical mode\n"); + printf(" -icanon - Disable canonical mode\n"); + printf(" icrnl - Map CR to NL on input\n"); + printf(" -icrnl - Don't map CR to NL\n"); + printf(" onlcr - Map NL to CR-NL on output\n"); + printf(" -onlcr - Don't map NL to CR-NL\n"); + printf(" opost - Enable output processing\n"); + printf(" -opost - Disable output processing\n"); + printf(" isig - Enable signal characters\n"); + printf(" -isig - Disable signal characters\n"); + printf(" speed N - Set baud rate\n"); + printf(" min N - Set minimum characters for read (0-255)\n"); + printf(" time N - Set read timeout in 0.1s units (0-255)\n"); + printf("\nExamples:\n"); + printf(" %s -F /dev/ttyS0 raw -echo", progname); + printf(" # Raw mode, no echo\n"); + printf(" %s -F /dev/ttyS0 cooked", progname); + printf(" # Interactive terminal mode\n"); +} + +static int parse_numeric_param(int argc, FAR char *argv[], FAR int *idx, + int min, int max) +{ + int val; + if (*idx + 1 >= argc) + { + stty_error("ERROR: %s requires a value\n", argv[*idx]); + return -EINVAL; + } + + val = atoi(argv[++(*idx)]); + if (val < min || val > max) + { + stty_error("ERROR: Invalid value: %d (must be %d-%d)\n", + val, min, max); + return -EINVAL; + } + + return val; +} + +static int apply_settings(int fd, int argc, FAR char *argv[], int idx) +{ + struct termios tio; + int val; + int ret; + + /* Get current settings */ + + ret = tcgetattr(fd, &tio); + if (ret < 0) + { + stty_error("ERROR: tcgetattr failed: %d\n", errno); + return -errno; + } + + /* Parse and apply settings */ + + for (; idx < argc; idx++) + { + FAR const char *arg = argv[idx]; + + if (strcmp(arg, "raw") == 0) + { + cfmakeraw(&tio); + } + else if (strcmp(arg, "cooked") == 0) + { + /* Set cooked mode (canonical with echo) */ + + tio.c_lflag |= ICANON | ECHO | ISIG; + tio.c_oflag |= OPOST | ONLCR; + tio.c_iflag |= ICRNL; + } + else if (strcmp(arg, "echo") == 0) + { + tio.c_lflag |= ECHO; + } + else if (strcmp(arg, "-echo") == 0) + { + tio.c_lflag &= ~ECHO; + } + else if (strcmp(arg, "icanon") == 0) + { + tio.c_lflag |= ICANON; + } + else if (strcmp(arg, "-icanon") == 0) + { + tio.c_lflag &= ~ICANON; + } + else if (strcmp(arg, "icrnl") == 0) + { + tio.c_iflag |= ICRNL; + } + else if (strcmp(arg, "-icrnl") == 0) + { + tio.c_iflag &= ~ICRNL; + } + else if (strcmp(arg, "onlcr") == 0) + { + tio.c_oflag |= ONLCR; + } + else if (strcmp(arg, "-onlcr") == 0) + { + tio.c_oflag &= ~ONLCR; + } + else if (strcmp(arg, "opost") == 0) + { + tio.c_oflag |= OPOST; + } + else if (strcmp(arg, "-opost") == 0) + { + tio.c_oflag &= ~OPOST; + } + else if (strcmp(arg, "isig") == 0) + { + tio.c_lflag |= ISIG; + } + else if (strcmp(arg, "-isig") == 0) + { + tio.c_lflag &= ~ISIG; + } + else if (strcmp(arg, "inlcr") == 0) + { + tio.c_iflag |= INLCR; + } + else if (strcmp(arg, "-inlcr") == 0) + { + tio.c_iflag &= ~INLCR; + } + else if (strcmp(arg, "igncr") == 0) + { + tio.c_iflag |= IGNCR; + } + else if (strcmp(arg, "-igncr") == 0) + { + tio.c_iflag &= ~IGNCR; + } + else if (strcmp(arg, "ocrnl") == 0) + { + tio.c_oflag |= OCRNL; + } + else if (strcmp(arg, "-ocrnl") == 0) + { + tio.c_oflag &= ~OCRNL; + } + else if (strcmp(arg, "speed") == 0) + { + val = parse_numeric_param(argc, argv, &idx, 0, 4000000); + if (val < 0) + { + return val; + } + + if (cfsetspeed(&tio, val) < 0) + { + stty_error("ERROR: Invalid speed value: %d\n", val); + return -errno; + } + } + else if (strcmp(arg, "min") == 0) + { + val = parse_numeric_param(argc, argv, &idx, 0, 255); + if (val < 0) + { + return val; + } + + tio.c_cc[VMIN] = val; + } + else if (strcmp(arg, "time") == 0) + { + val = parse_numeric_param(argc, argv, &idx, 0, 255); + if (val < 0) + { + return val; + } + + tio.c_cc[VTIME] = val; + } + else + { + stty_error("WARNING: Unknown setting: %s\n", arg); + } + } + + /* Apply the new settings */ + + ret = tcsetattr(fd, TCSANOW, &tio); + if (ret < 0) + { + stty_error("ERROR: tcsetattr failed: %d\n", errno); + return -errno; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + FAR const char *device = NULL; + int fd = STDIN_FILENO; + int ret; + int opt; + int idx; + + /* Parse -F option for device path */ + + while ((opt = getopt(argc, argv, "F:h")) != -1) + { + switch (opt) + { + case 'F': + device = optarg; + break; + + case 'h': + print_usage(argv[0]); + return 0; + + default: + print_usage(argv[0]); + return -EINVAL; + } + } + + idx = optind; + + /* Open device if specified, or use stdin as default */ + + if (device == NULL) + { + device = "stdin"; + } + else + { + fd = open(device, O_RDWR | O_NONBLOCK); + if (fd < 0) + { + stty_error("ERROR: Failed to open %s: %d\n", device, errno); + return -errno; + } + } + + if (!isatty(fd)) + { + stty_error("ERROR: %s is not a TTY\n", device); + if (fd != STDIN_FILENO) + { + close(fd); + } + + return -EINVAL; + } + + /* If no settings provided, just display current settings */ + + if (idx >= argc) + { + struct termios tio; + speed_t speed; + + memset(&tio, 0, sizeof(struct termios)); + ret = tcgetattr(fd, &tio); + if (ret < 0) + { + stty_error("ERROR: tcgetattr failed: %d\n", errno); + if (fd != STDIN_FILENO) + { + close(fd); + } + + return -errno; + } + + speed = cfgetispeed(&tio); + + printf("Terminal settings for %s:\n", device); + if (speed <= 0) + { + printf(" speed unknown or not supported by driver; "); + } + else + { + printf(" speed %lu baud; ", speed); + } + + printf("min = %d; time = %d;\n", tio.c_cc[VMIN], tio.c_cc[VTIME]); + + printf(" c_iflag: 0x%08x %sicrnl %signcr %sinlcr\n", tio.c_iflag, + FLAG_STR(tio.c_iflag, ICRNL), + FLAG_STR(tio.c_iflag, IGNCR), + FLAG_STR(tio.c_iflag, INLCR)); + printf(" c_oflag: 0x%08x %sopost %sonlcr %socrnl\n", tio.c_oflag, + FLAG_STR(tio.c_oflag, OPOST), + FLAG_STR(tio.c_oflag, ONLCR), + FLAG_STR(tio.c_oflag, OCRNL)); + printf(" c_lflag: 0x%08x %secho %sicanon %sisig\n", tio.c_lflag, + FLAG_STR(tio.c_lflag, ECHO), + FLAG_STR(tio.c_lflag, ICANON), + FLAG_STR(tio.c_lflag, ISIG)); + printf(" c_cflag: 0x%08x %sclocal %scread %scstopb\n", tio.c_cflag, + FLAG_STR(tio.c_cflag, CLOCAL), + FLAG_STR(tio.c_cflag, CREAD), + FLAG_STR(tio.c_cflag, CSTOPB)); + } + else + { + /* Apply settings */ + + ret = apply_settings(fd, argc, argv, idx); + if (ret < 0) + { + if (fd != STDIN_FILENO) + { + close(fd); + } + + return ret; + } + + printf("Settings applied to %s\n", device); + } + + if (fd != STDIN_FILENO) + { + close(fd); + } + + return 0; +}