diff --git a/Documentation/driver-api/serial/driver.rst b/Documentation/driver-api/serial/driver.rst index 1e7ab4142d49..ee1679858aa2 100644 --- a/Documentation/driver-api/serial/driver.rst +++ b/Documentation/driver-api/serial/driver.rst @@ -261,6 +261,8 @@ hardware. - parity enable PARODD - odd parity (when PARENB is in force) + ADDRB + - address bit (changed through .rs485_config()). CREAD - enable reception of characters (if not set, still receive characters from the port, but diff --git a/Documentation/driver-api/serial/serial-rs485.rst b/Documentation/driver-api/serial/serial-rs485.rst index 00b5d333acba..6ebad75c74ed 100644 --- a/Documentation/driver-api/serial/serial-rs485.rst +++ b/Documentation/driver-api/serial/serial-rs485.rst @@ -99,7 +99,31 @@ RS485 Serial Communications /* Error handling. See errno. */ } -5. References +5. Multipoint Addressing +======================== + + The Linux kernel provides addressing mode for multipoint RS-485 serial + communications line. The addressing mode is enabled with SER_RS485_ADDRB + flag in serial_rs485. Struct serial_rs485 has two additional flags and + fields for enabling receive and destination addresses. + + Address mode flags: + - SER_RS485_ADDRB: Enabled addressing mode (sets also ADDRB in termios). + - SER_RS485_ADDR_RECV: Receive (filter) address enabled. + - SER_RS485_ADDR_DEST: Set destination address. + + Address fields (enabled with corresponding SER_RS485_ADDR_* flag): + - addr_recv: Receive address. + - addr_dest: Destination address. + + Once a receive address is set, the communication can occur only with the + particular device and other peers are filtered out. It is left up to the + receiver side to enforce the filtering. Receive address will be cleared + if SER_RS485_ADDR_RECV is not set. + + Note: not all devices supporting RS485 support multipoint addressing. + +6. References ============= [1] include/uapi/linux/serial.h diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 2529153c8979..85ef7ef00b82 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1288,6 +1288,17 @@ static int uart_check_rs485_flags(struct uart_port *port, struct serial_rs485 *r if (flags & ~port->rs485_supported->flags) return -EINVAL; + /* Asking for address w/o addressing mode? */ + if (!(rs485->flags & SER_RS485_ADDRB) && + (rs485->flags & (SER_RS485_ADDR_RECV|SER_RS485_ADDR_DEST))) + return -EINVAL; + + /* Address given but not enabled? */ + if (!(rs485->flags & SER_RS485_ADDR_RECV) && rs485->addr_recv) + return -EINVAL; + if (!(rs485->flags & SER_RS485_ADDR_DEST) && rs485->addr_dest) + return -EINVAL; + return 0; } @@ -1343,7 +1354,8 @@ static void uart_sanitize_serial_rs485(struct uart_port *port, struct serial_rs4 rs485->flags &= supported_flags; /* Return clean padding area to userspace */ - memset(rs485->padding, 0, sizeof(rs485->padding)); + memset(rs485->padding0, 0, sizeof(rs485->padding0)); + memset(rs485->padding1, 0, sizeof(rs485->padding1)); } int uart_rs485_config(struct uart_port *port) @@ -3402,5 +3414,13 @@ int uart_get_rs485_mode(struct uart_port *port) } EXPORT_SYMBOL_GPL(uart_get_rs485_mode); +/* Compile-time assertions for serial_rs485 layout */ +static_assert(offsetof(struct serial_rs485, padding) == + (offsetof(struct serial_rs485, delay_rts_after_send) + sizeof(__u32))); +static_assert(offsetof(struct serial_rs485, padding1) == + offsetof(struct serial_rs485, padding[1])); +static_assert((offsetof(struct serial_rs485, padding[4]) + sizeof(__u32)) == + sizeof(struct serial_rs485)); + MODULE_DESCRIPTION("Serial driver core"); MODULE_LICENSE("GPL"); diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index adae687f654b..2a76b330e108 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -319,6 +319,8 @@ unsigned char tty_get_frame_size(unsigned int cflag) bits++; if (cflag & PARENB) bits++; + if (cflag & ADDRB) + bits++; return bits; } @@ -353,6 +355,8 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) old_termios = tty->termios; tty->termios = *new_termios; unset_locked_termios(tty, &old_termios); + /* Reset any ADDRB changes, ADDRB is changed through ->rs485_config() */ + tty->termios.c_cflag ^= (tty->termios.c_cflag ^ old_termios.c_cflag) & ADDRB; if (tty->ops->set_termios) tty->ops->set_termios(tty, &old_termios); diff --git a/include/uapi/asm-generic/termbits-common.h b/include/uapi/asm-generic/termbits-common.h index 4d084fe8def5..4a6a79f28b21 100644 --- a/include/uapi/asm-generic/termbits-common.h +++ b/include/uapi/asm-generic/termbits-common.h @@ -46,6 +46,7 @@ typedef unsigned int speed_t; #define EXTA B19200 #define EXTB B38400 +#define ADDRB 0x20000000 /* address bit */ #define CMSPAR 0x40000000 /* mark or space (stick) parity */ #define CRTSCTS 0x80000000 /* flow control */ diff --git a/include/uapi/linux/serial.h b/include/uapi/linux/serial.h index fa6b16e5fdd8..cea06924b295 100644 --- a/include/uapi/linux/serial.h +++ b/include/uapi/linux/serial.h @@ -126,10 +126,26 @@ struct serial_rs485 { #define SER_RS485_TERMINATE_BUS (1 << 5) /* Enable bus termination (if supported) */ + +/* RS-485 addressing mode */ +#define SER_RS485_ADDRB (1 << 6) /* Enable addressing mode */ +#define SER_RS485_ADDR_RECV (1 << 7) /* Receive address filter */ +#define SER_RS485_ADDR_DEST (1 << 8) /* Destination address */ + __u32 delay_rts_before_send; /* Delay before send (milliseconds) */ __u32 delay_rts_after_send; /* Delay after send (milliseconds) */ - __u32 padding[5]; /* Memory is cheap, new structs - are a royal PITA .. */ + + /* The fields below are defined by flags */ + union { + __u32 padding[5]; /* Memory is cheap, new structs are a pain */ + + struct { + __u8 addr_recv; + __u8 addr_dest; + __u8 padding0[2]; + __u32 padding1[4]; + }; + }; }; /*