Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 7 additions & 50 deletions serial_setserial_linux.go
Original file line number Diff line number Diff line change
@@ -1,76 +1,33 @@
//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)
}
return &ser, nil
}

// 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)
}
Expand Down
12 changes: 9 additions & 3 deletions serial_setserial_linux_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build linux
// +build linux

package serial

Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions serial_setserial_stub.go
Original file line number Diff line number Diff line change
@@ -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")
}
28 changes: 28 additions & 0 deletions serial_setserial_unix.go
Original file line number Diff line number Diff line change
@@ -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
}