]> git.ozlabs.org Git - ppp.git/commitdiff
pppd: Fix usage of BOTHER ioctl API on Linux (#314)
authorpali <7141871+pali@users.noreply.github.com>
Mon, 27 Sep 2021 07:10:31 +0000 (09:10 +0200)
committerGitHub <noreply@github.com>
Mon, 27 Sep 2021 07:10:31 +0000 (17:10 +1000)
Linux architectures have different content of struct termios2 and also
different value of BOTHER macro. So do not declare any struct termios2 nor
BOTHER macro. Current definitions in ppp were applicable only for x86.

Correct definitions for current architecture are only in <asm/termbits.h>
and <asm/ioctls.h> header files. But Linux header file <asm/termbits.h> is
in conflict with glibc header file <termios.h> and only one can be included
in one source unit. Moreover both header files contains struct termios but
with different content. So it is not possible to use glibc tc* functions
with <asm/termbits.h> definitions.

For this reason provide a new include header file "termios_linux.h" which
provides custom implementation of all glibc's termios.h functions via Linux
ioctl() interface with definitions from Linux <asm/termbits.h> header file.

Thus this "termios_linux.h" is replacement for <termios.h> with additional
support for BOTHER Linux termios API.

Same "termios_linux.h" is going to be used by U-Boot's kwboot utility for
the same reason to use arbitrary baudrate value via BOTHER ioctl API.

Hopefully one day glibc will provide some API functions for functionality
provided currently by BOTHER Linux API.

Signed-off-by: Pali Rohár <pali@kernel.org>
pppd/Makefile.am
pppd/sys-linux.c
pppd/termios_linux.h [new file with mode: 0644]

index 22445072f78b8e3add93bd7aefb8dd6ed6b47e4e..03c6fd448171eb9c80c9dccc0a79d71de55b963a 100644 (file)
@@ -72,7 +72,7 @@ pppd_LDFLAGS =
 pppd_LIBS =
 
 if LINUX
-pppd_SOURCES += sys-linux.c
+pppd_SOURCES += sys-linux.c termios_linux.h
 pppd_LIBS += $(CRYPT_LIBS) $(UTIL_LIBS)
 endif
 
index e1bb8108ea0964d81d0c4b64ead2132e97aa5889..513fc3dc009ef3010f434328121e8dde4b918137 100644 (file)
@@ -96,7 +96,6 @@
 #include <signal.h>
 #include <fcntl.h>
 #include <ctype.h>
-#include <termios.h>
 #include <unistd.h>
 
 /* This is in netdevice.h. However, this compile will fail miserably if
 
 #if !defined(__GLIBC__) || __GLIBC__ >= 2
 #include <asm/types.h>         /* glibc 2 conflicts with linux/types.h */
-#include <asm/ioctls.h>
 #include <net/if.h>
 #include <net/if_arp.h>
 #include <net/route.h>
 #include <sys/locks.h>
 #endif
 
-#ifndef BOTHER
-#define BOTHER 0010000
-#endif
-struct termios2 {
-       unsigned int c_iflag;
-       unsigned int c_oflag;
-       unsigned int c_cflag;
-       unsigned int c_lflag;
-       unsigned char c_line;
-       unsigned char c_cc[19];
-       unsigned int c_ispeed;
-       unsigned int c_ospeed;
-};
+/*
+ * Instead of system header file <termios.h> use local "termios_linux.h" header
+ * file as it provides additional support for arbitrary baud rates via BOTHER.
+ */
+#include "termios_linux.h"
 
 #ifdef INET6
 #ifndef _LINUX_IN6_H
@@ -1053,40 +1043,32 @@ void set_up_tty(int tty_fd, int local)
            cfsetospeed (&tios, speed);
            cfsetispeed (&tios, speed);
            speed = cfgetospeed(&tios);
+           baud_rate = baud_rate_of(speed);
+       } else {
+#ifdef BOTHER
+           tios.c_cflag &= ~CBAUD;
+           tios.c_cflag |= BOTHER;
+           tios.c_ospeed = inspeed;
+#ifdef IBSHIFT
+           /* B0 sets input baudrate to the output baudrate */
+           tios.c_cflag &= ~(CBAUD << IBSHIFT);
+           tios.c_cflag |= B0 << IBSHIFT;
+           tios.c_ispeed = inspeed;
+#endif
+           baud_rate = inspeed;
+#else
+           baud_rate = 0;
+#endif
        }
-       baud_rate = baud_rate_of(speed);
     }
     else {
        speed = cfgetospeed(&tios);
        baud_rate = baud_rate_of(speed);
-    }
-
-    while (tcsetattr(tty_fd, TCSAFLUSH, &tios) < 0 && !ok_error(errno))
-       if (errno != EINTR)
-           fatal("tcsetattr: %m (line %d)", __LINE__);
-    restore_term = 1;
-
-/* Most Linux architectures and drivers support arbitrary baud rate values via BOTHER */
-#ifdef TCGETS2
-    if (!baud_rate) {
-       struct termios2 tios2;
-       if (ioctl(tty_fd, TCGETS2, &tios2) == 0) {
-           if (inspeed) {
-               tios2.c_cflag &= ~CBAUD;
-               tios2.c_cflag |= BOTHER;
-               tios2.c_ispeed = inspeed;
-               tios2.c_ospeed = inspeed;
-#ifdef TCSETS2
-               if (ioctl(tty_fd, TCSETS2, &tios2) == 0)
-                   baud_rate = inspeed;
+#ifdef BOTHER
+       if (!baud_rate)
+           baud_rate = tios.c_ospeed;
 #endif
-           } else {
-               if ((tios2.c_cflag & CBAUD) == BOTHER && tios2.c_ospeed)
-                   baud_rate = tios2.c_ospeed;
-           }
-       }
     }
-#endif
 
 /*
  * We can't proceed if the serial port baud rate is unknown,
@@ -1098,6 +1080,11 @@ void set_up_tty(int tty_fd, int local)
        else
            fatal("Baud rate for %s is 0; need explicit baud rate", devnam);
     }
+
+    while (tcsetattr(tty_fd, TCSAFLUSH, &tios) < 0 && !ok_error(errno))
+       if (errno != EINTR)
+           fatal("tcsetattr: %m (line %d)", __LINE__);
+    restore_term = 1;
 }
 
 /********************************************************************
diff --git a/pppd/termios_linux.h b/pppd/termios_linux.h
new file mode 100644 (file)
index 0000000..9c79d16
--- /dev/null
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: GPL-2.0+ OR BSD-4-Clause OR BSD-3-Clause OR BSD-2-Clause */
+/*
+ * termios fuctions to support arbitrary baudrates (on Linux)
+ *
+ * Copyright (c) 2021 Pali Rohár <pali@kernel.org>
+ * Copyright (c) 2021 Marek Behún <kabel@kernel.org>
+ */
+
+#ifndef _TERMIOS_LINUX_H_
+#define _TERMIOS_LINUX_H_
+
+/*
+ * We need to use raw TCGETS2/TCSETS2 or TCGETS/TCSETS ioctls with the BOTHER
+ * flag in struct termios2/termios, defined in Linux headers <asm/ioctls.h>
+ * and <asm/termbits.h>. Since these headers conflict with glibc's header file
+ * <termios.h>, it is not possible to use libc's termios functions and we need
+ * to reimplement them via ioctl() calls.
+ *
+ * An arbitrary baudrate is supported when the macro BOTHER is defined. The
+ * baudrate value itself is then stored into the c_ospeed and c_ispeed members.
+ * If ioctls TCGETS2/TCSETS2 are defined and supported then these fields are
+ * present in struct termios2, otherwise these fields are present in struct
+ * termios.
+ *
+ * Note that the Bnnn constants from <termios.h> need not be compatible with
+ * Bnnn constants from <asm/termbits.h>.
+ */
+
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <asm/ioctls.h>
+#include <asm/termbits.h>
+
+#if defined(BOTHER) && defined(TCGETS2)
+#define termios termios2
+#endif
+
+static inline int tcgetattr(int fd, struct termios *t)
+{
+#if defined(BOTHER) && defined(TCGETS2)
+       return ioctl(fd, TCGETS2, t);
+#else
+       return ioctl(fd, TCGETS, t);
+#endif
+}
+
+static inline int tcsetattr(int fd, int a, const struct termios *t)
+{
+       int cmd;
+
+       switch (a) {
+#if defined(BOTHER) && defined(TCGETS2)
+       case TCSANOW:
+               cmd = TCSETS2;
+               break;
+       case TCSADRAIN:
+               cmd = TCSETSW2;
+               break;
+       case TCSAFLUSH:
+               cmd = TCSETSF2;
+               break;
+#else
+       case TCSANOW:
+               cmd = TCSETS;
+               break;
+       case TCSADRAIN:
+               cmd = TCSETSW;
+               break;
+       case TCSAFLUSH:
+               cmd = TCSETSF;
+               break;
+#endif
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+
+       return ioctl(fd, cmd, t);
+}
+
+static inline int tcdrain(int fd)
+{
+       return ioctl(fd, TCSBRK, 1);
+}
+
+static inline int tcflush(int fd, int q)
+{
+       return ioctl(fd, TCFLSH, q);
+}
+
+static inline int tcsendbreak(int fd, int d)
+{
+#ifdef TCSBRKP
+       return ioctl(fd, TCSBRKP, d);
+#else
+       return ioctl(fd, TCSBRK, 0);
+#endif
+}
+
+static inline int tcflow(int fd, int a)
+{
+       return ioctl(fd, TCXONC, a);
+}
+
+static inline pid_t tcgetsid(int fd)
+{
+       pid_t sid;
+
+       if (ioctl(fd, TIOCGSID, &sid) < 0)
+               return (pid_t)-1;
+
+       return sid;
+}
+
+static inline speed_t cfgetospeed(const struct termios *t)
+{
+       return t->c_cflag & CBAUD;
+}
+
+static inline int cfsetospeed(struct termios *t, speed_t s)
+{
+       if (s & ~CBAUD) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       t->c_cflag &= ~CBAUD;
+       t->c_cflag |= s;
+
+       return 0;
+}
+
+#ifdef IBSHIFT
+static inline speed_t cfgetispeed(const struct termios *t)
+{
+       speed_t s = (t->c_cflag >> IBSHIFT) & CBAUD;
+
+       if (s == B0)
+               return cfgetospeed(t);
+       else
+               return s;
+}
+
+static inline int cfsetispeed(struct termios *t, speed_t s)
+{
+       if (s == 0)
+               s = B0;
+
+       if (s & ~CBAUD) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       t->c_cflag &= ~(CBAUD << IBSHIFT);
+       t->c_cflag |= s << IBSHIFT;
+
+       return 0;
+}
+#else /* !IBSHIFT */
+static inline speed_t cfgetispeed(const struct termios *t)
+{
+       return cfgetospeed(t);
+}
+
+static inline int cfsetispeed(struct termios *t, speed_t s)
+{
+       return cfsetospeed(t, s);
+}
+#endif /* !IBSHIFT */
+
+static inline int cfsetspeed(struct termios *t, speed_t s)
+{
+       if (cfsetospeed(t, s))
+               return -1;
+#ifdef IBSHIFT
+       if (cfsetispeed(t, s))
+               return -1;
+#endif
+
+       return 0;
+}
+
+static void cfmakeraw(struct termios *t)
+{
+       t->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR |
+                       ICRNL | IXON);
+       t->c_oflag &= ~OPOST;
+       t->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+       t->c_cflag &= ~(CSIZE | PARENB);
+       t->c_cflag |= CS8;
+}
+
+#endif /* _TERMIOS_LINUX_H_ */