diff --git a/miscutils/setserial.c b/miscutils/setserial.c new file mode 100644 index 000000000..501beaacc --- /dev/null +++ b/miscutils/setserial.c @@ -0,0 +1,763 @@ +/* vi: set sw=4 ts=4: */ +/* + * setserial implementation for busybox + * + * + * Copyright (C) 2011 Marek Bečka + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ + +//config:config SETSERIAL +//config: bool "setserial" +//config: default y +//config: depends on PLATFORM_LINUX +//config: help +//config: Retrieve or set Linux serial port. + +//applet:IF_SETSERIAL(APPLET(setserial, BB_DIR_BIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_SETSERIAL) += setserial.o + +#include "libbb.h" +#include + +#ifndef PORT_UNKNOWN +# define PORT_UNKNOWN 0 +#endif +#ifndef PORT_8250 +# define PORT_8250 1 +#endif +#ifndef PORT_16450 +# define PORT_16450 2 +#endif +#ifndef PORT_16550 +# define PORT_16550 3 +#endif +#ifndef PORT_16550A +# define PORT_16550A 4 +#endif +#ifndef PORT_CIRRUS +# define PORT_CIRRUS 5 +#endif +#ifndef PORT_16650 +# define PORT_16650 6 +#endif +#ifndef PORT_16650V2 +# define PORT_16650V2 7 +#endif +#ifndef PORT_16750 +# define PORT_16750 8 +#endif +#ifndef PORT_STARTECH +# define PORT_STARTECH 9 +#endif +#ifndef PORT_16C950 +# define PORT_16C950 10 +#endif +#ifndef PORT_16654 +# define PORT_16654 11 +#endif +#ifndef PORT_16850 +# define PORT_16850 12 +#endif +#ifndef PORT_RSA +# define PORT_RSA 13 +#endif +#ifndef PORT_NS16550A +# define PORT_NS16550A 14 +#endif +#ifndef PORT_XSCALE +# define PORT_XSCALE 15 +#endif +#ifndef PORT_RM9000 +# define PORT_RM9000 16 +#endif +#ifndef PORT_OCTEON +# define PORT_OCTEON 17 +#endif +#ifndef PORT_AR7 +# define PORT_AR7 18 +#endif +#ifndef PORT_U6_16550A +# define PORT_U6_16550A 19 +#endif + +#ifndef ASYNCB_HUP_NOTIFY +# define ASYNCB_HUP_NOTIFY 0 +#endif +#ifndef ASYNCB_FOURPORT +# define ASYNCB_FOURPORT 1 +#endif +#ifndef ASYNCB_SAK +# define ASYNCB_SAK 2 +#endif +#ifndef ASYNCB_SPLIT_TERMIOS +# define ASYNCB_SPLIT_TERMIOS 3 +#endif +#ifndef ASYNCB_SPD_HI +# define ASYNCB_SPD_HI 4 +#endif +#ifndef ASYNCB_SPD_VHI +# define ASYNCB_SPD_VHI 5 +#endif +#ifndef ASYNCB_SKIP_TEST +# define ASYNCB_SKIP_TEST 6 +#endif +#ifndef ASYNCB_AUTO_IRQ +# define ASYNCB_AUTO_IRQ 7 +#endif +#ifndef ASYNCB_SESSION_LOCKOUT +# define ASYNCB_SESSION_LOCKOUT 8 +#endif +#ifndef ASYNCB_PGRP_LOCKOUT +# define ASYNCB_PGRP_LOCKOUT 9 +#endif +#ifndef ASYNCB_CALLOUT_NOHUP +# define ASYNCB_CALLOUT_NOHUP 10 +#endif +#ifndef ASYNCB_SPD_SHI +# define ASYNCB_SPD_SHI 12 +#endif +#ifndef ASYNCB_LOW_LATENCY +# define ASYNCB_LOW_LATENCY 13 +#endif +#ifndef ASYNCB_BUGGY_UART +# define ASYNCB_BUGGY_UART 14 +#endif + +#ifndef ASYNC_HUP_NOTIFY +# define ASYNC_HUP_NOTIFY (1U << ASYNCB_HUP_NOTIFY) +#endif +#ifndef ASYNC_FOURPORT +# define ASYNC_FOURPORT (1U << ASYNCB_FOURPORT) +#endif +#ifndef ASYNC_SAK +# define ASYNC_SAK (1U << ASYNCB_SAK) +#endif +#ifndef ASYNC_SPLIT_TERMIOS +# define ASYNC_SPLIT_TERMIOS (1U << ASYNCB_SPLIT_TERMIOS) +#endif +#ifndef ASYNC_SPD_HI +# define ASYNC_SPD_HI (1U << ASYNCB_SPD_HI) +#endif +#ifndef ASYNC_SPD_VHI +# define ASYNC_SPD_VHI (1U << ASYNCB_SPD_VHI) +#endif +#ifndef ASYNC_SKIP_TEST +# define ASYNC_SKIP_TEST (1U << ASYNCB_SKIP_TEST) +#endif +#ifndef ASYNC_AUTO_IRQ +# define ASYNC_AUTO_IRQ (1U << ASYNCB_AUTO_IRQ) +#endif +#ifndef ASYNC_SESSION_LOCKOUT +# define ASYNC_SESSION_LOCKOUT (1U << ASYNCB_SESSION_LOCKOUT) +#endif +#ifndef ASYNC_PGRP_LOCKOUT +# define ASYNC_PGRP_LOCKOUT (1U << ASYNCB_PGRP_LOCKOUT) +#endif +#ifndef ASYNC_CALLOUT_NOHUP +# define ASYNC_CALLOUT_NOHUP (1U << ASYNCB_CALLOUT_NOHUP) +#endif +#ifndef ASYNC_SPD_SHI +# define ASYNC_SPD_SHI (1U << ASYNCB_SPD_SHI) +#endif +#ifndef ASYNC_LOW_LATENCY +# define ASYNC_LOW_LATENCY (1U << ASYNCB_LOW_LATENCY) +#endif +#ifndef ASYNC_BUGGY_UART +# define ASYNC_BUGGY_UART (1U << ASYNCB_BUGGY_UART) +#endif + +#ifndef ASYNC_SPD_CUST +# define ASYNC_SPD_CUST (ASYNC_SPD_HI|ASYNC_SPD_VHI) +#endif +#ifndef ASYNC_SPD_WARP +# define ASYNC_SPD_WARP (ASYNC_SPD_HI|ASYNC_SPD_SHI) +#endif +#ifndef ASYNC_SPD_MASK +# define ASYNC_SPD_MASK (ASYNC_SPD_HI|ASYNC_SPD_VHI|ASYNC_SPD_SHI) +#endif + +#ifndef ASYNC_CLOSING_WAIT_INF +# define ASYNC_CLOSING_WAIT_INF 0 +#endif +#ifndef ASYNC_CLOSING_WAIT_NONE +# define ASYNC_CLOSING_WAIT_NONE 65535 +#endif + +#ifndef _LINUX_SERIAL_H +struct serial_struct { + int type; + int line; + unsigned int port; + int irq; + int flags; + int xmit_fifo_size; + int custom_divisor; + int baud_base; + unsigned short close_delay; + char io_type; + char reserved_char[1]; + int hub6; + unsigned short closing_wait; /* time to wait before closing */ + unsigned short closing_wait2; /* no longer used... */ + unsigned char *iomem_base; + unsigned short iomem_reg_shift; + unsigned int port_high; + unsigned long iomap_base; /* cookie passed into ioremap */ +}; +#endif + +//usage:#define setserial_trivial_usage +//usage: "[-gabGvzV] DEVICE [PARAMETER [ARG]]..." +//usage:#define setserial_full_usage "\n\n" +//usage: "Request or set Linux serial port information\n\n" +//usage: "Options:\n" +//usage: " -g Interpret parameters as list of devices for reporting" +//usage: " -a Print all available information\n" +//usage: " -b Print summary information\n" +//usage: " -G Print in form which can be fed back\n" +//usage: " to setserial as command line parameters\n" +//usage: " -z Zero out serial flags before setting\n" +//usage: " -v Verbose\n" +//usage: "\n" +//usage: "Parameters: (* = takes an argument, ^ = can be turned off by preceding ^)\n" +//usage: " *port, *irq, *divisor, *uart, *baund_base, *close_delay, *closing_wait,\n" +//usage: " ^fourport, ^auto_irq, ^skip_test, ^sak, ^session_lockout, ^pgrp_lockout,\n" +//usage: " ^callout_nohup, ^split_termios, ^hup_notify, ^low_latency, autoconfig,\n" +//usage: " spd_normal, spd_hi, spd_vhi, spd_shi, spd_warp, spd_cust\n" +//usage: "\n" +//usage: "UART types:\n" +//usage: " unknown, 8250, 16450, 16550, 16550A, Cirrus, 16650, 16650V2, 16750,\n" +//usage: " 16950, 16954, 16654, 16850, RSA, NS16550A, XSCALE, RM9000, OCTEON, AR7,\n" +//usage: " U6_16550A" + +#define OPT_PRINT_SUMMARY (1 << 0) +#define OPT_PRINT_FEDBACK (1 << 1) +#define OPT_PRINT_ALL (1 << 2) +#define OPT_VERBOSE (1 << 3) +#define OPT_ZERO (1 << 4) +#define OPT_GET (1 << 5) + +#define OPT_MODE_MASK \ + (OPT_PRINT_ALL | OPT_PRINT_SUMMARY | OPT_PRINT_FEDBACK) + +enum print_mode +{ + PRINT_NORMAL = 0, + PRINT_SUMMARY = (1 << 0), + PRINT_FEDBACK = (1 << 1), + PRINT_ALL = (1 << 2), +}; + +#define CTL_SET (1 << 0) +#define CTL_CONFIG (1 << 1) +#define CTL_GET (1 << 2) +#define CTL_CLOSE (1 << 3) +#define CTL_NODIE (1 << 4) + +static const char serial_types[] = + "unknown\0" /* 0 */ + "8250\0" /* 1 */ + "16450\0" /* 2 */ + "16550\0" /* 3 */ + "16550A\0" /* 4 */ + "Cirrus\0" /* 5 */ + "16650\0" /* 6 */ + "16650V2\0" /* 7 */ + "16750\0" /* 8 */ + "16950\0" /* 9 UNIMPLEMENTED: also know as "16950/954" */ + "16954\0" /* 10 */ + "16654\0" /* 11 */ + "16850\0" /* 12 */ + "RSA\0" /* 13 */ +#ifndef SETSERIAL_BASE + "NS16550A\0" /* 14 */ + "XSCALE\0" /* 15 */ + "RM9000\0" /* 16 */ + "OCTEON\0" /* 17 */ + "AR7\0" /* 18 */ + "U6_16550A\0" /* 19 */ +#endif +; + +#ifndef SETSERIAL_BASE +# define MAX_SERIAL_TYPE 19 +#else +# define MAX_SERIAL_TYPE 13 +#endif + +static const char commands[] = + "spd_normal\0" + "spd_hi\0" + "spd_vhi\0" + "spd_shi\0" + "spd_warp\0" + "spd_cust\0" + + "sak\0" + "fourport\0" + "hup_notify\0" + "skip_test\0" + "auto_irq\0" + "split_termios\0" + "session_lockout\0" + "pgrp_lockout\0" + "callout_nohup\0" + "low_latency\0" + + "port\0" + "irq\0" + "divisor\0" + "uart\0" + "baund_base\0" + "close_delay\0" + "closing_wait\0" + + "autoconfig\0" +; + +enum +{ + CMD_SPD_NORMAL = 0, + CMD_SPD_HI, + CMD_SPD_VHI, + CMD_SPD_SHI, + CMD_SPD_WARP, + CMD_SPD_CUST, + + CMD_FLAG_SAK, + CMD_FLAG_FOURPORT, + CMD_FLAG_NUP_NOTIFY, + CMD_FLAG_SKIP_TEST, + CMD_FLAG_AUTO_IRQ, + CMD_FLAG_SPLIT_TERMIOS, + CMD_FLAG_SESSION_LOCKOUT, + CMD_FLAG_PGRP_LOCKOUT, + CMD_FLAG_CALLOUT_NOHUP, + CMD_FLAG_LOW_LATENCY, + + CMD_PORT, + CMD_IRQ, + CMD_DIVISOR, + CMD_UART, + CMD_BASE, + CMD_DELAY, + CMD_WAIT, + + CMD_AUTOCONFIG, + + CMD_FLAG_FIRST = CMD_FLAG_SAK, + CMD_FLAG_LAST = CMD_FLAG_LOW_LATENCY, +}; + +static bool cmd_noprint(int cmd) +{ + return (cmd >= CMD_FLAG_SKIP_TEST && cmd <= CMD_FLAG_CALLOUT_NOHUP); +} + +static bool cmd_is_flag(int cmd) +{ + return (cmd >= CMD_FLAG_FIRST && cmd <= CMD_FLAG_LAST); +} + +static bool cmd_need_arg(int cmd) +{ + return (cmd >= CMD_PORT && cmd <= CMD_WAIT); +} + +#define ALL_SPD ( \ + ASYNC_SPD_HI | ASYNC_SPD_VHI | ASYNC_SPD_SHI | \ + ASYNC_SPD_WARP | ASYNC_SPD_CUST \ + ) + +#define ALL_FLAGS ( \ + ASYNC_SAK | ASYNC_FOURPORT | ASYNC_HUP_NOTIFY | \ + ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ | ASYNC_SPLIT_TERMIOS | \ + ASYNC_SESSION_LOCKOUT | ASYNC_PGRP_LOCKOUT | ASYNC_CALLOUT_NOHUP | \ + ASYNC_LOW_LATENCY \ + ) + +#if (ALL_SPD | ALL_FLAGS) > 0xffff +# error "Unexpected flags size" +#endif + +static const uint16_t setbits[CMD_FLAG_LAST + 1] = +{ + 0, + ASYNC_SPD_HI, + ASYNC_SPD_VHI, + ASYNC_SPD_SHI, + ASYNC_SPD_WARP, + ASYNC_SPD_CUST, + + ASYNC_SAK, + ASYNC_FOURPORT, + ASYNC_HUP_NOTIFY, + ASYNC_SKIP_TEST, + ASYNC_AUTO_IRQ, + ASYNC_SPLIT_TERMIOS, + ASYNC_SESSION_LOCKOUT, + ASYNC_PGRP_LOCKOUT, + ASYNC_CALLOUT_NOHUP, + ASYNC_LOW_LATENCY +}; + +static const char STR_INFINITE[] = "infinite"; +static const char STR_NONE[] = "none"; + +static const char *uart_type(int type) +{ + if (type > MAX_SERIAL_TYPE) + return "undefined"; + + return nth_string(serial_types, type); +} + +/* libbb candidate */ +static int index_in_strings_case_insensitive(const char *strings, const char *key) +{ + int idx = 0; + + while (*strings) { + if (strcasecmp(strings, key) == 0) { + return idx; + } + strings += strlen(strings) + 1; /* skip NUL */ + idx++; + } + return -1; +} + +static int uart_id(const char *name) +{ + return index_in_strings_case_insensitive(serial_types, name); +} + +static const char *get_spd(int flags, enum print_mode mode) +{ + int idx; + + switch (flags & ASYNC_SPD_MASK) { + case ASYNC_SPD_HI: + idx = CMD_SPD_HI; + break; + case ASYNC_SPD_VHI: + idx = CMD_SPD_VHI; + break; + case ASYNC_SPD_SHI: + idx = CMD_SPD_SHI; + break; + case ASYNC_SPD_WARP: + idx = CMD_SPD_WARP; + break; + case ASYNC_SPD_CUST: + idx = CMD_SPD_CUST; + break; + default: + if (mode < PRINT_FEDBACK) + return NULL; + idx = CMD_SPD_NORMAL; + } + + return nth_string(commands, idx); +} + +static int get_numeric(const char *arg) +{ + return bb_strtol(arg, NULL, 0); +} + +static int get_wait(const char *arg) +{ + if (strcasecmp(arg, STR_NONE) == 0) + return ASYNC_CLOSING_WAIT_NONE; + + if (strcasecmp(arg, STR_INFINITE) == 0) + return ASYNC_CLOSING_WAIT_INF; + + return get_numeric(arg); +} + +static int get_uart(const char *arg) +{ + int uart = uart_id(arg); + + if (uart < 0) + bb_error_msg_and_die("illegal UART type: %s", arg); + + return uart; +} + +static int serial_open(const char *dev, bool quiet) +{ + int fd; + + fd = device_open(dev, O_RDWR | O_NONBLOCK); + if (fd < 0 && !quiet) + bb_simple_perror_msg(dev); + + return fd; +} + +static int serial_ctl(int fd, int ops, struct serial_struct *serinfo) +{ + int ret = 0; + const char *err; + + if (ops & CTL_SET) { + ret = ioctl(fd, TIOCSSERIAL, serinfo); + if (ret < 0) { + err = "can't set serial info"; + goto fail; + } + } + + if (ops & CTL_CONFIG) { + ret = ioctl(fd, TIOCSERCONFIG); + if (ret < 0) { + err = "can't autoconfigure port"; + goto fail; + } + } + + if (ops & CTL_GET) { + ret = ioctl(fd, TIOCGSERIAL, serinfo); + if (ret < 0) { + err = "can't get serial info"; + goto fail; + } + } + nodie: + if (ops & CTL_CLOSE) + close(fd); + + return ret; + fail: + bb_simple_perror_msg(err); + if (ops & CTL_NODIE) + goto nodie; + exit(EXIT_FAILURE); +} + +static void print_flag(const char **prefix, const char *flag) +{ + printf("%s%s", *prefix, flag); + *prefix = " "; +} + +static void print_serial_flags(int serial_flags, enum print_mode mode, + const char *prefix, const char *postfix) +{ + int i; + const char *spd, *pr; + + pr = prefix; + + spd = get_spd(serial_flags, mode); + if (spd) + print_flag(&pr, spd); + + for (i = CMD_FLAG_FIRST; i <= CMD_FLAG_LAST; i++) { + if ((serial_flags & setbits[i]) + && (mode > PRINT_SUMMARY || !cmd_noprint(i)) + ) { + print_flag(&pr, nth_string(commands, i)); + } + } + + puts(pr == prefix ? "" : postfix); +} + +static void print_closing_wait(unsigned int closing_wait) +{ + switch (closing_wait) { + case ASYNC_CLOSING_WAIT_NONE: + puts(STR_NONE); + break; + case ASYNC_CLOSING_WAIT_INF: + puts(STR_INFINITE); + break; + default: + printf("%u\n", closing_wait); + } +} + +static void serial_get(const char *device, enum print_mode mode) +{ + int fd, ret; + const char *uart, *prefix, *postfix; + struct serial_struct serinfo; + + fd = serial_open(device, /*quiet:*/ mode == PRINT_SUMMARY); + if (fd < 0) + return; + + ret = serial_ctl(fd, CTL_GET | CTL_CLOSE | CTL_NODIE, &serinfo); + if (ret < 0) + return; + + uart = uart_type(serinfo.type); + prefix = ", Flags: "; + postfix = ""; + + switch (mode) { + case PRINT_NORMAL: + printf("%s, UART: %s, Port: 0x%.4x, IRQ: %d", + device, uart, serinfo.port, serinfo.irq); + break; + case PRINT_SUMMARY: + if (!serinfo.type) + return; + printf("%s at 0x%.4x (irq = %d) is a %s", + device, serinfo.port, serinfo.irq, uart); + prefix = " ("; + postfix = ")"; + break; + case PRINT_FEDBACK: + printf("%s uart %s port 0x%.4x irq %d baud_base %d", device, + uart, serinfo.port, serinfo.irq, serinfo.baud_base); + prefix = " "; + break; + case PRINT_ALL: + printf("%s, Line %d, UART: %s, Port: 0x%.4x, IRQ: %d\n", + device, serinfo.line, uart, serinfo.port, serinfo.irq); + printf("\tBaud_base: %d, close_delay: %u, divisor: %d\n", + serinfo.baud_base, serinfo.close_delay, + serinfo.custom_divisor); + printf("\tclosing_wait: "); + print_closing_wait(serinfo.closing_wait); + prefix = "\tFlags: "; + postfix = "\n"; + break; + default: + assert(0); + } + + print_serial_flags(serinfo.flags, mode, prefix, postfix); +} + +static int find_cmd(const char *cmd) +{ + int idx; + + idx = index_in_strings_case_insensitive(commands, cmd); + if (idx < 0) + bb_error_msg_and_die("invalid flag: %s", cmd); + + return idx; +} + +static void serial_set(char **arg, int opts) +{ + struct serial_struct serinfo; + int cmd; + const char *word; + int fd; + + fd = serial_open(*arg++, /*quiet:*/ false); + if (fd < 0) + exit(201); + + serial_ctl(fd, CTL_GET, &serinfo); + + if (opts & OPT_ZERO) + serinfo.flags = 0; + + while (*arg) { + int invert; + + word = *arg++; + invert = (*word == '^'); + word += invert; + + cmd = find_cmd(word); + + if (*arg == NULL && cmd_need_arg(cmd)) + bb_error_msg_and_die(bb_msg_requires_arg, word); + + if (invert && !cmd_is_flag(cmd)) + bb_error_msg_and_die("can't invert %s", word); + + switch (cmd) { + case CMD_SPD_NORMAL: + case CMD_SPD_HI: + case CMD_SPD_VHI: + case CMD_SPD_SHI: + case CMD_SPD_WARP: + case CMD_SPD_CUST: + serinfo.flags &= ASYNC_SPD_MASK; + /* fallthrough */ + case CMD_FLAG_SAK: + case CMD_FLAG_FOURPORT: + case CMD_FLAG_NUP_NOTIFY: + case CMD_FLAG_SKIP_TEST: + case CMD_FLAG_AUTO_IRQ: + case CMD_FLAG_SPLIT_TERMIOS: + case CMD_FLAG_SESSION_LOCKOUT: + case CMD_FLAG_PGRP_LOCKOUT: + case CMD_FLAG_CALLOUT_NOHUP: + case CMD_FLAG_LOW_LATENCY: + if (invert) + serinfo.flags &= ~setbits[cmd]; + else + serinfo.flags |= setbits[cmd]; + break; + case CMD_PORT: + serinfo.port = get_numeric(*arg++); + break; + case CMD_IRQ: + serinfo.irq = get_numeric(*arg++); + break; + case CMD_DIVISOR: + serinfo.custom_divisor = get_numeric(*arg++); + break; + case CMD_UART: + serinfo.type = get_uart(*arg++); + break; + case CMD_BASE: + serinfo.baud_base = get_numeric(*arg++); + break; + case CMD_DELAY: + serinfo.close_delay = get_numeric(*arg++); + break; + case CMD_WAIT: + serinfo.closing_wait = get_wait(*arg++); + break; + case CMD_AUTOCONFIG: + serial_ctl(fd, CTL_SET | CTL_CONFIG | CTL_GET, &serinfo); + break; + default: + assert(0); + } + } + + serial_ctl(fd, CTL_SET | CTL_CLOSE, &serinfo); +} + +int setserial_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int setserial_main(int argc UNUSED_PARAM, char **argv) +{ + int opts; + + opt_complementary = "-1:b-aG:G-ab:a-bG"; + opts = getopt32(argv, "bGavzg"); + argv += optind; + + if (!argv[1]) /* one arg only? */ + opts |= OPT_GET; + + if (!(opts & OPT_GET)) { + serial_set(argv, opts); + argv[1] = NULL; + } + + if (opts & (OPT_VERBOSE | OPT_GET)) { + do { + serial_get(*argv++, opts & OPT_MODE_MASK); + } while (*argv); + } + + return EXIT_SUCCESS; +}