/* 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: select 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" //usage: "\n" //usage: " -g Interpret parameters as list of devices for reporting\n" //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, *baud_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" "baud_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; }