From patchwork Sat Apr 1 22:21:15 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Pitre X-Patchwork-Id: 96580 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp1303568qgd; Sat, 1 Apr 2017 15:21:35 -0700 (PDT) X-Received: by 10.98.64.9 with SMTP id n9mr9350610pfa.211.1491085295790; Sat, 01 Apr 2017 15:21:35 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 35si9211646pgx.238.2017.04.01.15.21.35; Sat, 01 Apr 2017 15:21:35 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752047AbdDAWVd (ORCPT + 24 others); Sat, 1 Apr 2017 18:21:33 -0400 Received: from alt22.smtp-out.videotron.ca ([70.80.0.73]:37056 "EHLO alt22.smtp-out.videotron.ca" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751968AbdDAWVa (ORCPT ); Sat, 1 Apr 2017 18:21:30 -0400 Received: from yoda.home ([96.23.157.65]) by Videotron with SMTP id uROhciYOjzCgpuROicjZJO; Sat, 01 Apr 2017 18:21:29 -0400 X-Authority-Analysis: v=2.1 cv=QfzGxpvv c=1 sm=1 tr=0 a=keA3yYpnlypCNW5BNWqu+w==:117 a=keA3yYpnlypCNW5BNWqu+w==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=AzvcPWV-tVgA:10 a=KKAkSRfTAAAA:8 a=Uzn0aNsXRxZ8utFiinYA:9 a=cvBusfyB2V15izCimMoJ:22 Received: from xanadu.home (xanadu.home [192.168.2.2]) by yoda.home (Postfix) with ESMTP id 040B92DA044C; Sat, 1 Apr 2017 18:21:27 -0400 (EDT) From: Nicolas Pitre To: Greg Kroah-Hartman , Jiri Slaby , linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v2 1/5] console: move console_init() out of tty_io.c Date: Sat, 1 Apr 2017 18:21:15 -0400 Message-Id: <20170401222119.25106-2-nicolas.pitre@linaro.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170401222119.25106-1-nicolas.pitre@linaro.org> References: <20170401222119.25106-1-nicolas.pitre@linaro.org> X-CMAE-Envelope: MS4wfCmp5Kh2LvN0c6GUzputIqMzCIeH1iAWt/eXWlsHqfwtT7X1ZsGr2C/tCvdjLFaxvigi81mi8as4S7XcSPLMWxc7HX1rYZJSaMQ2CksbXHIHrSPGZYii n5H0HI4vxOj8EMi0vCLmRyPalbKICmi+0f8p5QyfyKVf7YanlWNrZ7l+VB48buW/eM/raBZAMXl7EYMXXGM7mWc7jSfUomzEGozE+2UXlyr5qcU0NAQSfX6a 2ReU34G9h9/83O6VoY9EpOFXlqaaHP+Hfl7kVppCJnga7UBpbVWx854QmOiqyTzPXf2FeWvUeMuaIyTv3zGFZIatBD2QrS0VDYH6c96JcYQ= Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org All the console driver handling code lives in printk.c. Move console_init() there as well so console support can still be used when the TTY code is configured out. Signed-off-by: Nicolas Pitre --- drivers/tty/tty_io.c | 24 ------------------------ include/linux/console.h | 2 ++ include/linux/tty.h | 7 ++++--- init/main.c | 2 +- kernel/printk/printk.c | 24 ++++++++++++++++++++++++ 5 files changed, 31 insertions(+), 28 deletions(-) -- 2.9.3 diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index e6d1a65108..2100295861 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3578,30 +3578,6 @@ void tty_default_fops(struct file_operations *fops) *fops = tty_fops; } -/* - * Initialize the console device. This is called *early*, so - * we can't necessarily depend on lots of kernel help here. - * Just do some early initializations, and do the complex setup - * later. - */ -void __init console_init(void) -{ - initcall_t *call; - - /* Setup the default TTY line discipline. */ - n_tty_init(); - - /* - * set up the console device so that later boot sequences can - * inform about problems etc.. - */ - call = __con_initcall_start; - while (call < __con_initcall_end) { - (*call)(); - call++; - } -} - static char *tty_devnode(struct device *dev, umode_t *mode) { if (!mode) diff --git a/include/linux/console.h b/include/linux/console.h index 5949d18555..b8920a031a 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -212,4 +212,6 @@ extern bool vgacon_text_force(void); static inline bool vgacon_text_force(void) { return false; } #endif +extern void console_init(void); + #endif /* _LINUX_CONSOLE_H */ diff --git a/include/linux/tty.h b/include/linux/tty.h index 1017e904c0..f1106d7c73 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -390,7 +390,6 @@ static inline bool tty_throttled(struct tty_struct *tty) } #ifdef CONFIG_TTY -extern void console_init(void); extern void tty_kref_put(struct tty_struct *tty); extern struct pid *tty_get_pgrp(struct tty_struct *tty); extern void tty_vhangup_self(void); @@ -402,8 +401,6 @@ extern struct tty_struct *get_current_tty(void); extern int __init tty_init(void); extern const char *tty_name(const struct tty_struct *tty); #else -static inline void console_init(void) -{ } static inline void tty_kref_put(struct tty_struct *tty) { } static inline struct pid *tty_get_pgrp(struct tty_struct *tty) @@ -669,7 +666,11 @@ extern int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p, /* n_tty.c */ extern void n_tty_inherit_ops(struct tty_ldisc_ops *ops); +#ifdef CONFIG_TTY extern void __init n_tty_init(void); +#else +static inline void n_tty_init(void) { } +#endif /* tty_audit.c */ #ifdef CONFIG_AUDIT diff --git a/init/main.c b/init/main.c index f9c9d99482..b9bd0edf21 100644 --- a/init/main.c +++ b/init/main.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 2984fb0f02..3a09406526 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2611,6 +2611,30 @@ int unregister_console(struct console *console) EXPORT_SYMBOL(unregister_console); /* + * Initialize the console device. This is called *early*, so + * we can't necessarily depend on lots of kernel help here. + * Just do some early initializations, and do the complex setup + * later. + */ +void __init console_init(void) +{ + initcall_t *call; + + /* Setup the default TTY line discipline. */ + n_tty_init(); + + /* + * set up the console device so that later boot sequences can + * inform about problems etc.. + */ + call = __con_initcall_start; + while (call < __con_initcall_end) { + (*call)(); + call++; + } +} + +/* * Some boot consoles access data that is in the init section and which will * be discarded after the initcalls have been run. To make sure that no code * will access this data, unregister the boot consoles in a late initcall. From patchwork Sat Apr 1 22:21:16 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Pitre X-Patchwork-Id: 96579 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp1303550qgd; Sat, 1 Apr 2017 15:21:33 -0700 (PDT) X-Received: by 10.84.164.106 with SMTP id m39mr11945813plg.9.1491085293125; Sat, 01 Apr 2017 15:21:33 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 35si9211646pgx.238.2017.04.01.15.21.32; Sat, 01 Apr 2017 15:21:33 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752019AbdDAWVc (ORCPT + 24 others); Sat, 1 Apr 2017 18:21:32 -0400 Received: from alt42.smtp-out.videotron.ca ([23.233.128.29]:5207 "EHLO alt42.smtp-out.videotron.ca" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751955AbdDAWV3 (ORCPT ); Sat, 1 Apr 2017 18:21:29 -0400 Received: from yoda.home ([96.23.157.65]) by Videotron with SMTP id uROhcIAY56cTKuROicqcoq; Sat, 01 Apr 2017 18:21:28 -0400 X-Authority-Analysis: v=2.1 cv=TfA2zUkh c=1 sm=1 tr=0 a=keA3yYpnlypCNW5BNWqu+w==:117 a=keA3yYpnlypCNW5BNWqu+w==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=AzvcPWV-tVgA:10 a=KKAkSRfTAAAA:8 a=p_Yj69bw2ReCMXgs2NQA:9 a=_zDC0-8_D9QA:10 a=cvBusfyB2V15izCimMoJ:22 Received: from xanadu.home (xanadu.home [192.168.2.2]) by yoda.home (Postfix) with ESMTP id 1C3922DA05CA; Sat, 1 Apr 2017 18:21:27 -0400 (EDT) From: Nicolas Pitre To: Greg Kroah-Hartman , Jiri Slaby , linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v2 2/5] tty: move baudrate handling code to a file of its own Date: Sat, 1 Apr 2017 18:21:16 -0400 Message-Id: <20170401222119.25106-3-nicolas.pitre@linaro.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170401222119.25106-1-nicolas.pitre@linaro.org> References: <20170401222119.25106-1-nicolas.pitre@linaro.org> X-CMAE-Envelope: MS4wfL/Hqqh9WarlObE30dCHvEbfdCPTRFg0Ek6R3W7eojZ9X21Pgkglz5sVPlaANqj/BAi+DMyqgmdlUejtuE3nHDmX3C7DnZQ+rDRPr7DBn8ew+H0oedxZ r8A08QHfk4W/5xLYctZCD00SGYbTXBoP8OR9/CJb/r4pbpI3L/PcB9Ob2bWcXMLqHCAgZ8J99cKmkGBH/HNwjvx2V8sCypTD3exf3Eq04UCa/zxMztGidsgq tAAtxr5uFpOJfhqGuI16fm/AbwiwiC2hRAijC5py6SlltXRSFDBPplRvx0kPVpNy5VVBKCOFPiy8lPVqwhfLuvd+ZhZXTqkpU1K7MvL98Kc= Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org To allow reuse without the rest of the tty_ioctl code. Signed-off-by: Nicolas Pitre --- drivers/tty/Makefile | 2 +- drivers/tty/tty_baudrate.c | 232 +++++++++++++++++++++++++++++++++++++++++++++ drivers/tty/tty_ioctl.c | 222 ------------------------------------------- 3 files changed, 233 insertions(+), 223 deletions(-) create mode 100644 drivers/tty/tty_baudrate.c -- 2.9.3 diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index b95bed92da..1461be6b90 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \ - tty_buffer.o tty_port.o tty_mutex.o tty_ldsem.o + tty_buffer.o tty_port.o tty_mutex.o tty_ldsem.o tty_baudrate.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-$(CONFIG_AUDIT) += tty_audit.o diff --git a/drivers/tty/tty_baudrate.c b/drivers/tty/tty_baudrate.c new file mode 100644 index 0000000000..5c33fd2567 --- /dev/null +++ b/drivers/tty/tty_baudrate.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds + */ + +#include +#include +#include +#include +#include + + +/* + * Routine which returns the baud rate of the tty + * + * Note that the baud_table needs to be kept in sync with the + * include/asm/termbits.h file. + */ +static const speed_t baud_table[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, +#ifdef __sparc__ + 76800, 153600, 307200, 614400, 921600 +#else + 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, + 2500000, 3000000, 3500000, 4000000 +#endif +}; + +#ifndef __sparc__ +static const tcflag_t baud_bits[] = { + B0, B50, B75, B110, B134, B150, B200, B300, B600, + B1200, B1800, B2400, B4800, B9600, B19200, B38400, + B57600, B115200, B230400, B460800, B500000, B576000, + B921600, B1000000, B1152000, B1500000, B2000000, B2500000, + B3000000, B3500000, B4000000 +}; +#else +static const tcflag_t baud_bits[] = { + B0, B50, B75, B110, B134, B150, B200, B300, B600, + B1200, B1800, B2400, B4800, B9600, B19200, B38400, + B57600, B115200, B230400, B460800, B76800, B153600, + B307200, B614400, B921600 +}; +#endif + +static int n_baud_table = ARRAY_SIZE(baud_table); + +/** + * tty_termios_baud_rate + * @termios: termios structure + * + * Convert termios baud rate data into a speed. This should be called + * with the termios lock held if this termios is a terminal termios + * structure. May change the termios data. Device drivers can call this + * function but should use ->c_[io]speed directly as they are updated. + * + * Locking: none + */ + +speed_t tty_termios_baud_rate(struct ktermios *termios) +{ + unsigned int cbaud; + + cbaud = termios->c_cflag & CBAUD; + +#ifdef BOTHER + /* Magic token for arbitrary speed via c_ispeed/c_ospeed */ + if (cbaud == BOTHER) + return termios->c_ospeed; +#endif + if (cbaud & CBAUDEX) { + cbaud &= ~CBAUDEX; + + if (cbaud < 1 || cbaud + 15 > n_baud_table) + termios->c_cflag &= ~CBAUDEX; + else + cbaud += 15; + } + return baud_table[cbaud]; +} +EXPORT_SYMBOL(tty_termios_baud_rate); + +/** + * tty_termios_input_baud_rate + * @termios: termios structure + * + * Convert termios baud rate data into a speed. This should be called + * with the termios lock held if this termios is a terminal termios + * structure. May change the termios data. Device drivers can call this + * function but should use ->c_[io]speed directly as they are updated. + * + * Locking: none + */ + +speed_t tty_termios_input_baud_rate(struct ktermios *termios) +{ +#ifdef IBSHIFT + unsigned int cbaud = (termios->c_cflag >> IBSHIFT) & CBAUD; + + if (cbaud == B0) + return tty_termios_baud_rate(termios); + + /* Magic token for arbitrary speed via c_ispeed*/ + if (cbaud == BOTHER) + return termios->c_ispeed; + + if (cbaud & CBAUDEX) { + cbaud &= ~CBAUDEX; + + if (cbaud < 1 || cbaud + 15 > n_baud_table) + termios->c_cflag &= ~(CBAUDEX << IBSHIFT); + else + cbaud += 15; + } + return baud_table[cbaud]; +#else + return tty_termios_baud_rate(termios); +#endif +} +EXPORT_SYMBOL(tty_termios_input_baud_rate); + +/** + * tty_termios_encode_baud_rate + * @termios: ktermios structure holding user requested state + * @ispeed: input speed + * @ospeed: output speed + * + * Encode the speeds set into the passed termios structure. This is + * used as a library helper for drivers so that they can report back + * the actual speed selected when it differs from the speed requested + * + * For maximal back compatibility with legacy SYS5/POSIX *nix behaviour + * we need to carefully set the bits when the user does not get the + * desired speed. We allow small margins and preserve as much of possible + * of the input intent to keep compatibility. + * + * Locking: Caller should hold termios lock. This is already held + * when calling this function from the driver termios handler. + * + * The ifdefs deal with platforms whose owners have yet to update them + * and will all go away once this is done. + */ + +void tty_termios_encode_baud_rate(struct ktermios *termios, + speed_t ibaud, speed_t obaud) +{ + int i = 0; + int ifound = -1, ofound = -1; + int iclose = ibaud/50, oclose = obaud/50; + int ibinput = 0; + + if (obaud == 0) /* CD dropped */ + ibaud = 0; /* Clear ibaud to be sure */ + + termios->c_ispeed = ibaud; + termios->c_ospeed = obaud; + +#ifdef BOTHER + /* If the user asked for a precise weird speed give a precise weird + answer. If they asked for a Bfoo speed they may have problems + digesting non-exact replies so fuzz a bit */ + + if ((termios->c_cflag & CBAUD) == BOTHER) + oclose = 0; + if (((termios->c_cflag >> IBSHIFT) & CBAUD) == BOTHER) + iclose = 0; + if ((termios->c_cflag >> IBSHIFT) & CBAUD) + ibinput = 1; /* An input speed was specified */ +#endif + termios->c_cflag &= ~CBAUD; + + /* + * Our goal is to find a close match to the standard baud rate + * returned. Walk the baud rate table and if we get a very close + * match then report back the speed as a POSIX Bxxxx value by + * preference + */ + + do { + if (obaud - oclose <= baud_table[i] && + obaud + oclose >= baud_table[i]) { + termios->c_cflag |= baud_bits[i]; + ofound = i; + } + if (ibaud - iclose <= baud_table[i] && + ibaud + iclose >= baud_table[i]) { + /* For the case input == output don't set IBAUD bits + if the user didn't do so */ + if (ofound == i && !ibinput) + ifound = i; +#ifdef IBSHIFT + else { + ifound = i; + termios->c_cflag |= (baud_bits[i] << IBSHIFT); + } +#endif + } + } while (++i < n_baud_table); + + /* + * If we found no match then use BOTHER if provided or warn + * the user their platform maintainer needs to wake up if not. + */ +#ifdef BOTHER + if (ofound == -1) + termios->c_cflag |= BOTHER; + /* Set exact input bits only if the input and output differ or the + user already did */ + if (ifound == -1 && (ibaud != obaud || ibinput)) + termios->c_cflag |= (BOTHER << IBSHIFT); +#else + if (ifound == -1 || ofound == -1) + pr_warn_once("tty: Unable to return correct speed data as your architecture needs updating.\n"); +#endif +} +EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate); + +/** + * tty_encode_baud_rate - set baud rate of the tty + * @ibaud: input baud rate + * @obad: output baud rate + * + * Update the current termios data for the tty with the new speed + * settings. The caller must hold the termios_rwsem for the tty in + * question. + */ + +void tty_encode_baud_rate(struct tty_struct *tty, speed_t ibaud, speed_t obaud) +{ + tty_termios_encode_baud_rate(&tty->termios, ibaud, obaud); +} +EXPORT_SYMBOL_GPL(tty_encode_baud_rate); diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index a9a978731c..efa96e6c4c 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -258,228 +258,6 @@ static void unset_locked_termios(struct tty_struct *tty, struct ktermios *old) /* FIXME: What should we do for i/ospeed */ } -/* - * Routine which returns the baud rate of the tty - * - * Note that the baud_table needs to be kept in sync with the - * include/asm/termbits.h file. - */ -static const speed_t baud_table[] = { - 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, - 9600, 19200, 38400, 57600, 115200, 230400, 460800, -#ifdef __sparc__ - 76800, 153600, 307200, 614400, 921600 -#else - 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, - 2500000, 3000000, 3500000, 4000000 -#endif -}; - -#ifndef __sparc__ -static const tcflag_t baud_bits[] = { - B0, B50, B75, B110, B134, B150, B200, B300, B600, - B1200, B1800, B2400, B4800, B9600, B19200, B38400, - B57600, B115200, B230400, B460800, B500000, B576000, - B921600, B1000000, B1152000, B1500000, B2000000, B2500000, - B3000000, B3500000, B4000000 -}; -#else -static const tcflag_t baud_bits[] = { - B0, B50, B75, B110, B134, B150, B200, B300, B600, - B1200, B1800, B2400, B4800, B9600, B19200, B38400, - B57600, B115200, B230400, B460800, B76800, B153600, - B307200, B614400, B921600 -}; -#endif - -static int n_baud_table = ARRAY_SIZE(baud_table); - -/** - * tty_termios_baud_rate - * @termios: termios structure - * - * Convert termios baud rate data into a speed. This should be called - * with the termios lock held if this termios is a terminal termios - * structure. May change the termios data. Device drivers can call this - * function but should use ->c_[io]speed directly as they are updated. - * - * Locking: none - */ - -speed_t tty_termios_baud_rate(struct ktermios *termios) -{ - unsigned int cbaud; - - cbaud = termios->c_cflag & CBAUD; - -#ifdef BOTHER - /* Magic token for arbitrary speed via c_ispeed/c_ospeed */ - if (cbaud == BOTHER) - return termios->c_ospeed; -#endif - if (cbaud & CBAUDEX) { - cbaud &= ~CBAUDEX; - - if (cbaud < 1 || cbaud + 15 > n_baud_table) - termios->c_cflag &= ~CBAUDEX; - else - cbaud += 15; - } - return baud_table[cbaud]; -} -EXPORT_SYMBOL(tty_termios_baud_rate); - -/** - * tty_termios_input_baud_rate - * @termios: termios structure - * - * Convert termios baud rate data into a speed. This should be called - * with the termios lock held if this termios is a terminal termios - * structure. May change the termios data. Device drivers can call this - * function but should use ->c_[io]speed directly as they are updated. - * - * Locking: none - */ - -speed_t tty_termios_input_baud_rate(struct ktermios *termios) -{ -#ifdef IBSHIFT - unsigned int cbaud = (termios->c_cflag >> IBSHIFT) & CBAUD; - - if (cbaud == B0) - return tty_termios_baud_rate(termios); - - /* Magic token for arbitrary speed via c_ispeed*/ - if (cbaud == BOTHER) - return termios->c_ispeed; - - if (cbaud & CBAUDEX) { - cbaud &= ~CBAUDEX; - - if (cbaud < 1 || cbaud + 15 > n_baud_table) - termios->c_cflag &= ~(CBAUDEX << IBSHIFT); - else - cbaud += 15; - } - return baud_table[cbaud]; -#else - return tty_termios_baud_rate(termios); -#endif -} -EXPORT_SYMBOL(tty_termios_input_baud_rate); - -/** - * tty_termios_encode_baud_rate - * @termios: ktermios structure holding user requested state - * @ispeed: input speed - * @ospeed: output speed - * - * Encode the speeds set into the passed termios structure. This is - * used as a library helper for drivers so that they can report back - * the actual speed selected when it differs from the speed requested - * - * For maximal back compatibility with legacy SYS5/POSIX *nix behaviour - * we need to carefully set the bits when the user does not get the - * desired speed. We allow small margins and preserve as much of possible - * of the input intent to keep compatibility. - * - * Locking: Caller should hold termios lock. This is already held - * when calling this function from the driver termios handler. - * - * The ifdefs deal with platforms whose owners have yet to update them - * and will all go away once this is done. - */ - -void tty_termios_encode_baud_rate(struct ktermios *termios, - speed_t ibaud, speed_t obaud) -{ - int i = 0; - int ifound = -1, ofound = -1; - int iclose = ibaud/50, oclose = obaud/50; - int ibinput = 0; - - if (obaud == 0) /* CD dropped */ - ibaud = 0; /* Clear ibaud to be sure */ - - termios->c_ispeed = ibaud; - termios->c_ospeed = obaud; - -#ifdef BOTHER - /* If the user asked for a precise weird speed give a precise weird - answer. If they asked for a Bfoo speed they may have problems - digesting non-exact replies so fuzz a bit */ - - if ((termios->c_cflag & CBAUD) == BOTHER) - oclose = 0; - if (((termios->c_cflag >> IBSHIFT) & CBAUD) == BOTHER) - iclose = 0; - if ((termios->c_cflag >> IBSHIFT) & CBAUD) - ibinput = 1; /* An input speed was specified */ -#endif - termios->c_cflag &= ~CBAUD; - - /* - * Our goal is to find a close match to the standard baud rate - * returned. Walk the baud rate table and if we get a very close - * match then report back the speed as a POSIX Bxxxx value by - * preference - */ - - do { - if (obaud - oclose <= baud_table[i] && - obaud + oclose >= baud_table[i]) { - termios->c_cflag |= baud_bits[i]; - ofound = i; - } - if (ibaud - iclose <= baud_table[i] && - ibaud + iclose >= baud_table[i]) { - /* For the case input == output don't set IBAUD bits - if the user didn't do so */ - if (ofound == i && !ibinput) - ifound = i; -#ifdef IBSHIFT - else { - ifound = i; - termios->c_cflag |= (baud_bits[i] << IBSHIFT); - } -#endif - } - } while (++i < n_baud_table); - - /* - * If we found no match then use BOTHER if provided or warn - * the user their platform maintainer needs to wake up if not. - */ -#ifdef BOTHER - if (ofound == -1) - termios->c_cflag |= BOTHER; - /* Set exact input bits only if the input and output differ or the - user already did */ - if (ifound == -1 && (ibaud != obaud || ibinput)) - termios->c_cflag |= (BOTHER << IBSHIFT); -#else - if (ifound == -1 || ofound == -1) - pr_warn_once("tty: Unable to return correct speed data as your architecture needs updating.\n"); -#endif -} -EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate); - -/** - * tty_encode_baud_rate - set baud rate of the tty - * @ibaud: input baud rate - * @obad: output baud rate - * - * Update the current termios data for the tty with the new speed - * settings. The caller must hold the termios_rwsem for the tty in - * question. - */ - -void tty_encode_baud_rate(struct tty_struct *tty, speed_t ibaud, speed_t obaud) -{ - tty_termios_encode_baud_rate(&tty->termios, ibaud, obaud); -} -EXPORT_SYMBOL_GPL(tty_encode_baud_rate); - /** * tty_termios_copy_hw - copy hardware settings * @new: New termios From patchwork Sat Apr 1 22:21:17 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Pitre X-Patchwork-Id: 96584 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp1306629qgd; Sat, 1 Apr 2017 15:36:37 -0700 (PDT) X-Received: by 10.99.215.85 with SMTP id w21mr9614931pgi.217.1491086197551; Sat, 01 Apr 2017 15:36:37 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id d185si9259529pgc.362.2017.04.01.15.36.37; Sat, 01 Apr 2017 15:36:37 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752027AbdDAWgg (ORCPT + 24 others); Sat, 1 Apr 2017 18:36:36 -0400 Received: from alt13.smtp-out.videotron.ca ([135.19.0.26]:38849 "EHLO alt12.smtp-out.videotron.ca" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751987AbdDAWgb (ORCPT ); Sat, 1 Apr 2017 18:36:31 -0400 Received: from yoda.home ([96.23.157.65]) by Videotron with SMTP id uROhcVwKzHh2duROiccJWj; Sat, 01 Apr 2017 18:21:29 -0400 X-Authority-Analysis: v=2.1 cv=Lv0ysipc c=1 sm=1 tr=0 a=keA3yYpnlypCNW5BNWqu+w==:117 a=keA3yYpnlypCNW5BNWqu+w==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=AzvcPWV-tVgA:10 a=KKAkSRfTAAAA:8 a=_0qeO3ElMST5N1HposAA:9 a=cvBusfyB2V15izCimMoJ:22 Received: from xanadu.home (xanadu.home [192.168.2.2]) by yoda.home (Postfix) with ESMTP id 380CB2DA064A; Sat, 1 Apr 2017 18:21:27 -0400 (EDT) From: Nicolas Pitre To: Greg Kroah-Hartman , Jiri Slaby , linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v2 3/5] serial: small Makefile reordering Date: Sat, 1 Apr 2017 18:21:17 -0400 Message-Id: <20170401222119.25106-4-nicolas.pitre@linaro.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170401222119.25106-1-nicolas.pitre@linaro.org> References: <20170401222119.25106-1-nicolas.pitre@linaro.org> X-CMAE-Envelope: MS4wfC+S1Rp4nZt0Hi8251knRwfhBtTfrwZELRZmK1Yt9AGFcnTAz+2HINYVAsWE7BtmeBurk51W4LMvZt75HOj4N09PTXiV29NSZa+xXFZpp9bET+tTOjVU 8cJgVbeZCtoF3cJW9D4f6AQZtsOSci3SKMzxrALPLwJX/fm2Gc5CyAWMldMZgUYPhoqyqKnKrfVkIGuUCZMm+zDqMtn1pI8Fou/2ZLnPeEOcOy4NbsNl9EGC cXf3zb1LS3DyXlccLdrCyf87eUA1si+hRQJorygfuHnlkUzOubm5JbJEngce42RCeImRK/U967sZ+LVdWd/AKcS89akzOB8ujSizrSiJVrg= Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Move 21285 entry down alongside other UART drivers to be more consistent with the rest of the file. It is kept before 8250 though, to preserve the existing link ordering between those two. Signed-off-by: Nicolas Pitre --- drivers/tty/serial/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) -- 2.9.3 diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 2d6288bc45..53c03e0051 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -3,7 +3,6 @@ # obj-$(CONFIG_SERIAL_CORE) += serial_core.o -obj-$(CONFIG_SERIAL_21285) += 21285.o obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o obj-$(CONFIG_SERIAL_EARLYCON_ARM_SEMIHOST) += earlycon-arm-semihost.o @@ -17,6 +16,8 @@ obj-$(CONFIG_SERIAL_SUNZILOG) += sunzilog.o obj-$(CONFIG_SERIAL_SUNSU) += sunsu.o obj-$(CONFIG_SERIAL_SUNSAB) += sunsab.o +obj-$(CONFIG_SERIAL_21285) += 21285.o + # Now bring in any enabled 8250/16450/16550 type drivers. obj-$(CONFIG_SERIAL_8250) += 8250/ From patchwork Sat Apr 1 22:21:18 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Pitre X-Patchwork-Id: 96582 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp1303685qgd; Sat, 1 Apr 2017 15:22:09 -0700 (PDT) X-Received: by 10.84.129.131 with SMTP id b3mr11790467plb.160.1491085329582; Sat, 01 Apr 2017 15:22:09 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id o184si9261375pfg.97.2017.04.01.15.22.09; Sat, 01 Apr 2017 15:22:09 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752090AbdDAWWI (ORCPT + 24 others); Sat, 1 Apr 2017 18:22:08 -0400 Received: from alt42.smtp-out.videotron.ca ([23.233.128.29]:13151 "EHLO alt42.smtp-out.videotron.ca" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751987AbdDAWVa (ORCPT ); Sat, 1 Apr 2017 18:21:30 -0400 Received: from yoda.home ([96.23.157.65]) by Videotron with SMTP id uROhcmFUPcMN9uROicgFyO; Sat, 01 Apr 2017 18:21:29 -0400 X-Authority-Analysis: v=2.1 cv=YqOvP9sX c=1 sm=1 tr=0 a=keA3yYpnlypCNW5BNWqu+w==:117 a=keA3yYpnlypCNW5BNWqu+w==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=AzvcPWV-tVgA:10 a=KKAkSRfTAAAA:8 a=wLCnSH7QxGmrT19RutcA:9 a=ez5DB9EMAEzTdY6M:21 a=gyIAFTJFnijismHS:21 a=PynleWgEvfYA:10 a=cvBusfyB2V15izCimMoJ:22 Received: from xanadu.home (xanadu.home [192.168.2.2]) by yoda.home (Postfix) with ESMTP id 53B442DA069E; Sat, 1 Apr 2017 18:21:27 -0400 (EDT) From: Nicolas Pitre To: Greg Kroah-Hartman , Jiri Slaby , linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v2 4/5] serial: split generic UART driver helper functions into a separate file Date: Sat, 1 Apr 2017 18:21:18 -0400 Message-Id: <20170401222119.25106-5-nicolas.pitre@linaro.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170401222119.25106-1-nicolas.pitre@linaro.org> References: <20170401222119.25106-1-nicolas.pitre@linaro.org> X-CMAE-Envelope: MS4wfCv0X8GMyhlpj1OwP/pNfjdnUWdOj5n0KbsvMUyrS3C8H28sp/qfzRwy9Eecy4gc1gifZj+gNxCiPSHGP8OAUe6fbfq4g98H3C+4B0KCcDy32/GtIiZN wLmZZcQrb0H1B2XbywAi8NAdWCP/eVtFpyEcz4BiQTrEDQ41Prddjnw5okft2VpVwY/yVNbxhLBYv9ub00xFNRKH1Xttf+OiRtsEXfkT0ew3Wz6bPXR8cIoz kxvttzpicYFL3U1B0yTWpYeFcHjVfe4zSeCRGVbsqzL1d7QGBxMeFmuord9Wir6CS3YyNdq9QoSL1w8tDhyqEqOulH615xVJ1Qvso4jEGew= Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This contains code that is common between serial_core.c and the minitty code to come. Mainly helper functions used by UART drivers. Signed-off-by: Nicolas Pitre --- drivers/tty/serial/Makefile | 2 +- drivers/tty/serial/serial_core.c | 419 +------------------------------------ drivers/tty/serial/serial_lib.c | 440 +++++++++++++++++++++++++++++++++++++++ include/linux/serial_core.h | 1 + 4 files changed, 443 insertions(+), 419 deletions(-) create mode 100644 drivers/tty/serial/serial_lib.c -- 2.9.3 diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 53c03e0051..073afd10c5 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -2,7 +2,7 @@ # Makefile for the kernel serial device drivers. # -obj-$(CONFIG_SERIAL_CORE) += serial_core.o +obj-$(CONFIG_SERIAL_CORE) += serial_core.o serial_lib.o obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o obj-$(CONFIG_SERIAL_EARLYCON_ARM_SEMIHOST) += earlycon-arm-semihost.o diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 3fe5689497..20214e1d87 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -44,12 +44,6 @@ */ static DEFINE_MUTEX(port_mutex); -/* - * lockdep: port->lock is initialized in two places, but we - * want only one lock-class: - */ -static struct lock_class_key port_lock_key; - #define HIGH_BITS_OFFSET ((sizeof(long)-sizeof(int))*8) static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, @@ -293,183 +287,6 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state) } } -/** - * uart_update_timeout - update per-port FIFO timeout. - * @port: uart_port structure describing the port - * @cflag: termios cflag value - * @baud: speed of the port - * - * Set the port FIFO timeout value. The @cflag value should - * reflect the actual hardware settings. - */ -void -uart_update_timeout(struct uart_port *port, unsigned int cflag, - unsigned int baud) -{ - unsigned int bits; - - /* byte size and parity */ - switch (cflag & CSIZE) { - case CS5: - bits = 7; - break; - case CS6: - bits = 8; - break; - case CS7: - bits = 9; - break; - default: - bits = 10; - break; /* CS8 */ - } - - if (cflag & CSTOPB) - bits++; - if (cflag & PARENB) - bits++; - - /* - * The total number of bits to be transmitted in the fifo. - */ - bits = bits * port->fifosize; - - /* - * Figure the timeout to send the above number of bits. - * Add .02 seconds of slop - */ - port->timeout = (HZ * bits) / baud + HZ/50; -} - -EXPORT_SYMBOL(uart_update_timeout); - -/** - * uart_get_baud_rate - return baud rate for a particular port - * @port: uart_port structure describing the port in question. - * @termios: desired termios settings. - * @old: old termios (or NULL) - * @min: minimum acceptable baud rate - * @max: maximum acceptable baud rate - * - * Decode the termios structure into a numeric baud rate, - * taking account of the magic 38400 baud rate (with spd_* - * flags), and mapping the %B0 rate to 9600 baud. - * - * If the new baud rate is invalid, try the old termios setting. - * If it's still invalid, we try 9600 baud. - * - * Update the @termios structure to reflect the baud rate - * we're actually going to be using. Don't do this for the case - * where B0 is requested ("hang up"). - */ -unsigned int -uart_get_baud_rate(struct uart_port *port, struct ktermios *termios, - struct ktermios *old, unsigned int min, unsigned int max) -{ - unsigned int try; - unsigned int baud; - unsigned int altbaud; - int hung_up = 0; - upf_t flags = port->flags & UPF_SPD_MASK; - - switch (flags) { - case UPF_SPD_HI: - altbaud = 57600; - break; - case UPF_SPD_VHI: - altbaud = 115200; - break; - case UPF_SPD_SHI: - altbaud = 230400; - break; - case UPF_SPD_WARP: - altbaud = 460800; - break; - default: - altbaud = 38400; - break; - } - - for (try = 0; try < 2; try++) { - baud = tty_termios_baud_rate(termios); - - /* - * The spd_hi, spd_vhi, spd_shi, spd_warp kludge... - * Die! Die! Die! - */ - if (try == 0 && baud == 38400) - baud = altbaud; - - /* - * Special case: B0 rate. - */ - if (baud == 0) { - hung_up = 1; - baud = 9600; - } - - if (baud >= min && baud <= max) - return baud; - - /* - * Oops, the quotient was zero. Try again with - * the old baud rate if possible. - */ - termios->c_cflag &= ~CBAUD; - if (old) { - baud = tty_termios_baud_rate(old); - if (!hung_up) - tty_termios_encode_baud_rate(termios, - baud, baud); - old = NULL; - continue; - } - - /* - * As a last resort, if the range cannot be met then clip to - * the nearest chip supported rate. - */ - if (!hung_up) { - if (baud <= min) - tty_termios_encode_baud_rate(termios, - min + 1, min + 1); - else - tty_termios_encode_baud_rate(termios, - max - 1, max - 1); - } - } - /* Should never happen */ - WARN_ON(1); - return 0; -} - -EXPORT_SYMBOL(uart_get_baud_rate); - -/** - * uart_get_divisor - return uart clock divisor - * @port: uart_port structure describing the port. - * @baud: desired baud rate - * - * Calculate the uart clock divisor for the port. - */ -unsigned int -uart_get_divisor(struct uart_port *port, unsigned int baud) -{ - unsigned int quot; - - /* - * Old custom speed handling. - */ - if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) - quot = port->custom_divisor; - else - quot = DIV_ROUND_CLOSEST(port->uartclk, 16 * baud); - - return quot; -} - -EXPORT_SYMBOL(uart_get_divisor); - /* Caller holds port mutex */ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, struct ktermios *old_termios) @@ -1837,207 +1654,6 @@ static const struct file_operations uart_proc_fops = { }; #endif -#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) -/** - * uart_console_write - write a console message to a serial port - * @port: the port to write the message - * @s: array of characters - * @count: number of characters in string to write - * @putchar: function to write character to port - */ -void uart_console_write(struct uart_port *port, const char *s, - unsigned int count, - void (*putchar)(struct uart_port *, int)) -{ - unsigned int i; - - for (i = 0; i < count; i++, s++) { - if (*s == '\n') - putchar(port, '\r'); - putchar(port, *s); - } -} -EXPORT_SYMBOL_GPL(uart_console_write); - -/* - * Check whether an invalid uart number has been specified, and - * if so, search for the first available port that does have - * console support. - */ -struct uart_port * __init -uart_get_console(struct uart_port *ports, int nr, struct console *co) -{ - int idx = co->index; - - if (idx < 0 || idx >= nr || (ports[idx].iobase == 0 && - ports[idx].membase == NULL)) - for (idx = 0; idx < nr; idx++) - if (ports[idx].iobase != 0 || - ports[idx].membase != NULL) - break; - - co->index = idx; - - return ports + idx; -} - -/** - * uart_parse_earlycon - Parse earlycon options - * @p: ptr to 2nd field (ie., just beyond ',') - * @iotype: ptr for decoded iotype (out) - * @addr: ptr for decoded mapbase/iobase (out) - * @options: ptr for field; NULL if not present (out) - * - * Decodes earlycon kernel command line parameters of the form - * earlycon=,io|mmio|mmio16|mmio32|mmio32be|mmio32native,, - * console=,io|mmio|mmio16|mmio32|mmio32be|mmio32native,, - * - * The optional form - * earlycon=,0x, - * console=,0x, - * is also accepted; the returned @iotype will be UPIO_MEM. - * - * Returns 0 on success or -EINVAL on failure - */ -int uart_parse_earlycon(char *p, unsigned char *iotype, resource_size_t *addr, - char **options) -{ - if (strncmp(p, "mmio,", 5) == 0) { - *iotype = UPIO_MEM; - p += 5; - } else if (strncmp(p, "mmio16,", 7) == 0) { - *iotype = UPIO_MEM16; - p += 7; - } else if (strncmp(p, "mmio32,", 7) == 0) { - *iotype = UPIO_MEM32; - p += 7; - } else if (strncmp(p, "mmio32be,", 9) == 0) { - *iotype = UPIO_MEM32BE; - p += 9; - } else if (strncmp(p, "mmio32native,", 13) == 0) { - *iotype = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) ? - UPIO_MEM32BE : UPIO_MEM32; - p += 13; - } else if (strncmp(p, "io,", 3) == 0) { - *iotype = UPIO_PORT; - p += 3; - } else if (strncmp(p, "0x", 2) == 0) { - *iotype = UPIO_MEM; - } else { - return -EINVAL; - } - - /* - * Before you replace it with kstrtoull(), think about options separator - * (',') it will not tolerate - */ - *addr = simple_strtoull(p, NULL, 0); - p = strchr(p, ','); - if (p) - p++; - - *options = p; - return 0; -} -EXPORT_SYMBOL_GPL(uart_parse_earlycon); - -/** - * uart_parse_options - Parse serial port baud/parity/bits/flow control. - * @options: pointer to option string - * @baud: pointer to an 'int' variable for the baud rate. - * @parity: pointer to an 'int' variable for the parity. - * @bits: pointer to an 'int' variable for the number of data bits. - * @flow: pointer to an 'int' variable for the flow control character. - * - * uart_parse_options decodes a string containing the serial console - * options. The format of the string is , - * eg: 115200n8r - */ -void -uart_parse_options(char *options, int *baud, int *parity, int *bits, int *flow) -{ - char *s = options; - - *baud = simple_strtoul(s, NULL, 10); - while (*s >= '0' && *s <= '9') - s++; - if (*s) - *parity = *s++; - if (*s) - *bits = *s++ - '0'; - if (*s) - *flow = *s; -} -EXPORT_SYMBOL_GPL(uart_parse_options); - -/** - * uart_set_options - setup the serial console parameters - * @port: pointer to the serial ports uart_port structure - * @co: console pointer - * @baud: baud rate - * @parity: parity character - 'n' (none), 'o' (odd), 'e' (even) - * @bits: number of data bits - * @flow: flow control character - 'r' (rts) - */ -int -uart_set_options(struct uart_port *port, struct console *co, - int baud, int parity, int bits, int flow) -{ - struct ktermios termios; - static struct ktermios dummy; - - /* - * Ensure that the serial console lock is initialised - * early. - * If this port is a console, then the spinlock is already - * initialised. - */ - if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) { - spin_lock_init(&port->lock); - lockdep_set_class(&port->lock, &port_lock_key); - } - - memset(&termios, 0, sizeof(struct ktermios)); - - termios.c_cflag |= CREAD | HUPCL | CLOCAL; - tty_termios_encode_baud_rate(&termios, baud, baud); - - if (bits == 7) - termios.c_cflag |= CS7; - else - termios.c_cflag |= CS8; - - switch (parity) { - case 'o': case 'O': - termios.c_cflag |= PARODD; - /*fall through*/ - case 'e': case 'E': - termios.c_cflag |= PARENB; - break; - } - - if (flow == 'r') - termios.c_cflag |= CRTSCTS; - - /* - * some uarts on other side don't support no flow control. - * So we set * DTR in host uart to make them happy - */ - port->mctrl |= TIOCM_DTR; - - port->ops->set_termios(port, &termios, &dummy); - /* - * Allow the setting of the UART parameters with a NULL console - * too: - */ - if (co) - co->cflag = termios.c_cflag; - - return 0; -} -EXPORT_SYMBOL_GPL(uart_set_options); -#endif /* CONFIG_SERIAL_CORE_CONSOLE */ - /** * uart_change_pm - set power state of the port * @@ -2751,15 +2367,8 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport) state->pm_state = UART_PM_STATE_UNDEFINED; uport->cons = drv->cons; uport->minor = drv->tty_driver->minor_start + uport->line; + uart_port_lock_init(uport); - /* - * If this port is a console, then the spinlock is already - * initialised. - */ - if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) { - spin_lock_init(&uport->lock); - lockdep_set_class(&uport->lock, &port_lock_key); - } if (uport->cons && uport->dev) of_console_check(uport->dev->of_node, uport->cons->name, uport->line); @@ -2885,32 +2494,6 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport) return ret; } -/* - * Are the two ports equivalent? - */ -int uart_match_port(struct uart_port *port1, struct uart_port *port2) -{ - if (port1->iotype != port2->iotype) - return 0; - - switch (port1->iotype) { - case UPIO_PORT: - return (port1->iobase == port2->iobase); - case UPIO_HUB6: - return (port1->iobase == port2->iobase) && - (port1->hub6 == port2->hub6); - case UPIO_MEM: - case UPIO_MEM16: - case UPIO_MEM32: - case UPIO_MEM32BE: - case UPIO_AU: - case UPIO_TSI: - return (port1->mapbase == port2->mapbase); - } - return 0; -} -EXPORT_SYMBOL(uart_match_port); - /** * uart_handle_dcd_change - handle a change of carrier detect state * @uport: uart_port structure for the open port diff --git a/drivers/tty/serial/serial_lib.c b/drivers/tty/serial/serial_lib.c new file mode 100644 index 0000000000..c3f521b401 --- /dev/null +++ b/drivers/tty/serial/serial_lib.c @@ -0,0 +1,440 @@ +/* + * Common support functions for serial port drivers + * + * Copyright 1999 ARM Limited + * Copyright (C) 2000-2001 Deep Blue Solutions Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +/* + * lockdep: port->lock is initialized in two places, but we + * want only one lock-class: + */ +static struct lock_class_key port_lock_key; + +void uart_port_lock_init(struct uart_port *port) +{ + /* + * If this port is a console, then the spinlock is already + * initialised. + */ + if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) { + spin_lock_init(&port->lock); + lockdep_set_class(&port->lock, &port_lock_key); + } +} + +/** + * uart_update_timeout - update per-port FIFO timeout. + * @port: uart_port structure describing the port + * @cflag: termios cflag value + * @baud: speed of the port + * + * Set the port FIFO timeout value. The @cflag value should + * reflect the actual hardware settings. + */ +void +uart_update_timeout(struct uart_port *port, unsigned int cflag, + unsigned int baud) +{ + unsigned int bits; + + /* byte size and parity */ + switch (cflag & CSIZE) { + case CS5: + bits = 7; + break; + case CS6: + bits = 8; + break; + case CS7: + bits = 9; + break; + default: + bits = 10; + break; /* CS8 */ + } + + if (cflag & CSTOPB) + bits++; + if (cflag & PARENB) + bits++; + + /* + * The total number of bits to be transmitted in the fifo. + */ + bits = bits * port->fifosize; + + /* + * Figure the timeout to send the above number of bits. + * Add .02 seconds of slop + */ + port->timeout = (HZ * bits) / baud + HZ/50; +} +EXPORT_SYMBOL(uart_update_timeout); + +/** + * uart_get_baud_rate - return baud rate for a particular port + * @port: uart_port structure describing the port in question. + * @termios: desired termios settings. + * @old: old termios (or NULL) + * @min: minimum acceptable baud rate + * @max: maximum acceptable baud rate + * + * Decode the termios structure into a numeric baud rate, + * taking account of the magic 38400 baud rate (with spd_* + * flags), and mapping the %B0 rate to 9600 baud. + * + * If the new baud rate is invalid, try the old termios setting. + * If it's still invalid, we try 9600 baud. + * + * Update the @termios structure to reflect the baud rate + * we're actually going to be using. Don't do this for the case + * where B0 is requested ("hang up"). + */ +unsigned int +uart_get_baud_rate(struct uart_port *port, struct ktermios *termios, + struct ktermios *old, unsigned int min, unsigned int max) +{ + unsigned int try; + unsigned int baud; + unsigned int altbaud; + int hung_up = 0; + upf_t flags = port->flags & UPF_SPD_MASK; + + switch (flags) { + case UPF_SPD_HI: + altbaud = 57600; + break; + case UPF_SPD_VHI: + altbaud = 115200; + break; + case UPF_SPD_SHI: + altbaud = 230400; + break; + case UPF_SPD_WARP: + altbaud = 460800; + break; + default: + altbaud = 38400; + break; + } + + for (try = 0; try < 2; try++) { + baud = tty_termios_baud_rate(termios); + + /* + * The spd_hi, spd_vhi, spd_shi, spd_warp kludge... + * Die! Die! Die! + */ + if (try == 0 && baud == 38400) + baud = altbaud; + + /* + * Special case: B0 rate. + */ + if (baud == 0) { + hung_up = 1; + baud = 9600; + } + + if (baud >= min && baud <= max) + return baud; + + /* + * Oops, the quotient was zero. Try again with + * the old baud rate if possible. + */ + termios->c_cflag &= ~CBAUD; + if (old) { + baud = tty_termios_baud_rate(old); + if (!hung_up) + tty_termios_encode_baud_rate(termios, + baud, baud); + old = NULL; + continue; + } + + /* + * As a last resort, if the range cannot be met then clip to + * the nearest chip supported rate. + */ + if (!hung_up) { + if (baud <= min) + tty_termios_encode_baud_rate(termios, + min + 1, min + 1); + else + tty_termios_encode_baud_rate(termios, + max - 1, max - 1); + } + } + /* Should never happen */ + WARN_ON(1); + return 0; +} +EXPORT_SYMBOL(uart_get_baud_rate); + +/** + * uart_get_divisor - return uart clock divisor + * @port: uart_port structure describing the port. + * @baud: desired baud rate + * + * Calculate the uart clock divisor for the port. + */ +unsigned int +uart_get_divisor(struct uart_port *port, unsigned int baud) +{ + unsigned int quot; + + /* + * Old custom speed handling. + */ + if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) + quot = port->custom_divisor; + else + quot = DIV_ROUND_CLOSEST(port->uartclk, 16 * baud); + + return quot; +} +EXPORT_SYMBOL(uart_get_divisor); + +#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) +/** + * uart_console_write - write a console message to a serial port + * @port: the port to write the message + * @s: array of characters + * @count: number of characters in string to write + * @putchar: function to write character to port + */ +void uart_console_write(struct uart_port *port, const char *s, + unsigned int count, + void (*putchar)(struct uart_port *, int)) +{ + unsigned int i; + + for (i = 0; i < count; i++, s++) { + if (*s == '\n') + putchar(port, '\r'); + putchar(port, *s); + } +} +EXPORT_SYMBOL_GPL(uart_console_write); + +/* + * Check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ +struct uart_port * __init +uart_get_console(struct uart_port *ports, int nr, struct console *co) +{ + int idx = co->index; + + if (idx < 0 || idx >= nr || (ports[idx].iobase == 0 && + ports[idx].membase == NULL)) + for (idx = 0; idx < nr; idx++) + if (ports[idx].iobase != 0 || + ports[idx].membase != NULL) + break; + + co->index = idx; + + return ports + idx; +} + +/** + * uart_parse_earlycon - Parse earlycon options + * @p: ptr to 2nd field (ie., just beyond ',') + * @iotype: ptr for decoded iotype (out) + * @addr: ptr for decoded mapbase/iobase (out) + * @options: ptr for field; NULL if not present (out) + * + * Decodes earlycon kernel command line parameters of the form + * earlycon=,io|mmio|mmio16|mmio32|mmio32be|mmio32native,, + * console=,io|mmio|mmio16|mmio32|mmio32be|mmio32native,, + * + * The optional form + * earlycon=,0x, + * console=,0x, + * is also accepted; the returned @iotype will be UPIO_MEM. + * + * Returns 0 on success or -EINVAL on failure + */ +int uart_parse_earlycon(char *p, unsigned char *iotype, resource_size_t *addr, + char **options) +{ + if (strncmp(p, "mmio,", 5) == 0) { + *iotype = UPIO_MEM; + p += 5; + } else if (strncmp(p, "mmio16,", 7) == 0) { + *iotype = UPIO_MEM16; + p += 7; + } else if (strncmp(p, "mmio32,", 7) == 0) { + *iotype = UPIO_MEM32; + p += 7; + } else if (strncmp(p, "mmio32be,", 9) == 0) { + *iotype = UPIO_MEM32BE; + p += 9; + } else if (strncmp(p, "mmio32native,", 13) == 0) { + *iotype = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) ? + UPIO_MEM32BE : UPIO_MEM32; + p += 13; + } else if (strncmp(p, "io,", 3) == 0) { + *iotype = UPIO_PORT; + p += 3; + } else if (strncmp(p, "0x", 2) == 0) { + *iotype = UPIO_MEM; + } else { + return -EINVAL; + } + + /* + * Before you replace it with kstrtoull(), think about options separator + * (',') it will not tolerate + */ + *addr = simple_strtoull(p, NULL, 0); + p = strchr(p, ','); + if (p) + p++; + + *options = p; + return 0; +} +EXPORT_SYMBOL_GPL(uart_parse_earlycon); + +/** + * uart_parse_options - Parse serial port baud/parity/bits/flow control. + * @options: pointer to option string + * @baud: pointer to an 'int' variable for the baud rate. + * @parity: pointer to an 'int' variable for the parity. + * @bits: pointer to an 'int' variable for the number of data bits. + * @flow: pointer to an 'int' variable for the flow control character. + * + * uart_parse_options decodes a string containing the serial console + * options. The format of the string is , + * eg: 115200n8r + */ +void +uart_parse_options(char *options, int *baud, int *parity, int *bits, int *flow) +{ + char *s = options; + + *baud = simple_strtoul(s, NULL, 10); + while (*s >= '0' && *s <= '9') + s++; + if (*s) + *parity = *s++; + if (*s) + *bits = *s++ - '0'; + if (*s) + *flow = *s; +} +EXPORT_SYMBOL_GPL(uart_parse_options); + +/** + * uart_set_options - setup the serial console parameters + * @port: pointer to the serial ports uart_port structure + * @co: console pointer + * @baud: baud rate + * @parity: parity character - 'n' (none), 'o' (odd), 'e' (even) + * @bits: number of data bits + * @flow: flow control character - 'r' (rts) + */ +int +uart_set_options(struct uart_port *port, struct console *co, + int baud, int parity, int bits, int flow) +{ + struct ktermios termios; + static struct ktermios dummy; + + /* + * Ensure that the serial console lock is initialised + * early. + */ + uart_port_lock_init(port); + + memset(&termios, 0, sizeof(struct ktermios)); + + termios.c_cflag |= CREAD | HUPCL | CLOCAL; + tty_termios_encode_baud_rate(&termios, baud, baud); + + if (bits == 7) + termios.c_cflag |= CS7; + else + termios.c_cflag |= CS8; + + switch (parity) { + case 'o': case 'O': + termios.c_cflag |= PARODD; + /*fall through*/ + case 'e': case 'E': + termios.c_cflag |= PARENB; + break; + } + + if (flow == 'r') + termios.c_cflag |= CRTSCTS; + + /* + * some uarts on other side don't support no flow control. + * So we set * DTR in host uart to make them happy + */ + port->mctrl |= TIOCM_DTR; + + port->ops->set_termios(port, &termios, &dummy); + /* + * Allow the setting of the UART parameters with a NULL console + * too: + */ + if (co) + co->cflag = termios.c_cflag; + + return 0; +} +EXPORT_SYMBOL_GPL(uart_set_options); +#endif /* CONFIG_SERIAL_CORE_CONSOLE */ + +/* + * Are the two ports equivalent? + */ +int uart_match_port(struct uart_port *port1, struct uart_port *port2) +{ + if (port1->iotype != port2->iotype) + return 0; + + switch (port1->iotype) { + case UPIO_PORT: + return (port1->iobase == port2->iobase); + case UPIO_HUB6: + return (port1->iobase == port2->iobase) && + (port1->hub6 == port2->hub6); + case UPIO_MEM: + case UPIO_MEM16: + case UPIO_MEM32: + case UPIO_MEM32BE: + case UPIO_AU: + case UPIO_TSI: + return (port1->mapbase == port2->mapbase); + } + return 0; +} +EXPORT_SYMBOL(uart_match_port); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 58484fb35c..505b51db59 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -402,6 +402,7 @@ void uart_unregister_driver(struct uart_driver *uart); int uart_add_one_port(struct uart_driver *reg, struct uart_port *port); int uart_remove_one_port(struct uart_driver *reg, struct uart_port *port); int uart_match_port(struct uart_port *port1, struct uart_port *port2); +void uart_port_lock_init(struct uart_port *port); /* * Power Management From patchwork Sat Apr 1 22:21:19 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Pitre X-Patchwork-Id: 96581 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp1303614qgd; Sat, 1 Apr 2017 15:21:47 -0700 (PDT) X-Received: by 10.84.229.151 with SMTP id c23mr11529595plk.27.1491085307507; Sat, 01 Apr 2017 15:21:47 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id f3si9246938pln.137.2017.04.01.15.21.47; Sat, 01 Apr 2017 15:21:47 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752066AbdDAWVq (ORCPT + 24 others); Sat, 1 Apr 2017 18:21:46 -0400 Received: from alt42.smtp-out.videotron.ca ([23.233.128.29]:20757 "EHLO alt42.smtp-out.videotron.ca" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751972AbdDAWVb (ORCPT ); Sat, 1 Apr 2017 18:21:31 -0400 Received: from yoda.home ([96.23.157.65]) by Videotron with SMTP id uROicmFUYcMN9uROjcgFyc; Sat, 01 Apr 2017 18:21:29 -0400 X-Authority-Analysis: v=2.1 cv=YqOvP9sX c=1 sm=1 tr=0 a=keA3yYpnlypCNW5BNWqu+w==:117 a=keA3yYpnlypCNW5BNWqu+w==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=AzvcPWV-tVgA:10 a=KKAkSRfTAAAA:8 a=VwQbUJbxAAAA:8 a=yWBngyN9vBZuigoEwt4A:9 a=YFn_yOVhCxx0V7nK:21 a=joG3JuO4nAXgxi7_:21 a=UbcTF4XiCdogQlFq:21 a=cvBusfyB2V15izCimMoJ:22 a=AjGcO6oz07-iQ99wixmX:22 Received: from xanadu.home (xanadu.home [192.168.2.2]) by yoda.home (Postfix) with ESMTP id 6F0522DA06A4; Sat, 1 Apr 2017 18:21:27 -0400 (EDT) From: Nicolas Pitre To: Greg Kroah-Hartman , Jiri Slaby , linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v2 5/5] minitty: minimal TTY support alternative for serial ports Date: Sat, 1 Apr 2017 18:21:19 -0400 Message-Id: <20170401222119.25106-6-nicolas.pitre@linaro.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170401222119.25106-1-nicolas.pitre@linaro.org> References: <20170401222119.25106-1-nicolas.pitre@linaro.org> X-CMAE-Envelope: MS4wfCv0X8GMyhlpj1OwP/pNfjdnUWdOj5n0KbsvMUyrS3C8H28sp/qfzRwy9Eecy4gc1gifZj+gNxCiPSHGP8OAUe6fbfq4g98H3C+4B0KCcDy32/GtIiZN wLmZZcQrb0H1B2XbywAi8NAdWCP/eVtFpyEcz4BiQTrEDQ41Prddjnw5okft2VpVwY/yVNbxhLBYv9ub00xFNRKH1Xttf+OiRtsEXfkT0ew3Wz6bPXR8cIoz kxvttzpicYFL3U1B0yTWpYeFcHjVfe4zSeCRGVbsqzL1d7QGBxMeFmuord9Wir6CS3YyNdq9QoSL1w8tDhyqEqOulH615xVJ1Qvso4jEGew= Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This is a minimal TTY layer alternative for embedded systems with limited capabilities. This supports only serial ports, supports only a subset of the default line discipline, and dispense with anything that is of no use for a small embedded system. The goal here is to minimize memory footprint. The code size is more than 5x smaller than the regular code providing the same functionalities. Runtime memory usage is greatly reduced as well. Signed-off-by: Nicolas Pitre --- MAINTAINERS | 8 +- drivers/tty/Kconfig | 10 +- drivers/tty/Makefile | 1 + drivers/tty/serial/Kconfig | 12 +- drivers/tty/serial/Makefile | 2 + .../tty/serial/{serial_core.c => fulltty_serial.c} | 0 drivers/tty/serial/minitty_serial.c | 1793 ++++++++++++++++++++ include/linux/tty_flip.h | 9 + 8 files changed, 1829 insertions(+), 6 deletions(-) rename drivers/tty/serial/{serial_core.c => fulltty_serial.c} (100%) create mode 100644 drivers/tty/serial/minitty_serial.c -- 2.9.3 diff --git a/MAINTAINERS b/MAINTAINERS index c776906f67..12523d7f97 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8410,6 +8410,12 @@ F: include/linux/cciss*.h F: include/uapi/linux/cciss*.h F: Documentation/scsi/smartpqi.txt +MINI TTY SUBSTITUTION FOR SERIAL PORTS +M: Nicolas Pitre +L: linux-serial@vger.kernel.org +S: Maintained +F: drivers/tty/serial/minitty_serial.c + MN88472 MEDIA DRIVER M: Antti Palosaari L: linux-media@vger.kernel.org @@ -12743,7 +12749,7 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git F: Documentation/serial/ F: drivers/tty/ -F: drivers/tty/serial/serial_core.c +F: drivers/tty/serial/fulltty_serial.c F: include/linux/serial_core.h F: include/linux/serial.h F: include/linux/tty.h diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index 95103054c0..8517c353d8 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -2,10 +2,12 @@ config TTY bool "Enable TTY" if EXPERT default y ---help--- - Allows you to remove TTY support which can save space, and - blocks features that require TTY from inclusion in the kernel. - TTY is required for any text terminals or serial port - communication. Most users should leave this enabled. + Allows you to remove the full-featured TTY support which can save + space, and blocks features that require it from inclusion in the + kernel. TTY support is required for any text terminals or serial + port communication. If turned off, a much smaller TTY implementation + that only supports serial ports in a limited capacity may be + selected instead. Most users should leave this enabled. if TTY diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 1461be6b90..9b7b3418cd 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \ tty_buffer.o tty_port.o tty_mutex.o tty_ldsem.o tty_baudrate.o +obj-$(CONFIG_MINITTY_SERIAL) += tty_baudrate.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-$(CONFIG_AUDIT) += tty_audit.o diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 6117ac8da4..a552387a39 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -2,7 +2,17 @@ # Serial device configuration # -if TTY +config MINITTY_SERIAL + bool "Enable mini TTY for serial ports" + depends on !TTY + default y + help + This enables a much smaller TTY implementation that only supports + serial ports in a limited capacity. This is however sufficient for + many embedded use cases that use serial ports mainly as a debug + console where the saving in kernel code size is welcome. + +if TTY || MINITTY_SERIAL menu "Serial drivers" depends on HAS_IOMEM diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 073afd10c5..92027db811 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -2,6 +2,8 @@ # Makefile for the kernel serial device drivers. # +serial_core-$(CONFIG_TTY) := fulltty_serial.o +serial_core-$(CONFIG_MINITTY_SERIAL) := minitty_serial.o obj-$(CONFIG_SERIAL_CORE) += serial_core.o serial_lib.o obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/fulltty_serial.c similarity index 100% rename from drivers/tty/serial/serial_core.c rename to drivers/tty/serial/fulltty_serial.c diff --git a/drivers/tty/serial/minitty_serial.c b/drivers/tty/serial/minitty_serial.c new file mode 100644 index 0000000000..99af6dc180 --- /dev/null +++ b/drivers/tty/serial/minitty_serial.c @@ -0,0 +1,1793 @@ +/* + * Smallest shortcut replacement for tty and serial core layers. + * + * Based mainly on tty_io.c, n_tty.c and serial_core.c from many smart people. + * + * Created by: Nicolas Pitre, January 2017 + * Copyright: (C) 2017 Linaro Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct minitty_data { + struct uart_state state; + struct ktermios termios; + struct mutex mutex; + unsigned char *rx_buf; + int rx_head, rx_vetted, rx_tail; + int rx_lines, column, canon_start_pos; + bool rx_raw; + bool rx_overflow; + wait_queue_head_t write_wait; + wait_queue_head_t read_wait; + struct work_struct rx_work; + struct cdev cdev; + struct device *dev; + int usecount; +}; + +#define RX_BUF_SIZE PAGE_SIZE +#define RX_BUF_WRAP(x) ((x) & (RX_BUF_SIZE - 1)) + +/* + * Functions called back by low level UART drivers when + * the TX buffer is getting near empty. + */ +void uart_write_wakeup(struct uart_port *port) +{ + struct uart_state *state = port->state; + struct minitty_data *mtty = container_of(state, typeof(*mtty), state); + + wake_up_interruptible_poll(&mtty->write_wait, POLLOUT); +} +EXPORT_SYMBOL(uart_write_wakeup); + +static void +uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear) +{ + unsigned long flags; + unsigned int old; + + spin_lock_irqsave(&port->lock, flags); + old = port->mctrl; + port->mctrl = (old & ~clear) | set; + if (old != port->mctrl) + port->ops->set_mctrl(port, port->mctrl); + spin_unlock_irqrestore(&port->lock, flags); +} + +#define uart_set_mctrl(port, set) uart_update_mctrl(port, set, 0) +#define uart_clear_mctrl(port, clear) uart_update_mctrl(port, 0, clear) + +static void uart_change_pm(struct uart_state *state, + enum uart_pm_state pm_state) +{ + struct uart_port *port =state->uart_port; + + if (state->pm_state != pm_state) { + if (port && port->ops->pm) + port->ops->pm(port, pm_state, state->pm_state); + state->pm_state = pm_state; + } +} + +int uart_suspend_port(struct uart_driver *drv, struct uart_port *port) +{ + return -EPROTONOSUPPORT; +} +EXPORT_SYMBOL(uart_suspend_port); + +int uart_resume_port(struct uart_driver *drv, struct uart_port *port) +{ + return -EPROTONOSUPPORT; +} +EXPORT_SYMBOL(uart_resume_port); + +/** + * uart_handle_dcd_change - handle a change of carrier detect state + * @port: uart_port structure for the open port + * @status: new carrier detect status, nonzero if active + * + * Caller must hold port->lock + */ +void uart_handle_dcd_change(struct uart_port *port, unsigned int status) +{ + port->icount.dcd++; +} +EXPORT_SYMBOL_GPL(uart_handle_dcd_change); + +/** + * uart_handle_cts_change - handle a change of clear-to-send state + * @port: uart_port structure for the open port + * @status: new clear to send status, nonzero if active + * + * Caller must hold port->lock + */ +void uart_handle_cts_change(struct uart_port *port, unsigned int status) +{ + port->icount.cts++; + + if (uart_softcts_mode(port)) { + if (port->hw_stopped) { + if (status) { + port->hw_stopped = 0; + port->ops->start_tx(port); + uart_write_wakeup(port); + } + } else { + if (!status) { + port->hw_stopped = 1; + port->ops->stop_tx(port); + } + } + } +} +EXPORT_SYMBOL_GPL(uart_handle_cts_change); + +static void uart_start_tx(struct minitty_data *mtty) +{ + struct uart_port *port = mtty->state.uart_port; + spin_lock_irq(&port->lock); + if (!port->hw_stopped) + port->ops->start_tx(port); + spin_unlock_irq(&port->lock); +} + +static int uart_chars_in_buffer(struct minitty_data *mtty) +{ + struct uart_state *state = &mtty->state; + struct uart_port *port = mtty->state.uart_port; + int ret; + + spin_lock_irq(&port->lock); + ret = uart_circ_chars_pending(&state->xmit); + spin_unlock_irq(&port->lock); + return ret; +} + +static void uart_flush_tx_buffer(struct minitty_data *mtty) +{ + struct uart_state *state = &mtty->state; + struct uart_port *port = mtty->state.uart_port; + + spin_lock_irq(&port->lock); + uart_circ_clear(&state->xmit); + if (port->ops->flush_buffer) + port->ops->flush_buffer(port); + spin_unlock_irq(&port->lock); + uart_write_wakeup(port); +} + +static int uart_get_lsr_info(struct minitty_data *mtty, unsigned int __user *p) +{ + struct uart_state *state = &mtty->state; + struct uart_port *port = mtty->state.uart_port; + unsigned int result; + + mutex_lock(&mtty->mutex); + result = port->ops->tx_empty(port); + + /* + * If we're about to load something into the transmit + * register, we'll pretend the transmitter isn't empty to + * avoid a race condition (depending on when the transmit + * interrupt happens). + */ + if (port->x_char || + ((uart_circ_chars_pending(&state->xmit) > 0) && + !uart_tx_stopped(port))) + result &= ~TIOCSER_TEMT; + mutex_unlock(&mtty->mutex); + return put_user(result, p); +} + +static int uart_tiocmget(struct minitty_data *mtty, int __user *p) +{ + struct uart_port *port = mtty->state.uart_port; + int ret = -EIO; + + mutex_lock(&mtty->mutex); + ret = port->mctrl; + spin_lock_irq(&port->lock); + ret |= port->ops->get_mctrl(port); + spin_unlock_irq(&port->lock); + mutex_unlock(&mtty->mutex); + if (ret >= 0) + ret = put_user(ret, p); + return ret; +} + +static int +uart_tiocmset(struct minitty_data *mtty, unsigned int cmd, unsigned __user *p) +{ + struct uart_port *port = mtty->state.uart_port; + unsigned int set, clear, val; + int ret; + + ret = get_user(val, p); + if (ret) + return ret; + set = clear = 0; + switch (cmd) { + case TIOCMBIS: + set = val; + break; + case TIOCMBIC: + clear = val; + break; + case TIOCMSET: + set = val; + clear = ~val; + break; + } + set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP; + clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP; + + mutex_lock(&mtty->mutex); + uart_update_mctrl(port, set, clear); + mutex_unlock(&mtty->mutex); + return 0; +} + +static int uart_break_ctl(struct minitty_data *mtty, int break_state) +{ + struct uart_port *port = mtty->state.uart_port; + int ret = -EIO; + + mutex_lock(&mtty->mutex); + port->ops->break_ctl(port, break_state); + mutex_unlock(&mtty->mutex); + return ret; +} + +/* + * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change + * - mask passed in arg for lines of interest + * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) + * Caller should use TIOCGICOUNT to see which one it was + */ +static int uart_wait_modem_status(struct minitty_data *mtty, unsigned long arg) +{ + struct uart_port *uport = mtty->state.uart_port; + struct tty_port *port = &mtty->state.port; + DECLARE_WAITQUEUE(wait, current); + struct uart_icount cprev, cnow; + int ret; + + /* + * note the counters on entry + */ + spin_lock_irq(&uport->lock); + memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); + if (uport->ops->enable_ms) + uport->ops->enable_ms(uport); + spin_unlock_irq(&uport->lock); + + add_wait_queue(&port->delta_msr_wait, &wait); + for (;;) { + spin_lock_irq(&uport->lock); + memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); + spin_unlock_irq(&uport->lock); + + set_current_state(TASK_INTERRUPTIBLE); + + if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || + ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || + ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || + ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) { + ret = 0; + break; + } + + schedule(); + + /* see if a signal did it */ + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + cprev = cnow; + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&port->delta_msr_wait, &wait); + + return ret; +} + +/* + * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) + * NB: both 1->0 and 0->1 transitions are counted except for + * RI where only 0->1 is counted. + */ +static int uart_tiocgicount(struct minitty_data *mtty, void __user *p) +{ + struct uart_port *port = mtty->state.uart_port; + struct serial_icounter_struct icount; + struct uart_icount cnow; + + spin_lock_irq(&port->lock); + memcpy(&cnow, &port->icount, sizeof(struct uart_icount)); + spin_unlock_irq(&port->lock); + + memset(&icount, 0, sizeof(icount)); + icount.cts = cnow.cts; + icount.dsr = cnow.dsr; + icount.rng = cnow.rng; + icount.dcd = cnow.dcd; + icount.rx = cnow.rx; + icount.tx = cnow.tx; + icount.frame = cnow.frame; + icount.overrun = cnow.overrun; + icount.parity = cnow.parity; + icount.brk = cnow.brk; + icount.buf_overrun = cnow.buf_overrun; + if (copy_to_user(p, &icount, sizeof(icount))) + return -EFAULT; + return 0; +} + +static void uart_change_speed(struct minitty_data *mtty, + struct ktermios *old_termios) +{ + struct uart_port *port = mtty->state.uart_port; + struct ktermios *termios = &mtty->termios; + int hw_stopped; + + port->ops->set_termios(port, termios, old_termios); + + /* + * Set modem status enables based on termios cflag + */ + spin_lock_irq(&port->lock); + if (termios->c_cflag & CRTSCTS) + port->status |= UPSTAT_CTS_ENABLE; + else + port->status &= ~UPSTAT_CTS_ENABLE; + + if (termios->c_cflag & CLOCAL) + port->status &= ~UPSTAT_DCD_ENABLE; + else + port->status |= UPSTAT_DCD_ENABLE; + + /* reset sw-assisted CTS flow control based on (possibly) new mode */ + hw_stopped = port->hw_stopped; + port->hw_stopped = uart_softcts_mode(port) && + !(port->ops->get_mctrl(port) & TIOCM_CTS); + if (port->hw_stopped) { + if (!hw_stopped) + port->ops->stop_tx(port); + } else { + if (hw_stopped) + port->ops->start_tx(port); + } + spin_unlock_irq(&port->lock); +} + +static void uart_set_termios(struct minitty_data *mtty, + struct ktermios *old_termios) +{ + struct uart_port *port = mtty->state.uart_port; + unsigned int cflag = mtty->termios.c_cflag; + unsigned int iflag_mask = IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK; + bool sw_changed = false; + + /* + * Drivers doing software flow control also need to know + * about changes to these input settings. + */ + if (port->flags & UPF_SOFT_FLOW) { + iflag_mask |= IXANY|IXON|IXOFF; + sw_changed = + mtty->termios.c_cc[VSTART] != old_termios->c_cc[VSTART] || + mtty->termios.c_cc[VSTOP] != old_termios->c_cc[VSTOP]; + } + + /* + * These are the bits that are used to setup various + * flags in the low level driver. We can ignore the Bfoo + * bits in c_cflag; c_[io]speed will always be set + * appropriately by set_termios(). + */ + if ((cflag ^ old_termios->c_cflag) == 0 && + mtty->termios.c_ospeed == old_termios->c_ospeed && + mtty->termios.c_ispeed == old_termios->c_ispeed && + ((mtty->termios.c_iflag ^ old_termios->c_iflag) & iflag_mask) == 0 && + !sw_changed) + return; + + uart_change_speed(mtty, old_termios); + /* reload cflag from termios; port driver may have overriden flags */ + cflag = mtty->termios.c_cflag; + + /* Handle transition to B0 status */ + if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) + uart_clear_mctrl(port, TIOCM_RTS | TIOCM_DTR); + /* Handle transition away from B0 status */ + else if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { + unsigned int mask = TIOCM_DTR; + if (!(cflag & CRTSCTS)) + mask |= TIOCM_RTS; + uart_set_mctrl(port, mask); + } +} + +static void uart_wait_until_sent(struct minitty_data *mtty) +{ + struct uart_port *port = mtty->state.uart_port; + unsigned long char_time, expire, timeout; + + /* + * Set the check interval to be 1/5 of the estimated time to + * send a single character, and make it at least 1. + * + * Note: we have to use pretty tight timings here to satisfy + * the NIST-PCTS. + */ + char_time = (port->timeout - HZ/50) / port->fifosize; + char_time = char_time / 5; + if (char_time == 0) + char_time = 1; + + /* + * If the transmitter hasn't cleared in twice the approximate + * amount of time to send the entire FIFO, it probably won't + * ever clear. This assumes the UART isn't doing flow + * control, which is currently the case. Hence, if it ever + * takes longer than port->timeout, this is probably due to a + * UART bug of some kind. So, we clamp the timeout parameter at + * 2*port->timeout. + */ + timeout = 2 * port->timeout; + + expire = jiffies + timeout; + while (!port->ops->tx_empty(port)) { + msleep_interruptible(jiffies_to_msecs(char_time)); + if (signal_pending(current)) + break; + if (time_after(jiffies, expire)) + break; + } +} + +static void mtty_wait_until_sent(struct minitty_data *mtty) +{ + long timeout = MAX_SCHEDULE_TIMEOUT; + + timeout = wait_event_interruptible_timeout(mtty->write_wait, + !uart_chars_in_buffer(mtty), timeout); + if (timeout > 0) + uart_wait_until_sent(mtty); +} + +static void mtty_set_termios(struct minitty_data *mtty, + struct ktermios *old_termios) +{ + bool was_raw = mtty->rx_raw; + + mtty->rx_raw = !I_IGNCR(mtty) && !I_ICRNL(mtty) && !I_INLCR(mtty) && + !L_ICANON(mtty) && !L_ISIG(mtty) && !L_ECHO(mtty); + if (!mtty->rx_raw && was_raw) + mtty->rx_lines = mtty->column = mtty->canon_start_pos = 0; + + /* mark things we don't support. */ + mtty->termios.c_iflag |= IGNBRK | IGNPAR; + mtty->termios.c_iflag &= ~(ISTRIP | IUCLC | IXON | IXOFF); + mtty->termios.c_lflag &= ~IEXTEN; + + /* The termios change make the tty ready for I/O */ + wake_up_interruptible(&mtty->write_wait); + wake_up_interruptible(&mtty->read_wait); +} + +static int set_termios(struct minitty_data *mtty, unsigned int cmd, + void __user *arg) +{ + struct ktermios new_termios, old_termios; + int ret; + + mutex_lock(&mtty->mutex); + new_termios = mtty->termios; + mutex_unlock(&mtty->mutex); + + switch (cmd) { + case TCSETAF: + case TCSETAW: + case TCSETA: + ret = user_termio_to_kernel_termios(&new_termios, + (struct termio __user *)arg); + break; +#ifdef TCGETS2 + case TCSETSF2: + case TCSETSW2: + case TCSETS2: + ret = user_termios_to_kernel_termios(&new_termios, + (struct termios2 __user *)arg); + break; + default: + ret = user_termios_to_kernel_termios_1(&new_termios, + (struct termios __user *)arg); + break; +#else + default: + ret = user_termios_to_kernel_termios(&new_termios, + (struct termios __user *)arg); + break; +#endif + } + if (ret) + return -EFAULT; + + switch (cmd) { + case TCSETSF: +#ifdef TCGETS2 + case TCSETSF2: +#endif + case TCSETAF: + uart_flush_tx_buffer(mtty); + } + + switch (cmd) { + case TCSETSF: + case TCSETSW: +#ifdef TCGETS2 + case TCSETSF2: + case TCSETSW2: +#endif + case TCSETAF: + case TCSETAW: + mtty_wait_until_sent(mtty); + if (signal_pending(current)) + return -ERESTARTSYS; + } + + /* + * If old style Bfoo values are used then load c_ispeed/c_ospeed + * with the real speed so its unconditionally usable. + */ + new_termios.c_ispeed = tty_termios_input_baud_rate(&new_termios); + new_termios.c_ospeed = tty_termios_baud_rate(&new_termios); + + mutex_lock(&mtty->mutex); + old_termios = mtty->termios; + mtty->termios = new_termios; + mtty_set_termios(mtty, &old_termios); + uart_set_termios(mtty, &old_termios); + mutex_unlock(&mtty->mutex); + return 0; +} + +static int tiocsetd(int __user *p) +{ + int ldisc; + + if (get_user(ldisc, p)) + return -EFAULT; + if (ldisc != N_TTY) + return -EINVAL; + return 0; +} + +static int tiocgetd(int __user *p) +{ + return put_user(N_TTY, p); +} + +static void copy_termios(struct minitty_data *mtty, struct ktermios *kterm) +{ + mutex_lock(&mtty->mutex); + *kterm = mtty->termios; + mutex_unlock(&mtty->mutex); +} + +static long minitty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct minitty_data *mtty = file->private_data; + struct uart_port *port = mtty->state.uart_port; + void __user *p = (void __user *)arg; + struct ktermios kterm; + int ret = -ENOIOCTLCMD; + + switch (cmd) { + case TIOCSETD: + return tiocsetd(p); + case TIOCGETD: + return tiocgetd(p); + case TIOCSBRK: + return uart_break_ctl(mtty, -1); + case TIOCCBRK: + return uart_break_ctl(mtty, 0); + case TIOCMGET: + return uart_tiocmget(mtty, p); + case TIOCMSET: + case TIOCMBIC: + case TIOCMBIS: + return uart_tiocmset(mtty, cmd, p); + case TIOCGICOUNT: + return uart_tiocgicount(mtty, p); + case TIOCMIWAIT: + return uart_wait_modem_status(mtty, arg); + case TIOCSERGETLSR: + return uart_get_lsr_info(mtty, p); + +#ifndef TCGETS2 + case TCGETS: + copy_termios(mtty, &kterm); + if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm)) + return -EFAULT; + return 0; +#else + case TCGETS: + copy_termios(mtty, &kterm); + if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm)) + return -EFAULT; + return 0; + case TCGETS2: + copy_termios(mtty, &kterm); + if (kernel_termios_to_user_termios((struct termios2 __user *)arg, &kterm)) + return -EFAULT; + return 0; + case TCSETSF2: + case TCSETSW2: + case TCSETS2: +#endif + case TCSETSF: + case TCSETSW: + case TCSETS: + case TCSETAF: + case TCSETAW: + case TCSETA: + return set_termios(mtty, cmd, p); + case TCGETA: + copy_termios(mtty, &kterm); + if (kernel_termios_to_user_termio((struct termio __user *)arg, &kterm)) + return -EFAULT; + return 0; + + default: + mutex_lock(&mtty->mutex); + if (port->ops->ioctl) + ret = port->ops->ioctl(port, cmd, arg); + mutex_unlock(&mtty->mutex); + break; + } + + if (ret == -ENOIOCTLCMD) + ret = -EINVAL; + return ret; +} + +/* + * Functions called back by low level UART drivers normally provided by + * the regular TTY layer to deliver RX data that we have to emulate. + * We ssimply ignore characters with errors here. + */ + +int tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag) +{ + struct uart_state *state = container_of(port, struct uart_state, port); + struct minitty_data *mtty = container_of(state, typeof(*mtty), state); + + if (flag == TTY_NORMAL) { + int tail = smp_load_acquire(&mtty->rx_tail); + int head = mtty->rx_head; + int next = RX_BUF_WRAP(head + 1); + /* + * Advance head only if buffer is not full. + * Keep on overwriting last char otherwise. + */ + mtty->rx_buf[head] = ch; + if (next != tail) { + smp_store_release(&mtty->rx_head, next); + return 1; + } else { + smp_store_release(&mtty->rx_overflow, true); + } + } + return 0; +} +EXPORT_SYMBOL(tty_insert_flip_char); + +void uart_insert_char(struct uart_port *port, unsigned int status, + unsigned int overrun, unsigned int ch, unsigned int flag) +{ + struct uart_state *state = port->state; + struct minitty_data *mtty = container_of(state, typeof(*mtty), state); + + if (flag == TTY_NORMAL) { + int tail = smp_load_acquire(&mtty->rx_tail); + int head = mtty->rx_head; + int next = RX_BUF_WRAP(head + 1); + /* + * Advance head only if buffer is not full. + * Keep on overwriting last char otherwise. + */ + mtty->rx_buf[head] = ch; + if (next != tail) { + smp_store_release(&mtty->rx_head, next); + } else { + smp_store_release(&mtty->rx_overflow, true); + port->icount.buf_overrun++; + } + } +} +EXPORT_SYMBOL_GPL(uart_insert_char); + +int tty_insert_flip_string(struct tty_port *port, const unsigned char *chars, + size_t size) +{ + struct uart_state *state = container_of(port, struct uart_state, port); + struct minitty_data *mtty = container_of(state, typeof(*mtty), state); + int head, tail, len, ret = 0; + + tail = smp_load_acquire(&mtty->rx_tail); + head = mtty->rx_head; + do { + len = CIRC_SPACE(head, tail, RX_BUF_SIZE); + if (len > size) + len = size; + memcpy(mtty->rx_buf+head, chars, len); + head = RX_BUF_WRAP(head + len); + chars += len; + size -= len; + ret += len; + } while (size && len && head == 0); + smp_store_release(&mtty->rx_head, head); + return ret; +} +EXPORT_SYMBOL(tty_insert_flip_string); + +int tty_buffer_request_room(struct tty_port *port, size_t size) +{ + struct uart_state *state = container_of(port, struct uart_state, port); + struct minitty_data *mtty = container_of(state, typeof(*mtty), state); + int tail = smp_load_acquire(&mtty->rx_tail); + int head = mtty->rx_head; + int space = CIRC_SPACE(head, tail, RX_BUF_SIZE); + return size < space ? size : space; +} +EXPORT_SYMBOL_GPL(tty_buffer_request_room); + +void tty_schedule_flip(struct tty_port *port) +{ + struct uart_state *state = container_of(port, struct uart_state, port); + struct minitty_data *mtty = container_of(state, typeof(*mtty), state); + + queue_work(system_unbound_wq, &mtty->rx_work); +} +EXPORT_SYMBOL(tty_schedule_flip); + +void tty_flip_buffer_push(struct tty_port *port) +{ + tty_schedule_flip(port); +} +EXPORT_SYMBOL(tty_flip_buffer_push); + +/* + * Line Discipline Stuff + */ + +static bool is_utf8_continuation(struct minitty_data *mtty, unsigned char c) +{ + return (I_IUTF8(mtty) && (c & 0xc0) == 0x80); +} + +static bool is_line_termination(struct minitty_data *mtty, unsigned char c) +{ + return (c == '\n' || c == EOF_CHAR(mtty) || c == EOL_CHAR(mtty)); +} + +/* + * Queue the provided character string in its entirety or nothing. + * Return true if queued, false otherwise. + */ +static bool queue_tx_chars(struct minitty_data *mtty, unsigned char *s, int len) +{ + struct circ_buf *circ = &mtty->state.xmit; + int head, tail, space; + + tail = smp_load_acquire(&circ->tail); + head = circ->head; + space = CIRC_SPACE(head, tail, UART_XMIT_SIZE); + if (space < len) + return false; + while (len--) { + circ->buf[head] = *s++; + head = (head + 1) & (UART_XMIT_SIZE - 1); + } + smp_store_release(&circ->head, head); + return true; +} + +/* + * Queue characters in their cooked sequence. + * Return true if queued, or false otherwise. + */ +static bool tx_cooked_char(struct minitty_data *mtty, unsigned char c) +{ + int spaces, next_col = mtty->column; + + switch (c) { + case '\n': + if (O_ONLRET(mtty)) + next_col = 0; + if (O_ONLCR(mtty)) { + if (!queue_tx_chars(mtty, "\r\n", 2)) + return false; + mtty->column = mtty->canon_start_pos = 0; + return true; + } + break; + case '\r': + if (O_ONOCR(mtty) && mtty->column == 0) + return true; + if (O_OCRNL(mtty)) { + c = '\n'; + if (O_ONLRET(mtty)) + next_col = 0; + } else + next_col = 0; + break; + case '\t': + spaces = 8 - (mtty->column & 7); + if (O_TABDLY(mtty) == XTABS) { + if (!queue_tx_chars(mtty, " ", spaces)) + return false; + mtty->column += spaces; + return true; + } + next_col += spaces; + break; + case '\b': + if (next_col > 0) + next_col--; + break; + default: + if (iscntrl(c)) + break; + if (is_utf8_continuation(mtty, c)) + break; + next_col++; + break; + } + if (!queue_tx_chars(mtty, &c, 1)) + return false; + mtty->column = next_col; + if (next_col == 0) + mtty->canon_start_pos = 0; + return true; +} + +/* + * Queue echoed characters, converting CTRL sequences into "^X" if need be. + * Return true if queued, or false otherwise. + */ +static bool echo_rx_char(struct minitty_data *mtty, unsigned char c) +{ + if (L_ECHOCTL(mtty) && iscntrl(c) && c != '\t' && c != '\n') { + unsigned char buf[2]; + buf[0] = '^'; + buf[1] = c ^ 0100; + return queue_tx_chars(mtty, buf, 2); + } + if (O_OPOST(mtty)) + return tx_cooked_char(mtty, c); + else + return queue_tx_chars(mtty, &c, 1); +} + +/* + * Remove character from RX buffer at given position by shifting + * all preceding characters ahead. + */ +static void eat_rx_char(struct minitty_data *mtty, int pos) +{ + unsigned char *buf = mtty->rx_buf; + int tail = mtty->rx_tail; + int bottom = (tail <= pos) ? tail : 0; + + memmove(&buf[bottom+1], &buf[bottom], pos - bottom); + if (tail > pos) { + buf[0] = buf[RX_BUF_SIZE-1]; + memmove(&buf[tail+1], &buf[tail], RX_BUF_SIZE - 1 - tail); + } + smp_store_release(&mtty->rx_tail, RX_BUF_WRAP(tail + 1)); +} + +/* + * Create needed erase sequence according to the erase character c at + * position pos in the RX buffer. The erase sequence is sent for each + * erased characters and only if that succeeds then the character is + * actually removed from the buffer. The erase character itself is removed + * last so if the whole erase sequence cannot be completed then this can + * be resumed later. + */ +static bool erase_rx_char(struct minitty_data *mtty, unsigned char c, int pos) +{ + int prev_pos = RX_BUF_WRAP(pos - 1); + bool seen_alnum = false; + + while (pos != mtty->rx_tail) { + unsigned char prev_c = mtty->rx_buf[prev_pos]; + + if (is_line_termination(mtty, prev_c)) { + /* End of previous line: we don't erase further. */ + break; + } + + if (is_utf8_continuation(mtty, prev_c)) { + /* UTF8 continuation char: we just drop it */ + eat_rx_char(mtty, prev_pos); + continue; + } + + if (c == WERASE_CHAR(mtty) && seen_alnum && !isalnum(prev_c)) { + /* Beginning of previous word: we don't erase further */ + break; + } + + if (prev_c == '\t') { + /* depends on characters before the tab */ + int spaces = 0; + int i = prev_pos; + while (i != mtty->rx_tail) { + unsigned char before; + i = RX_BUF_WRAP(i - 1); + before = mtty->rx_buf[i]; + if (before == '\t') + break; + if (is_line_termination(mtty, before)) + break; + if (L_ECHOCTL(mtty) && iscntrl(before)) + spaces += 2; + else if (is_utf8_continuation(mtty, before)) + continue; + else if (!iscntrl(before)) + spaces++; + } + if (i == mtty->rx_tail) + spaces += mtty->canon_start_pos; + spaces = 8 - (spaces & 7); + if (!queue_tx_chars(mtty, "\b\b\b\b\b\b\b\b", spaces)) + return false; + mtty->column -= spaces; + } else if (L_ECHOCTL(mtty) && iscntrl(prev_c)) { + /* control chars were printed as "^X" */ + if (!queue_tx_chars(mtty, "\b\b \b\b", 6)) + return false; + mtty->column -= 2; + } else if (!iscntrl(prev_c)) { + if (!queue_tx_chars(mtty, "\b \b", 3)) + return false; + mtty->column -= 1; + } + + /* erase sequence sent, now remove the char from the buffer */ + eat_rx_char(mtty, prev_pos); + + if (c == ERASE_CHAR(mtty)) + break; + } + + /* Finally remove the erase character itself. */ + eat_rx_char(mtty, pos); + return true; +} + +/* + * Process RX bytes: canonical mode, echo, signals, etc. + * This might not process all RX characters if e.g. there is not enough + * room in the TX buffer to contain corresponding echo sequences. + */ +static void minitty_process_rx(struct minitty_data *mtty) +{ + bool xmit = false; + int i, head; + + head = smp_load_acquire(&mtty->rx_head); + + if (mtty->rx_raw) { + smp_store_release(&mtty->rx_vetted, head); + return; + } + + /* + * RX overflow mitigation: evaluate the last received character + * stored at the very head of the buffer in case it might be a + * signal or newline character that could kick the reader into + * action. We potentially overwrite the last vetted character but + * we're past any concern for lost characters at this point. + */ + if (unlikely(mtty->rx_overflow)) { + WRITE_ONCE(mtty->rx_overflow, false); + if (RX_BUF_WRAP(head + 1) == mtty->rx_tail) { + i = RX_BUF_WRAP(head - 1); + mtty->rx_buf[i] = mtty->rx_buf[head]; + if (mtty->rx_vetted == head) + mtty->rx_vetted = i; + } + } + + for (i = mtty->rx_vetted; i != head; i = RX_BUF_WRAP(i + 1)) { + unsigned char c = mtty->rx_buf[i]; + + if (c == '\r') { + if (I_IGNCR(mtty)) { + eat_rx_char(mtty, i); + continue; + } + if (I_ICRNL(mtty)) + mtty->rx_buf[i] = c = '\n'; + } else if (c == '\n' && I_INLCR(mtty)) + mtty->rx_buf[i] = c = '\r'; + + if (L_ICANON(mtty)) { + if ((L_ECHOE(mtty) && c == ERASE_CHAR(mtty)) || + (L_ECHOE(mtty) && c == WERASE_CHAR(mtty)) || + (L_ECHOK(mtty) && c == KILL_CHAR(mtty))) { + xmit = true; + if (!erase_rx_char(mtty, c, i)) + break; + continue; + } + if (is_line_termination(mtty, c)) { + mtty->rx_lines++; + if (c != '\n') + continue; + } + } + + if (L_ECHO(mtty) || (c == '\n' && L_ECHONL(mtty))) { + xmit = true; + if (!echo_rx_char(mtty, c)) + break; + } + } + + smp_store_release(&mtty->rx_vetted, i); + + if (xmit) + uart_start_tx(mtty); +} + +static bool rx_data_available(struct minitty_data *mtty, bool poll) +{ + bool data_avail = (mtty->rx_tail != mtty->rx_vetted); + if (data_avail && !L_ICANON(mtty)) { + int amt = poll && !TIME_CHAR(mtty) && MIN_CHAR(mtty) ? + MIN_CHAR(mtty) : 1; + data_avail = RX_BUF_WRAP(mtty->rx_vetted - mtty->rx_tail) >= amt; + } else if (data_avail && !mtty->rx_lines) { + /* wait for a full line */ + data_avail = false; + } else if (!data_avail && mtty->rx_lines) { + /* + * This may happen if the RX buffer was flushed by a signal + * or during RX overflow. Let's just reset it to zero. + */ + mtty->rx_lines = 0; + } + return data_avail; +} + +static void uart_rx_work(struct work_struct *work) +{ + struct minitty_data *mtty = container_of(work, typeof(*mtty), rx_work); + + mutex_lock(&mtty->mutex); + minitty_process_rx(mtty); + if (rx_data_available(mtty, true)) + wake_up_interruptible_poll(&mtty->read_wait, POLLIN); + mutex_unlock(&mtty->mutex); +} + +static ssize_t minitty_raw_read(struct minitty_data *mtty, char __user *buf, + size_t count) +{ + int head, tail, len, ret = 0; + + head = smp_load_acquire(&mtty->rx_vetted); + tail = mtty->rx_tail; + do { + len = CIRC_CNT(head, tail, RX_BUF_SIZE); + if (len > count) + len = count; + if (copy_to_user(buf, mtty->rx_buf+tail, len) != 0) + return -EFAULT; + tail = RX_BUF_WRAP(tail + len); + buf += len; + count -= len; + ret += len; + } while (count && len && tail == 0); + smp_store_release(&mtty->rx_tail, tail); + return ret; +} + +static ssize_t minitty_cooked_read(struct minitty_data *mtty, char __user *buf, + size_t count) +{ + int head, tail, i, ret; + bool eol = false; + + head = smp_load_acquire(&mtty->rx_vetted); + tail = mtty->rx_tail; + + /* First, locate the end-of-line marker if any. */ + for (i = tail; i != head && count; i = RX_BUF_WRAP(i + 1), count--) { + unsigned char c = mtty->rx_buf[i]; + if (is_line_termination(mtty, c)) { + eol = true; + break; + } + } + + count = CIRC_CNT(i, tail, RX_BUF_SIZE); + + if (eol) { + /* Include the line delimiter except for EOF */ + if (mtty->rx_buf[i] != EOF_CHAR(mtty)) + count++; + i = RX_BUF_WRAP(i + 1); + } + + ret = minitty_raw_read(mtty, buf, count); + if (ret >= 0 && eol) { + /* we consumed a whole line */ + mtty->rx_lines--; + /* adjust tail in case EOF was skipped */ + smp_store_release(&mtty->rx_tail, i); + } + return ret; +} + +static ssize_t minitty_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct minitty_data *mtty = file->private_data; + char __user *buf0 = buf; + DEFINE_WAIT_FUNC(wait, woken_wake_function); + int minimum, time; + long timeout; + int ret = 0; + + mutex_lock(&mtty->mutex); + + minimum = time = 0; + timeout = MAX_SCHEDULE_TIMEOUT; + if (!L_ICANON(mtty)) { + minimum = MIN_CHAR(mtty); + if (minimum) { + time = (HZ / 10) * TIME_CHAR(mtty); + } else { + timeout = (HZ / 10) * TIME_CHAR(mtty); + minimum = 1; + } + } + + add_wait_queue(&mtty->read_wait, &wait); + + while (count) { + minitty_process_rx(mtty); + + if (!rx_data_available(mtty, false)) { + if (!timeout) + break; + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + mutex_unlock(&mtty->mutex); + timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, + timeout); + mutex_lock(&mtty->mutex); + continue; + } + + if (L_ICANON(mtty)) { + ret = minitty_cooked_read(mtty, buf, count); + if (ret > 0) + buf += ret; + break; + } + + ret = minitty_raw_read(mtty, buf, count); + if (ret < 0) + break; + buf += ret; + count -= ret; + if (buf - buf0 >= minimum) + break; + if (time) + timeout = time; + } + + remove_wait_queue(&mtty->read_wait, &wait); + mutex_unlock(&mtty->mutex); + if (buf - buf0) + ret = buf - buf0; + return ret; +} + +static ssize_t minitty_raw_write(struct minitty_data *mtty, const char __user *buf, + size_t count) +{ + struct circ_buf *circ = &mtty->state.xmit; + int head, tail, len, ret = 0; + + tail = smp_load_acquire(&circ->tail); + head = circ->head; + do { + len = CIRC_SPACE_TO_END(head, tail, UART_XMIT_SIZE); + if (len > count) + len = count; + if (copy_from_user(circ->buf + head, buf, len) != 0) + return -EFAULT; + head = (head + len) & (UART_XMIT_SIZE - 1); + buf += len; + count -= len; + ret += len; + } while (count && len && head == 0); + smp_store_release(&circ->head, head); + + uart_start_tx(mtty); + return ret; +} + +static ssize_t minitty_cooked_write(struct minitty_data *mtty, const char __user *buf, + size_t count) +{ + const char __user *buf0 = buf; + + while (count--) { + unsigned char c; + if (get_user(c, buf) != 0) + return -EFAULT; + if (!tx_cooked_char(mtty, c)) + break; + buf++; + } + mtty->canon_start_pos = mtty->column; + + uart_start_tx(mtty); + return buf - buf0; +} + +static ssize_t minitty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct minitty_data *mtty = file->private_data; + const char __user *buf0 = buf; + DEFINE_WAIT_FUNC(wait, woken_wake_function); + int ret; + + mutex_lock(&mtty->mutex); + add_wait_queue(&mtty->write_wait, &wait); + + while (1) { + /* give priority to RX echo and signals */ + minitty_process_rx(mtty); + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + if (O_OPOST(mtty)) + ret = minitty_cooked_write(mtty, buf, count); + else + ret = minitty_raw_write(mtty, buf, count); + if (ret < 0) + break; + buf += ret; + count -= ret; + if (!count) + break; + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + mutex_unlock(&mtty->mutex); + wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); + mutex_lock(&mtty->mutex); + } + + remove_wait_queue(&mtty->write_wait, &wait); + mutex_unlock(&mtty->mutex); + return (buf - buf0) ? buf - buf0 : ret; +} + +static unsigned int minitty_poll(struct file *file, poll_table *wait) +{ + struct minitty_data *mtty = file->private_data; + struct uart_port *port = mtty->state.uart_port; + unsigned int mask = 0; + + mutex_lock(&mtty->mutex); + + poll_wait(file, &mtty->read_wait, wait); + poll_wait(file, &mtty->write_wait, wait); + + if (rx_data_available(mtty, true)) { + mask |= POLLIN | POLLRDNORM; + } else { + minitty_process_rx(mtty); + if (rx_data_available(mtty, true)) + mask |= POLLIN | POLLRDNORM; + } + + if (!port->hw_stopped) { + struct circ_buf *circ = &mtty->state.xmit; + int tail = smp_load_acquire(&circ->tail); + int head = circ->head; + int count = CIRC_CNT(head, tail, UART_XMIT_SIZE); + if (count < WAKEUP_CHARS) + mask |= POLLOUT | POLLWRNORM; + } + + mutex_unlock(&mtty->mutex); + + return mask; +} + +static int uart_port_startup(struct minitty_data *mtty) +{ + struct uart_state *state = &mtty->state; + struct uart_port *port = state->uart_port; + unsigned long page; + int ret; + + /* Make sure the device is in D0 state. */ + uart_change_pm(state, UART_PM_STATE_ON); + + /* Initialise and allocate the transmit buffer. */ + page = get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + state->xmit.buf = (unsigned char *) page; + uart_circ_clear(&state->xmit); + + /* Initialise and allocate the receive buffer. */ + page = get_zeroed_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto err_free_tx; + } + mtty->rx_buf = (unsigned char *) page; + mtty->rx_head = mtty->rx_tail = mtty->rx_vetted = mtty->rx_lines = 0; + mtty->rx_overflow = false; + + ret = port->ops->startup(port); + if (ret) + goto err_free_rx; + + if (uart_console(port) && port->cons->cflag) { + mtty->termios.c_cflag = port->cons->cflag; + port->cons->cflag = 0; + } + + /* Initialise the hardware port settings. */ + uart_change_speed(mtty, NULL); + + /* + * Setup the RTS and DTR signals once the + * port is open and ready to respond. + */ + uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); + + return 0; + +err_free_rx: + free_page((unsigned long)mtty->rx_buf); + mtty->rx_buf = NULL; +err_free_tx: + free_page((unsigned long)state->xmit.buf); + state->xmit.buf = NULL; + return ret; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void uart_port_shutdown(struct minitty_data *mtty) +{ + struct uart_state *state = &mtty->state; + struct uart_port *port = state->uart_port; + + spin_lock_irq(&port->lock); + port->ops->stop_rx(port); + spin_unlock_irq(&port->lock); + + if (uart_console(port)) + port->cons->cflag = mtty->termios.c_cflag; + + /* Turn off DTR and RTS early. */ + if (C_HUPCL(mtty)) + uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); + + /* Free the IRQ and disable the port. */ + port->ops->shutdown(port); + synchronize_irq(port->irq); + + /* Free the transmit buffer page. */ + free_page((unsigned long)state->xmit.buf); + state->xmit.buf = NULL; + + /* Free the receive buffer page. */ + free_page((unsigned long)mtty->rx_buf); + mtty->rx_buf = NULL; +} + +static int minitty_open(struct inode *inode, struct file *file) +{ + struct minitty_data *mtty = NULL; + dev_t devt = inode->i_rdev; + int ret = 0; + + if (devt == MKDEV(TTYAUX_MAJOR, 1)) { + struct console *co; + struct uart_driver *drv; + console_lock(); + for_each_console(co) { + if (co->device != uart_console_device) + continue; + drv = co->data; + mtty = container_of(drv->state, typeof(*mtty), state); + mtty += co->index; + break; + } + console_unlock(); + if (!mtty) + return -ENODEV; + } else { + mtty = container_of(inode->i_cdev, typeof(*mtty), cdev); + } + + nonseekable_open(inode, file); + + file->private_data = mtty; + + mutex_lock(&mtty->mutex); + if (!mtty->usecount++) { + ret = uart_port_startup(mtty); + if (ret) + mtty->usecount--; + } + mutex_unlock(&mtty->mutex); + return ret; +} + +static int minitty_release(struct inode *inode, struct file *file) +{ + struct minitty_data *mtty = file->private_data; + struct uart_state *state = &mtty->state; + struct uart_port *port = state->uart_port; + + mutex_lock(&mtty->mutex); + mtty->usecount--; + if (!mtty->usecount) { + uart_flush_tx_buffer(mtty); + uart_port_shutdown(mtty); + if (!uart_console(port)) + uart_change_pm(state, UART_PM_STATE_OFF); + } + mutex_unlock(&mtty->mutex); + return 0; +} + +static const struct file_operations minitty_fops = { + .llseek = no_llseek, + .read = minitty_read, + .write = minitty_write, + .poll = minitty_poll, + .unlocked_ioctl = minitty_ioctl, + .open = minitty_open, + .release = minitty_release, +}; + +struct class *minitty_class; + +static int +uart_configure_port(struct uart_driver *drv, struct uart_state *state, + struct uart_port *port) +{ + unsigned int flags; + + /* + * If there isn't a port here, don't do anything further. + */ + if (!port->iobase && !port->mapbase && !port->membase) + return -ENXIO; + + /* + * Now do the auto configuration stuff. Note that config_port + * is expected to claim the resources and map the port for us. + */ + flags = 0; + if (port->flags & UPF_BOOT_AUTOCONF) { + if (!(port->flags & UPF_FIXED_TYPE)) { + port->type = PORT_UNKNOWN; + flags |= UART_CONFIG_TYPE; + } + port->ops->config_port(port, flags); + } + + if (port->type != PORT_UNKNOWN) { + unsigned long flags; + + pr_info("%s%d %s\n", drv->dev_name, port->line, + port->ops->type ? port->ops->type(port) : ""); + + /* Power up port for set_mctrl() */ + uart_change_pm(state, UART_PM_STATE_ON); + + /* + * Ensure that the modem control lines are de-activated. + * keep the DTR setting that is set in uart_set_options() + * We probably don't need a spinlock around this, but + */ + spin_lock_irqsave(&port->lock, flags); + port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR); + spin_unlock_irqrestore(&port->lock, flags); + + /* + * If this driver supports console, and it hasn't been + * successfully registered yet, try to re-register it. + * It may be that the port was not available. + */ + if (port->cons && !(port->cons->flags & CON_ENABLED)) + register_console(port->cons); + + /* + * Power down all ports by default, except the + * console if we have one. + */ + if (!uart_console(port)) + uart_change_pm(state, UART_PM_STATE_OFF); + + return 0; + } + + return -EINVAL; +} + +/** + * uart_add_one_port - attach a driver-defined port structure + * @drv: pointer to the uart low level driver structure for this port + * @port: uart port structure to use for this port. + */ +int uart_add_one_port(struct uart_driver *drv, struct uart_port *port) +{ + unsigned int index = port->line; + dev_t devt = MKDEV(drv->major, drv->minor) + index; + struct minitty_data *mtty; + struct uart_state *state; + int ret; + + mtty = container_of(drv->state, typeof(*mtty), state) + index; + state = &mtty->state; + + state->uart_port = port; + state->pm_state = UART_PM_STATE_UNDEFINED; + port->state = state; + port->cons = drv->cons; + port->minor = drv->minor + index; + uart_port_lock_init(port); + + /* our default termios */ + mtty->termios.c_iflag = ICRNL; + mtty->termios.c_oflag = OPOST | ONLCR; + mtty->termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + mtty->termios.c_lflag = ICANON | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL; + mtty->termios.c_ispeed = 9600; + mtty->termios.c_ospeed = 9600; + memcpy(mtty->termios.c_cc, INIT_C_CC, sizeof(cc_t)*NCCS); + + mutex_init(&mtty->mutex); + init_waitqueue_head(&mtty->write_wait); + init_waitqueue_head(&mtty->read_wait); + INIT_WORK(&mtty->rx_work, uart_rx_work); + + if (port->cons && port->dev) + of_console_check(port->dev->of_node, port->cons->name, index); + + ret = uart_configure_port(drv, state, port); + /* + * We don't support setserial so no point registering a nonexistent + * device . Silently ignore this port if not present. + */ + if (ret) { + ret = 0; + goto out; + } + + state->port.console = uart_console(port); + + cdev_init(&mtty->cdev, &minitty_fops); + mtty->cdev.owner = drv->owner; + ret = cdev_add(&mtty->cdev, devt, 1); + if (ret) + goto out; + mtty->dev = device_create(minitty_class, port->dev, devt, mtty, + "%s%d", drv->dev_name, index); + if (IS_ERR(mtty->dev)) { + ret = PTR_ERR(mtty->dev); + goto err_cdev_del; + } + + return 0; + +err_cdev_del: + cdev_del(&mtty->cdev); +out: + return ret; +} +EXPORT_SYMBOL(uart_add_one_port); + +/** + * uart_remove_one_port - detach a driver defined port structure + * @drv: pointer to the uart low level driver structure for this port + * @port: uart port structure for this port + * + * This unhooks the specified port structure from the core driver. + * No further calls will be made to the low-level code for this port. + */ +int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port) +{ + unsigned int index = port->line; + dev_t devt = MKDEV(drv->major, drv->minor) + index; + struct minitty_data *mtty; + struct uart_state *state; + + mtty = container_of(drv->state, typeof(*mtty), state) + index; + state = &mtty->state; + BUG_ON(state != port->state); + + device_destroy(minitty_class, devt); + cdev_del(&mtty->cdev); + + if (uart_console(port)) + unregister_console(port->cons); + + if (port->type != PORT_UNKNOWN && port->ops->release_port) + port->ops->release_port(port); + port->type = PORT_UNKNOWN; + state->uart_port = NULL; + + return 0; +} +EXPORT_SYMBOL(uart_remove_one_port); + +/** + * uart_register_driver - register a driver with the uart core layer + * @drv: low level driver structure + * + * Register a uart driver. The per-port structures should be + * registered using uart_add_one_port after this call has succeeded. + */ +int uart_register_driver(struct uart_driver *drv) +{ + struct minitty_data *mtty; + int ret; + + BUG_ON(drv->state); + + mtty = kzalloc(sizeof(*mtty) * drv->nr, GFP_KERNEL); + if (!mtty) + return -ENOMEM; + + if (!drv->major) { + dev_t devt; + ret = alloc_chrdev_region(&devt, drv->minor, drv->nr, drv->driver_name); + drv->major = MAJOR(devt); + drv->minor = MINOR(devt); + } else { + dev_t devt = MKDEV(drv->major, drv->minor); + ret = register_chrdev_region(devt, drv->nr, drv->driver_name); + } + if (ret < 0) + goto err; + + drv->state = &mtty->state; + return 0; + +err: + kfree(mtty); + return ret; +} +EXPORT_SYMBOL(uart_register_driver); + +/** + * uart_unregister_driver - remove a driver from the uart core layer + * @drv: low level driver structure + * + * Remove all references to a driver from the core driver. The low + * level driver must have removed all its ports via the + * uart_remove_one_port() if it registered them with uart_add_one_port(). + */ +void uart_unregister_driver(struct uart_driver *drv) +{ + dev_t devt = MKDEV(drv->major, drv->minor); + struct minitty_data *mtty; + + unregister_chrdev_region(devt, drv->nr); + mtty = container_of(drv->state, typeof(*mtty), state); + drv->state = NULL; + kfree(mtty); +} +EXPORT_SYMBOL(uart_unregister_driver); + +struct tty_struct *tty_port_tty_get(struct tty_port *port) +{ + return NULL; +} +EXPORT_SYMBOL(tty_port_tty_get); + +void do_SAK(struct tty_struct *tty) +{ +} +EXPORT_SYMBOL(do_SAK); + +struct tty_driver *uart_console_device(struct console *co, int *index) +{ + return NULL; +} + +static struct cdev console_cdev; + +static char *minitty_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + if (dev->devt == MKDEV(TTYAUX_MAJOR, 0) || + dev->devt == MKDEV(TTYAUX_MAJOR, 2)) + *mode = 0666; + return NULL; +} + +static int __init minitty_class_init(void) +{ + minitty_class = class_create(THIS_MODULE, "tty"); + if (IS_ERR(minitty_class)) + return PTR_ERR(minitty_class); + minitty_class->devnode = minitty_devnode; + return 0; +} +postcore_initcall(minitty_class_init); + +int __init minitty_init(void) +{ + dev_t devt = MKDEV(TTYAUX_MAJOR, 1); + cdev_init(&console_cdev, &minitty_fops); + if (cdev_add(&console_cdev, devt, 1) || + register_chrdev_region(devt, 1, "/dev/console") < 0) + panic("Couldn't register /dev/console driver\n"); + device_create(minitty_class, NULL, devt, NULL, "console"); + return 0; +} +device_initcall(minitty_init); diff --git a/include/linux/tty_flip.h b/include/linux/tty_flip.h index c28dd523f9..1d3dc2c237 100644 --- a/include/linux/tty_flip.h +++ b/include/linux/tty_flip.h @@ -13,6 +13,8 @@ extern int tty_prepare_flip_string(struct tty_port *port, extern void tty_flip_buffer_push(struct tty_port *port); void tty_schedule_flip(struct tty_port *port); +#ifndef CONFIG_MINITTY_SERIAL + static inline int tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag) { @@ -35,6 +37,13 @@ static inline int tty_insert_flip_string(struct tty_port *port, return tty_insert_flip_string_fixed_flag(port, chars, TTY_NORMAL, size); } +#else +extern int tty_insert_flip_char(struct tty_port *port, unsigned char ch, + char flag); +extern int tty_insert_flip_string(struct tty_port *port, + const unsigned char *chars, size_t size); +#endif + extern void tty_buffer_lock_exclusive(struct tty_port *port); extern void tty_buffer_unlock_exclusive(struct tty_port *port);