diff --git a/serial_setserial_linux.go b/serial_setserial_linux.go index 4571b53..c142dc1 100644 --- a/serial_setserial_linux.go +++ b/serial_setserial_linux.go @@ -1,53 +1,19 @@ //go:build linux -// +build linux package serial import ( "fmt" - "os" "syscall" "unsafe" "golang.org/x/sys/unix" ) -// CSerialStruct is a C-interop struct for linux/serial.h: struct serial_struct -// Field order and types must exactly match the C definition for binary compatibility. -// See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/serial.h#L135 -type CSerialStruct struct { - Type int32 - Line int32 - Port uint32 - IRQ int32 - Flags int32 - XmitFifoSize int32 - CustomDivisor int32 - BaudBase int32 - CloseDelay uint16 - IOType byte - ReservedChar byte - Hub6 int32 - ClosingWait uint16 - ClosingWait2 uint16 - IOMemBase uintptr - IOMemRegShift uint16 - PortHigh uint32 - // IOMapBase: In C, this is 'unsigned long'. Use uint64 for 64-bit systems. - // For 32-bit systems, you may need to use uint32 for binary compatibility. - IOMapBase uint64 -} - // GetSerialStruct opens the device and retrieves the CSerialStruct using TIOCGSERIAL ioctl. -func GetSerialStruct(device string) (*CSerialStruct, error) { - f, err := os.OpenFile(device, os.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666) - if err != nil { - return nil, fmt.Errorf("failed to open device: %w", err) - } - defer f.Close() - - var ser CSerialStruct - _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.TIOCGSERIAL, uintptr(unsafe.Pointer(&ser))) +func (port *unixPort) GetSerialStruct() (*LinuxCSerialStruct, error) { + var ser LinuxCSerialStruct + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(port.handle), unix.TIOCGSERIAL, uintptr(unsafe.Pointer(&ser))) if errno != 0 { return nil, fmt.Errorf("ioctl TIOCGSERIAL failed: %v", errno) } @@ -55,22 +21,13 @@ func GetSerialStruct(device string) (*CSerialStruct, error) { } // SetSerialPortMode sets the port mode using ioctl TIOCSSERIAL -func SetSerialPortMode(device string, portMode uint32) error { - f, err := os.OpenFile(device, os.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666) +func (port *unixPort) SetSerialPortMode(portMode uint32) error { + ser, err := port.GetSerialStruct() if err != nil { - return fmt.Errorf("failed to open device: %w", err) + return err } - defer f.Close() - - var ser CSerialStruct - _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.TIOCGSERIAL, uintptr(unsafe.Pointer(&ser))) - if errno != 0 { - return fmt.Errorf("ioctl TIOCGSERIAL failed: %v", errno) - } - ser.Port = portMode - - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.TIOCSSERIAL, uintptr(unsafe.Pointer(&ser))) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(port.handle), unix.TIOCSSERIAL, uintptr(unsafe.Pointer(&ser))) if errno != 0 { return fmt.Errorf("ioctl TIOCSSERIAL failed: %v", errno) } diff --git a/serial_setserial_linux_test.go b/serial_setserial_linux_test.go index aafb565..9b8af3d 100644 --- a/serial_setserial_linux_test.go +++ b/serial_setserial_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux package serial @@ -28,7 +27,11 @@ func TestGetSerialStructOnFakeTty(t *testing.T) { cmd := startSocatAndWaitForSetserialTest(t, ctx) go cmd.Wait() - ser, err := GetSerialStruct("/tmp/faketty_setserial") + port, err := nativeOpen("/tmp/faketty", &Mode{}) + require.NoError(t, err) + defer port.Close() + ser, err := port.GetSerialStruct() + // Note: socat's virtual TTY may not fully support TIOCGSERIAL. // If not supported, skip the test (environment dependent). if err != nil { @@ -43,7 +46,10 @@ func TestSetSerialPortModeOnFakeTty(t *testing.T) { cmd := startSocatAndWaitForSetserialTest(t, ctx) go cmd.Wait() - err := SetSerialPortMode("/tmp/faketty_setserial", 0) + port, err := nativeOpen("/tmp/faketty", &Mode{}) + require.NoError(t, err) + defer port.Close() + err = port.SetSerialPortMode(0) // Note: socat's virtual TTY may not fully support TIOCSSERIAL. // If not supported, skip the test (environment dependent). if err != nil { diff --git a/serial_setserial_stub.go b/serial_setserial_stub.go new file mode 100644 index 0000000..ebf2173 --- /dev/null +++ b/serial_setserial_stub.go @@ -0,0 +1,12 @@ +//go:build darwin || freebsd || openbsd + +package serial + +import "fmt" + +func (port *unixPort) SetSerialPortMode(portMode uint32) error { + return fmt.Errorf("SetSerialPortMode is not supported on this OS") +} +func (port *unixPort) GetSerialStruct() (*LinuxCSerialStruct, error) { + return nil, fmt.Errorf("GetSerialStruct is not supported on this OS") +} diff --git a/serial_setserial_unix.go b/serial_setserial_unix.go new file mode 100644 index 0000000..7fe864d --- /dev/null +++ b/serial_setserial_unix.go @@ -0,0 +1,28 @@ +//go:build linux || darwin || freebsd || openbsd + +package serial + +// LinuxCSerialStruct is a C-interop struct for linux/serial.h: struct serial_struct +// Field order and types must exactly match the C definition for binary compatibility. +// See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/serial.h#L135 +type LinuxCSerialStruct struct { + Type int32 + Line int32 + Port uint32 + IRQ int32 + Flags int32 + XmitFifoSize int32 + CustomDivisor int32 + BaudBase int32 + CloseDelay uint16 + IOType byte + ReservedChar byte + Hub6 int32 + ClosingWait uint16 + ClosingWait2 uint16 + IOMemBase uintptr + IOMemRegShift uint16 + PortHigh uint32 + // IOMapBase: In C, this is 'unsigned long'. + IOMapBase uint +}