//go:build linux package serial import ( "fmt" "golang.org/x/sys/unix" ) func ApplyLineConfig(fd uintptr, config LineConfig) error { normalized, err := NormalizeLineConfig(config) if err != nil { return err } termios, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) if err != nil { return fmt.Errorf("read serial termios: %w", err) } applyTermiosConfig(termios, normalized) if err := unix.IoctlSetTermios(int(fd), unix.TCSETS, termios); err != nil { return fmt.Errorf("apply serial termios: %w", err) } if err := applyModemOutputs(int(fd), normalized); err != nil { return err } return nil } // applyModemOutputs makes the public rts_control/dtr_control settings affect // the physical RS-232 output pins. "auto" deliberately leaves ownership to // the tty driver (for example CRTSCTS), while on/off are applied and verified. func applyModemOutputs(fd int, config LineConfig) error { if err := applyModemOutput(fd, "DTR", config.DTRControl, unix.TIOCM_DTR); err != nil { return err } if err := applyModemOutput(fd, "RTS", config.RTSControl, unix.TIOCM_RTS); err != nil { return err } return nil } func applyModemOutput(fd int, name, control string, mask int) error { if control == "auto" { return nil } request := uint(unix.TIOCMBIC) wantOn := control == "on" if wantOn { request = uint(unix.TIOCMBIS) } if err := unix.IoctlSetPointerInt(fd, request, mask); err != nil { return fmt.Errorf("set serial %s output %s: %w", name, control, err) } bits, err := unix.IoctlGetInt(fd, unix.TIOCMGET) if err != nil { return fmt.Errorf("verify serial %s output %s: %w", name, control, err) } if (bits&mask != 0) != wantOn { return fmt.Errorf("verify serial %s output: requested %s but hardware readback did not match", name, control) } return nil } func ReadLineConfig(fd uintptr, device string) (LineConfig, error) { termios, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) if err != nil { return LineConfig{}, fmt.Errorf("read serial termios: %w", err) } baudRate, ok := baudRateFromConstant(termios.Cflag & unix.CBAUD) if !ok { return LineConfig{}, fmt.Errorf("read serial termios: unsupported baud encoding %#x", termios.Cflag&unix.CBAUD) } dataBits, ok := dataBitsFromConstant(termios.Cflag & unix.CSIZE) if !ok { return LineConfig{}, fmt.Errorf("read serial termios: unsupported data-bit encoding %#x", termios.Cflag&unix.CSIZE) } parity := "none" if termios.Cflag&unix.PARENB != 0 { if termios.Cflag&unix.CMSPAR != 0 { parity = "space" if termios.Cflag&unix.PARODD != 0 { parity = "mark" } } else { parity = "even" if termios.Cflag&unix.PARODD != 0 { parity = "odd" } } } stopBits := 1 stopBitsMode := "1" if termios.Cflag&unix.CSTOPB != 0 { if dataBits == 5 { stopBitsMode = "1.5" } else { stopBits = 2 stopBitsMode = "2" } } flowControl := "none" if termios.Cflag&unix.CRTSCTS != 0 { flowControl = "rtscts" } else if termios.Iflag&(unix.IXON|unix.IXOFF) != 0 { flowControl = "xonxoff" } return LineConfig{ Device: device, BaudRate: baudRate, DataBits: dataBits, Parity: parity, StopBits: stopBits, StopBitsMode: stopBitsMode, FlowControl: flowControl, RTSControl: "auto", DTRControl: "auto", }, nil } func applyTermiosConfig(termios *unix.Termios, config LineConfig) { speed := baudConstant(config.BaudRate) termios.Iflag = unix.IGNPAR termios.Oflag = 0 termios.Lflag = 0 termios.Cflag = unix.CLOCAL | unix.CREAD | speed | dataBitsConstant(config.DataBits) switch config.Parity { case "even": termios.Cflag |= unix.PARENB case "odd": termios.Cflag |= unix.PARENB | unix.PARODD case "mark": termios.Cflag |= unix.PARENB | unix.PARODD | unix.CMSPAR case "space": termios.Cflag |= unix.PARENB | unix.CMSPAR } if config.StopBitsMode == "1.5" || config.StopBitsMode == "2" { termios.Cflag |= unix.CSTOPB } if config.FlowControl == "rtscts" { termios.Cflag |= unix.CRTSCTS } else if config.FlowControl == "xonxoff" { termios.Iflag |= unix.IXON | unix.IXOFF } termios.Cc[unix.VMIN] = 1 termios.Cc[unix.VTIME] = 0 } func baudConstant(baudRate int) uint32 { switch baudRate { case 110: return unix.B110 case 300: return unix.B300 case 600: return unix.B600 case 1200: return unix.B1200 case 1800: return unix.B1800 case 2400: return unix.B2400 case 4800: return unix.B4800 case 9600: return unix.B9600 case 19200: return unix.B19200 case 38400: return unix.B38400 case 57600: return unix.B57600 case 115200: return unix.B115200 case 230400: return unix.B230400 case 460800: return unix.B460800 default: return unix.B9600 } } func dataBitsConstant(dataBits int) uint32 { switch dataBits { case 5: return unix.CS5 case 6: return unix.CS6 case 7: return unix.CS7 default: return unix.CS8 } } func baudRateFromConstant(value uint32) (int, bool) { switch value { case unix.B110: return 110, true case unix.B300: return 300, true case unix.B600: return 600, true case unix.B1200: return 1200, true case unix.B1800: return 1800, true case unix.B2400: return 2400, true case unix.B4800: return 4800, true case unix.B9600: return 9600, true case unix.B19200: return 19200, true case unix.B38400: return 38400, true case unix.B57600: return 57600, true case unix.B115200: return 115200, true case unix.B230400: return 230400, true case unix.B460800: return 460800, true default: return 0, false } } func dataBitsFromConstant(value uint32) (int, bool) { switch value { case unix.CS5: return 5, true case unix.CS6: return 6, true case unix.CS7: return 7, true case unix.CS8: return 8, true default: return 0, false } }