style: fix whitespace/gofumpt/golines in linux/unix build-tagged files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
545fae02de
commit
a16a13f7cd
4 changed files with 63 additions and 477 deletions
|
|
@ -8,465 +8,285 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C ioctl constants from Linux kernel headers (<linux/i2c-dev.h>, <linux/i2c.h>)
|
// I2C ioctl constants from Linux kernel headers (<linux/i2c-dev.h>, <linux/i2c.h>)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
i2cSlave = 0x0703 // Set slave address (fails if in use by driver)
|
i2cSlave = 0x0703 // Set slave address (fails if in use by driver)
|
||||||
|
|
||||||
i2cFuncs = 0x0705 // Query adapter functionality bitmask
|
i2cFuncs = 0x0705 // Query adapter functionality bitmask
|
||||||
|
|
||||||
i2cSmbus = 0x0720 // Perform SMBus transaction
|
i2cSmbus = 0x0720 // Perform SMBus transaction
|
||||||
|
|
||||||
// I2C_FUNC capability bits
|
// I2C_FUNC capability bits
|
||||||
|
i2cFuncSmbusQuick = 0x00010000
|
||||||
i2cFuncSmbusQuick = 0x00010000
|
|
||||||
|
|
||||||
i2cFuncSmbusReadByte = 0x00020000
|
i2cFuncSmbusReadByte = 0x00020000
|
||||||
|
|
||||||
// SMBus transaction types
|
// SMBus transaction types
|
||||||
|
i2cSmbusRead = 0
|
||||||
i2cSmbusRead = 0
|
|
||||||
|
|
||||||
i2cSmbusWrite = 1
|
i2cSmbusWrite = 1
|
||||||
|
|
||||||
// SMBus protocol sizes
|
// SMBus protocol sizes
|
||||||
|
|
||||||
i2cSmbusQuick = 0
|
i2cSmbusQuick = 0
|
||||||
|
i2cSmbusByte = 1
|
||||||
i2cSmbusByte = 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// i2cSmbusData matches the kernel union i2c_smbus_data (34 bytes max).
|
// i2cSmbusData matches the kernel union i2c_smbus_data (34 bytes max).
|
||||||
|
|
||||||
// For quick and byte transactions only the first byte is used (if at all).
|
// For quick and byte transactions only the first byte is used (if at all).
|
||||||
|
|
||||||
type i2cSmbusData [34]byte
|
type i2cSmbusData [34]byte
|
||||||
|
|
||||||
// i2cSmbusArgs matches the kernel struct i2c_smbus_ioctl_data.
|
// i2cSmbusArgs matches the kernel struct i2c_smbus_ioctl_data.
|
||||||
|
|
||||||
type i2cSmbusArgs struct {
|
type i2cSmbusArgs struct {
|
||||||
readWrite uint8
|
readWrite uint8
|
||||||
|
command uint8
|
||||||
command uint8
|
size uint32
|
||||||
|
data *i2cSmbusData
|
||||||
size uint32
|
|
||||||
|
|
||||||
data *i2cSmbusData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// smbusProbe performs a single SMBus probe at the given address.
|
// smbusProbe performs a single SMBus probe at the given address.
|
||||||
|
|
||||||
// Uses SMBus Quick Write (safest) or falls back to SMBus Read Byte for
|
// Uses SMBus Quick Write (safest) or falls back to SMBus Read Byte for
|
||||||
|
|
||||||
// EEPROM address ranges where quick write can corrupt AT24RF08 chips.
|
// EEPROM address ranges where quick write can corrupt AT24RF08 chips.
|
||||||
|
|
||||||
// This matches i2cdetect's MODE_AUTO behavior.
|
// This matches i2cdetect's MODE_AUTO behavior.
|
||||||
|
|
||||||
func smbusProbe(fd int, addr int, hasQuick bool) bool {
|
func smbusProbe(fd int, addr int, hasQuick bool) bool {
|
||||||
|
|
||||||
// EEPROM ranges: use read byte (quick write can corrupt AT24RF08)
|
// EEPROM ranges: use read byte (quick write can corrupt AT24RF08)
|
||||||
|
|
||||||
useReadByte := (addr >= 0x30 && addr <= 0x37) || (addr >= 0x50 && addr <= 0x5F)
|
useReadByte := (addr >= 0x30 && addr <= 0x37) || (addr >= 0x50 && addr <= 0x5F)
|
||||||
|
|
||||||
if !useReadByte && hasQuick {
|
if !useReadByte && hasQuick {
|
||||||
|
|
||||||
// SMBus Quick Write: [START] [ADDR|W] [ACK/NACK] [STOP]
|
// SMBus Quick Write: [START] [ADDR|W] [ACK/NACK] [STOP]
|
||||||
|
|
||||||
// Safest probe — no data transferred
|
// Safest probe — no data transferred
|
||||||
|
|
||||||
args := i2cSmbusArgs{
|
args := i2cSmbusArgs{
|
||||||
|
|
||||||
readWrite: i2cSmbusWrite,
|
readWrite: i2cSmbusWrite,
|
||||||
|
command: 0,
|
||||||
command: 0,
|
size: i2cSmbusQuick,
|
||||||
|
data: nil,
|
||||||
size: i2cSmbusQuick,
|
|
||||||
|
|
||||||
data: nil,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
|
||||||
|
|
||||||
return errno == 0
|
return errno == 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMBus Read Byte: [START] [ADDR|R] [ACK/NACK] [DATA] [STOP]
|
// SMBus Read Byte: [START] [ADDR|R] [ACK/NACK] [DATA] [STOP]
|
||||||
|
|
||||||
var data i2cSmbusData
|
var data i2cSmbusData
|
||||||
|
|
||||||
args := i2cSmbusArgs{
|
args := i2cSmbusArgs{
|
||||||
|
|
||||||
readWrite: i2cSmbusRead,
|
readWrite: i2cSmbusRead,
|
||||||
|
command: 0,
|
||||||
command: 0,
|
size: i2cSmbusByte,
|
||||||
|
data: &data,
|
||||||
size: i2cSmbusByte,
|
|
||||||
|
|
||||||
data: &data,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
|
||||||
|
|
||||||
return errno == 0
|
return errno == 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan probes valid 7-bit addresses on a bus for connected devices.
|
// scan probes valid 7-bit addresses on a bus for connected devices.
|
||||||
|
|
||||||
// Uses the same hybrid probe strategy as i2cdetect's MODE_AUTO:
|
// Uses the same hybrid probe strategy as i2cdetect's MODE_AUTO:
|
||||||
|
|
||||||
// SMBus Quick Write for most addresses, SMBus Read Byte for EEPROM ranges.
|
// SMBus Quick Write for most addresses, SMBus Read Byte for EEPROM ranges.
|
||||||
|
|
||||||
func (t *I2CTool) scan(args map[string]any) *ToolResult {
|
func (t *I2CTool) scan(args map[string]any) *ToolResult {
|
||||||
|
|
||||||
bus, errResult := parseI2CBus(args)
|
bus, errResult := parseI2CBus(args)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
|
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
|
||||||
|
|
||||||
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and i2c-dev module)", devPath, err))
|
return ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and i2c-dev module)", devPath, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer syscall.Close(fd)
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
// Query adapter capabilities to determine available probe methods.
|
// Query adapter capabilities to determine available probe methods.
|
||||||
|
|
||||||
// I2C_FUNCS writes an unsigned long, which is word-sized on Linux.
|
// I2C_FUNCS writes an unsigned long, which is word-sized on Linux.
|
||||||
|
|
||||||
var funcs uintptr
|
var funcs uintptr
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cFuncs, uintptr(unsafe.Pointer(&funcs)))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cFuncs, uintptr(unsafe.Pointer(&funcs)))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to query I2C adapter capabilities on %s: %v", devPath, errno))
|
return ErrorResult(fmt.Sprintf("failed to query I2C adapter capabilities on %s: %v", devPath, errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasQuick := funcs&i2cFuncSmbusQuick != 0
|
hasQuick := funcs&i2cFuncSmbusQuick != 0
|
||||||
|
|
||||||
hasReadByte := funcs&i2cFuncSmbusReadByte != 0
|
hasReadByte := funcs&i2cFuncSmbusReadByte != 0
|
||||||
|
|
||||||
if !hasQuick && !hasReadByte {
|
if !hasQuick && !hasReadByte {
|
||||||
|
|
||||||
return ErrorResult(
|
return ErrorResult(
|
||||||
|
fmt.Sprintf(
|
||||||
fmt.Sprintf("I2C adapter %s supports neither SMBus Quick nor Read Byte — cannot probe safely", devPath),
|
"I2C adapter %s supports neither SMBus Quick nor Read Byte — cannot probe safely",
|
||||||
|
devPath,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type deviceEntry struct {
|
type deviceEntry struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var found []deviceEntry
|
var found []deviceEntry
|
||||||
|
|
||||||
// Scan 0x08-0x77, skipping I2C reserved addresses 0x00-0x07
|
// Scan 0x08-0x77, skipping I2C reserved addresses 0x00-0x07
|
||||||
|
|
||||||
for addr := 0x08; addr <= 0x77; addr++ {
|
for addr := 0x08; addr <= 0x77; addr++ {
|
||||||
|
|
||||||
// Set slave address — EBUSY means a kernel driver owns this address
|
// Set slave address — EBUSY means a kernel driver owns this address
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
if errno == syscall.EBUSY {
|
if errno == syscall.EBUSY {
|
||||||
|
|
||||||
found = append(found, deviceEntry{
|
found = append(found, deviceEntry{
|
||||||
|
|
||||||
Address: fmt.Sprintf("0x%02x", addr),
|
Address: fmt.Sprintf("0x%02x", addr),
|
||||||
|
Status: "busy (in use by kernel driver)",
|
||||||
Status: "busy (in use by kernel driver)",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if smbusProbe(fd, addr, hasQuick) {
|
if smbusProbe(fd, addr, hasQuick) {
|
||||||
|
|
||||||
found = append(found, deviceEntry{
|
found = append(found, deviceEntry{
|
||||||
|
|
||||||
Address: fmt.Sprintf("0x%02x", addr),
|
Address: fmt.Sprintf("0x%02x", addr),
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(found) == 0 {
|
if len(found) == 0 {
|
||||||
|
|
||||||
return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath))
|
return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]any{
|
result, _ := json.MarshalIndent(map[string]any{
|
||||||
|
"bus": devPath,
|
||||||
"bus": devPath,
|
|
||||||
|
|
||||||
"devices": found,
|
"devices": found,
|
||||||
|
"count": len(found),
|
||||||
"count": len(found),
|
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
|
|
||||||
return SilentResult(fmt.Sprintf("Scan of %s:\n%s", devPath, string(result)))
|
return SilentResult(fmt.Sprintf("Scan of %s:\n%s", devPath, string(result)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readDevice reads bytes from an I2C device, optionally at a specific register
|
// readDevice reads bytes from an I2C device, optionally at a specific register.
|
||||||
|
|
||||||
func (t *I2CTool) readDevice(args map[string]any) *ToolResult {
|
func (t *I2CTool) readDevice(args map[string]any) *ToolResult {
|
||||||
|
|
||||||
bus, errResult := parseI2CBus(args)
|
bus, errResult := parseI2CBus(args)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, errResult := parseI2CAddress(args)
|
addr, errResult := parseI2CAddress(args)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
length := 1
|
length := 1
|
||||||
|
|
||||||
if l, ok := args["length"].(float64); ok {
|
if l, ok := args["length"].(float64); ok {
|
||||||
|
|
||||||
length = int(l)
|
length = int(l)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if length < 1 || length > 256 {
|
if length < 1 || length > 256 {
|
||||||
|
|
||||||
return ErrorResult("length must be between 1 and 256")
|
return ErrorResult("length must be between 1 and 256")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
|
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
|
||||||
|
|
||||||
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
|
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer syscall.Close(fd)
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
// Set slave address
|
// Set slave address
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
|
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If register is specified, write it first
|
// If register is specified, write it first
|
||||||
|
|
||||||
if regFloat, ok := args["register"].(float64); ok {
|
if regFloat, ok := args["register"].(float64); ok {
|
||||||
|
|
||||||
reg := int(regFloat)
|
reg := int(regFloat)
|
||||||
|
|
||||||
if reg < 0 || reg > 255 {
|
if reg < 0 || reg > 255 {
|
||||||
|
|
||||||
return ErrorResult("register must be between 0x00 and 0xFF")
|
return ErrorResult("register must be between 0x00 and 0xFF")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = syscall.Write(fd, []byte{byte(reg)})
|
_, err = syscall.Write(fd, []byte{byte(reg)})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to write register 0x%02x: %v", reg, err))
|
return ErrorResult(fmt.Sprintf("failed to write register 0x%02x: %v", reg, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
|
|
||||||
buf := make([]byte, length)
|
buf := make([]byte, length)
|
||||||
|
|
||||||
n, err := syscall.Read(fd, buf)
|
n, err := syscall.Read(fd, buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to read from device 0x%02x: %v", addr, err))
|
return ErrorResult(fmt.Sprintf("failed to read from device 0x%02x: %v", addr, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format as hex bytes
|
// Format as hex bytes
|
||||||
|
|
||||||
hexBytes := make([]string, n)
|
hexBytes := make([]string, n)
|
||||||
|
|
||||||
intBytes := make([]int, n)
|
intBytes := make([]int, n)
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
|
|
||||||
hexBytes[i] = fmt.Sprintf("0x%02x", buf[i])
|
hexBytes[i] = fmt.Sprintf("0x%02x", buf[i])
|
||||||
|
|
||||||
intBytes[i] = int(buf[i])
|
intBytes[i] = int(buf[i])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]any{
|
result, _ := json.MarshalIndent(map[string]any{
|
||||||
|
"bus": devPath,
|
||||||
"bus": devPath,
|
|
||||||
|
|
||||||
"address": fmt.Sprintf("0x%02x", addr),
|
"address": fmt.Sprintf("0x%02x", addr),
|
||||||
|
"bytes": intBytes,
|
||||||
"bytes": intBytes,
|
"hex": hexBytes,
|
||||||
|
"length": n,
|
||||||
"hex": hexBytes,
|
|
||||||
|
|
||||||
"length": n,
|
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
|
|
||||||
return SilentResult(string(result))
|
return SilentResult(string(result))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeDevice writes bytes to an I2C device, optionally at a specific register
|
// writeDevice writes bytes to an I2C device, optionally at a specific register.
|
||||||
|
|
||||||
func (t *I2CTool) writeDevice(args map[string]any) *ToolResult {
|
func (t *I2CTool) writeDevice(args map[string]any) *ToolResult {
|
||||||
|
|
||||||
confirm, _ := args["confirm"].(bool)
|
confirm, _ := args["confirm"].(bool)
|
||||||
|
|
||||||
if !confirm {
|
if !confirm {
|
||||||
|
|
||||||
return ErrorResult(
|
return ErrorResult(
|
||||||
|
"write operations require confirm: true." +
|
||||||
"write operations require confirm: true. Please confirm with the user before writing to I2C devices, as incorrect writes can misconfigure hardware.",
|
" Please confirm with the user before writing to I2C devices," +
|
||||||
|
" as incorrect writes can misconfigure hardware.",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bus, errResult := parseI2CBus(args)
|
bus, errResult := parseI2CBus(args)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, errResult := parseI2CAddress(args)
|
addr, errResult := parseI2CAddress(args)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dataRaw, ok := args["data"].([]any)
|
dataRaw, ok := args["data"].([]any)
|
||||||
|
|
||||||
if !ok || len(dataRaw) == 0 {
|
if !ok || len(dataRaw) == 0 {
|
||||||
|
|
||||||
return ErrorResult("data is required for write (array of byte values 0-255)")
|
return ErrorResult("data is required for write (array of byte values 0-255)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(dataRaw) > 256 {
|
if len(dataRaw) > 256 {
|
||||||
|
|
||||||
return ErrorResult("data too long: maximum 256 bytes per I2C transaction")
|
return ErrorResult("data too long: maximum 256 bytes per I2C transaction")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]byte, 0, len(dataRaw)+1)
|
data := make([]byte, 0, len(dataRaw)+1)
|
||||||
|
|
||||||
// If register is specified, prepend it to the data
|
// If register is specified, prepend it to the data
|
||||||
|
|
||||||
if regFloat, ok := args["register"].(float64); ok {
|
if regFloat, ok := args["register"].(float64); ok {
|
||||||
|
|
||||||
reg := int(regFloat)
|
reg := int(regFloat)
|
||||||
|
|
||||||
if reg < 0 || reg > 255 {
|
if reg < 0 || reg > 255 {
|
||||||
|
|
||||||
return ErrorResult("register must be between 0x00 and 0xFF")
|
return ErrorResult("register must be between 0x00 and 0xFF")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data = append(data, byte(reg))
|
data = append(data, byte(reg))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range dataRaw {
|
for i, v := range dataRaw {
|
||||||
|
|
||||||
f, ok := v.(float64)
|
f, ok := v.(float64)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
|
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b := int(f)
|
b := int(f)
|
||||||
|
|
||||||
if b < 0 || b > 255 {
|
if b < 0 || b > 255 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
|
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data = append(data, byte(b))
|
data = append(data, byte(b))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
|
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
|
||||||
|
|
||||||
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
|
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer syscall.Close(fd)
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
// Set slave address
|
// Set slave address
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
|
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write data
|
// Write data
|
||||||
|
|
||||||
n, err := syscall.Write(fd, data)
|
n, err := syscall.Write(fd, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("failed to write to device 0x%02x: %v", addr, err))
|
return ErrorResult(fmt.Sprintf("failed to write to device 0x%02x: %v", addr, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SilentResult(fmt.Sprintf("Wrote %d byte(s) to device 0x%02x on %s", n, addr, devPath))
|
return SilentResult(fmt.Sprintf("Wrote %d byte(s) to device 0x%02x on %s", n, addr, devPath))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,129 +11,76 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func prepareCommandForTermination(cmd *exec.Cmd) {
|
func prepareCommandForTermination(cmd *exec.Cmd) {
|
||||||
|
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func terminateProcessTree(cmd *exec.Cmd) error {
|
func terminateProcessTree(cmd *exec.Cmd) error {
|
||||||
|
|
||||||
if cmd == nil || cmd.Process == nil {
|
if cmd == nil || cmd.Process == nil {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pid := cmd.Process.Pid
|
pid := cmd.Process.Pid
|
||||||
|
|
||||||
if pid <= 0 {
|
if pid <= 0 {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill the entire process group spawned by the shell command.
|
// Kill the entire process group spawned by the shell command.
|
||||||
|
|
||||||
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
||||||
|
|
||||||
// Some shells/background jobs may still leave descendants around
|
// Some shells/background jobs may still leave descendants around
|
||||||
|
|
||||||
// briefly; aggressively walk /proc and kill child processes too.
|
// briefly; aggressively walk /proc and kill child processes too.
|
||||||
|
|
||||||
killDescendants(pid)
|
killDescendants(pid)
|
||||||
|
|
||||||
// Fallback kill on the shell process itself.
|
// Fallback kill on the shell process itself.
|
||||||
|
|
||||||
_ = cmd.Process.Kill()
|
_ = cmd.Process.Kill()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func killDescendants(ppid int) {
|
func killDescendants(ppid int) {
|
||||||
|
|
||||||
if ppid <= 0 {
|
if ppid <= 0 {
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := os.ReadDir("/proc")
|
entries, err := os.ReadDir("/proc")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
|
|
||||||
if !e.IsDir() {
|
if !e.IsDir() {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
childPID, err := strconv.Atoi(e.Name())
|
childPID, err := strconv.Atoi(e.Name())
|
||||||
|
|
||||||
if err != nil || childPID <= 0 || childPID == ppid {
|
if err != nil || childPID <= 0 || childPID == ppid {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statPath := "/proc/" + e.Name() + "/stat"
|
statPath := "/proc/" + e.Name() + "/stat"
|
||||||
|
|
||||||
data, err := os.ReadFile(statPath)
|
data, err := os.ReadFile(statPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// /proc/<pid>/stat: pid (comm) state ppid ...
|
// /proc/<pid>/stat: pid (comm) state ppid ...
|
||||||
|
|
||||||
raw := string(data)
|
raw := string(data)
|
||||||
|
|
||||||
end := strings.LastIndex(raw, ")")
|
end := strings.LastIndex(raw, ")")
|
||||||
|
|
||||||
if end == -1 || end+2 >= len(raw) {
|
if end == -1 || end+2 >= len(raw) {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Fields(raw[end+2:])
|
fields := strings.Fields(raw[end+2:])
|
||||||
|
|
||||||
if len(fields) < 2 {
|
if len(fields) < 2 {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parent, err := strconv.Atoi(fields[1])
|
parent, err := strconv.Atoi(fields[1])
|
||||||
|
|
||||||
if err != nil || parent != ppid {
|
if err != nil || parent != ppid {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse first, then kill child process/group.
|
// Recurse first, then kill child process/group.
|
||||||
|
|
||||||
killDescendants(childPID)
|
killDescendants(childPID)
|
||||||
|
|
||||||
_ = syscall.Kill(-childPID, syscall.SIGKILL)
|
_ = syscall.Kill(-childPID, syscall.SIGKILL)
|
||||||
|
|
||||||
_ = syscall.Kill(childPID, syscall.SIGKILL)
|
_ = syscall.Kill(childPID, syscall.SIGKILL)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,122 +14,69 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func processRunning(pid int) bool {
|
func processRunning(pid int) bool {
|
||||||
|
|
||||||
if pid <= 0 {
|
if pid <= 0 {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// kill(0) can return success for zombie processes too, so inspect /proc
|
// kill(0) can return success for zombie processes too, so inspect /proc
|
||||||
|
|
||||||
// state and treat zombies as not-running for timeout cleanup assertions.
|
// state and treat zombies as not-running for timeout cleanup assertions.
|
||||||
|
|
||||||
err := syscall.Kill(pid, 0)
|
err := syscall.Kill(pid, 0)
|
||||||
|
|
||||||
if err != nil && err != syscall.EPERM {
|
if err != nil && err != syscall.EPERM {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, readErr := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
|
data, readErr := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
|
||||||
|
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := string(data)
|
raw := string(data)
|
||||||
|
|
||||||
end := strings.LastIndex(raw, ")")
|
end := strings.LastIndex(raw, ")")
|
||||||
|
|
||||||
if end == -1 || end+2 >= len(raw) {
|
if end == -1 || end+2 >= len(raw) {
|
||||||
|
|
||||||
return true // best effort fallback
|
return true // best effort fallback
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Fields(raw[end+2:])
|
fields := strings.Fields(raw[end+2:])
|
||||||
|
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
|
|
||||||
return true // best effort fallback
|
return true // best effort fallback
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state := fields[0]
|
state := fields[0]
|
||||||
|
|
||||||
return state != "Z"
|
return state != "Z"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShellTool_TimeoutKillsChildProcess(t *testing.T) {
|
func TestShellTool_TimeoutKillsChildProcess(t *testing.T) {
|
||||||
|
|
||||||
tool, err := NewExecTool(t.TempDir(), false)
|
tool, err := NewExecTool(t.TempDir(), false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
t.Errorf("unable to configure exec tool: %s", err)
|
t.Errorf("unable to configure exec tool: %s", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tool.SetTimeout(500 * time.Millisecond)
|
tool.SetTimeout(500 * time.Millisecond)
|
||||||
|
|
||||||
args := map[string]any{
|
args := map[string]any{
|
||||||
|
|
||||||
// Spawn a child process that would outlive the shell unless process-group kill is used.
|
// Spawn a child process that would outlive the shell unless process-group kill is used.
|
||||||
|
|
||||||
"command": "sleep 60 & echo $! > child.pid; wait",
|
"command": "sleep 60 & echo $! > child.pid; wait",
|
||||||
}
|
}
|
||||||
|
|
||||||
result := tool.Execute(context.Background(), args)
|
result := tool.Execute(context.Background(), args)
|
||||||
|
|
||||||
if !result.IsError {
|
if !result.IsError {
|
||||||
|
|
||||||
t.Fatalf("expected timeout error, got success: %s", result.ForLLM)
|
t.Fatalf("expected timeout error, got success: %s", result.ForLLM)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(result.ForLLM, "timed out") {
|
if !strings.Contains(result.ForLLM, "timed out") {
|
||||||
|
|
||||||
t.Fatalf("expected timeout message, got: %s", result.ForLLM)
|
t.Fatalf("expected timeout message, got: %s", result.ForLLM)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
childPIDPath := filepath.Join(tool.workingDir, "child.pid")
|
childPIDPath := filepath.Join(tool.workingDir, "child.pid")
|
||||||
|
|
||||||
data, err := os.ReadFile(childPIDPath)
|
data, err := os.ReadFile(childPIDPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
t.Fatalf("failed to read child pid file: %v", err)
|
t.Fatalf("failed to read child pid file: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
childPID, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
childPID, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
t.Fatalf("failed to parse child pid: %v", err)
|
t.Fatalf("failed to parse child pid: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline := time.Now().Add(2 * time.Second)
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
|
|
||||||
if !processRunning(childPID) {
|
if !processRunning(childPID) {
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Fatalf("child process %d is still running after timeout", childPID)
|
t.Fatalf("child process %d is still running after timeout", childPID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,321 +9,193 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI ioctl constants from Linux kernel headers.
|
// SPI ioctl constants from Linux kernel headers.
|
||||||
|
|
||||||
// Calculated from _IOW('k', nr, size) macro:
|
// Calculated from _IOW('k', nr, size) macro:
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
// direction(1)<<30 | size<<16 | type(0x6B)<<8 | nr
|
// direction(1)<<30 | size<<16 | type(0x6B)<<8 | nr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
spiIocWrMode = 0x40016B01 // _IOW('k', 1, __u8)
|
spiIocWrMode = 0x40016B01 // _IOW('k', 1, __u8)
|
||||||
|
|
||||||
spiIocWrBitsPerWord = 0x40016B03 // _IOW('k', 3, __u8)
|
spiIocWrBitsPerWord = 0x40016B03 // _IOW('k', 3, __u8)
|
||||||
|
spiIocWrMaxSpeedHz = 0x40046B04 // _IOW('k', 4, __u32)
|
||||||
spiIocWrMaxSpeedHz = 0x40046B04 // _IOW('k', 4, __u32)
|
spiIocMessage1 = 0x40206B00 // _IOW('k', 0, struct spi_ioc_transfer) — 32 bytes
|
||||||
|
|
||||||
spiIocMessage1 = 0x40206B00 // _IOW('k', 0, struct spi_ioc_transfer) — 32 bytes
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// spiTransfer matches Linux kernel struct spi_ioc_transfer (32 bytes on all architectures).
|
// spiTransfer matches Linux kernel struct spi_ioc_transfer (32 bytes on all architectures).
|
||||||
|
|
||||||
type spiTransfer struct {
|
type spiTransfer struct {
|
||||||
txBuf uint64
|
txBuf uint64
|
||||||
|
rxBuf uint64
|
||||||
rxBuf uint64
|
length uint32
|
||||||
|
speedHz uint32
|
||||||
length uint32
|
delayUsecs uint16
|
||||||
|
|
||||||
speedHz uint32
|
|
||||||
|
|
||||||
delayUsecs uint16
|
|
||||||
|
|
||||||
bitsPerWord uint8
|
bitsPerWord uint8
|
||||||
|
csChange uint8
|
||||||
csChange uint8
|
txNbits uint8
|
||||||
|
rxNbits uint8
|
||||||
txNbits uint8
|
wordDelay uint8
|
||||||
|
pad uint8
|
||||||
rxNbits uint8
|
|
||||||
|
|
||||||
wordDelay uint8
|
|
||||||
|
|
||||||
pad uint8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// configureSPI opens an SPI device and sets mode, bits per word, and speed
|
// configureSPI opens an SPI device and sets mode, bits per word, and speed.
|
||||||
|
func configureSPI(
|
||||||
func configureSPI(devPath string, mode uint8, bits uint8, speed uint32) (int, *ToolResult) {
|
devPath string, mode uint8, bits uint8, speed uint32,
|
||||||
|
) (int, *ToolResult) {
|
||||||
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return -1, ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and spidev module)", devPath, err))
|
return -1, ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and spidev module)", devPath, err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set SPI mode
|
// Set SPI mode
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrMode, uintptr(unsafe.Pointer(&mode)))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrMode, uintptr(unsafe.Pointer(&mode)))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
syscall.Close(fd)
|
syscall.Close(fd)
|
||||||
|
|
||||||
return -1, ErrorResult(fmt.Sprintf("failed to set SPI mode %d: %v", mode, errno))
|
return -1, ErrorResult(fmt.Sprintf("failed to set SPI mode %d: %v", mode, errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set bits per word
|
// Set bits per word
|
||||||
|
|
||||||
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrBitsPerWord, uintptr(unsafe.Pointer(&bits)))
|
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrBitsPerWord, uintptr(unsafe.Pointer(&bits)))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
syscall.Close(fd)
|
syscall.Close(fd)
|
||||||
|
|
||||||
return -1, ErrorResult(fmt.Sprintf("failed to set bits per word %d: %v", bits, errno))
|
return -1, ErrorResult(fmt.Sprintf("failed to set bits per word %d: %v", bits, errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set max speed
|
// Set max speed
|
||||||
|
|
||||||
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrMaxSpeedHz, uintptr(unsafe.Pointer(&speed)))
|
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrMaxSpeedHz, uintptr(unsafe.Pointer(&speed)))
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
syscall.Close(fd)
|
syscall.Close(fd)
|
||||||
|
|
||||||
return -1, ErrorResult(fmt.Sprintf("failed to set SPI speed %d Hz: %v", speed, errno))
|
return -1, ErrorResult(fmt.Sprintf("failed to set SPI speed %d Hz: %v", speed, errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fd, nil
|
return fd, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// transfer performs a full-duplex SPI transfer
|
// transfer performs a full-duplex SPI transfer.
|
||||||
|
|
||||||
func (t *SPITool) transfer(args map[string]any) *ToolResult {
|
func (t *SPITool) transfer(args map[string]any) *ToolResult {
|
||||||
|
|
||||||
confirm, _ := args["confirm"].(bool)
|
confirm, _ := args["confirm"].(bool)
|
||||||
|
|
||||||
if !confirm {
|
if !confirm {
|
||||||
|
|
||||||
return ErrorResult(
|
return ErrorResult(
|
||||||
|
"transfer operations require confirm: true." +
|
||||||
"transfer operations require confirm: true. Please confirm with the user before sending data to SPI devices.",
|
" Please confirm with the user before sending data to SPI devices.",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dev, speed, mode, bits, errMsg := parseSPIArgs(args)
|
dev, speed, mode, bits, errMsg := parseSPIArgs(args)
|
||||||
|
|
||||||
if errMsg != "" {
|
if errMsg != "" {
|
||||||
|
|
||||||
return ErrorResult(errMsg)
|
return ErrorResult(errMsg)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dataRaw, ok := args["data"].([]any)
|
dataRaw, ok := args["data"].([]any)
|
||||||
|
|
||||||
if !ok || len(dataRaw) == 0 {
|
if !ok || len(dataRaw) == 0 {
|
||||||
|
|
||||||
return ErrorResult("data is required for transfer (array of byte values 0-255)")
|
return ErrorResult("data is required for transfer (array of byte values 0-255)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(dataRaw) > 4096 {
|
if len(dataRaw) > 4096 {
|
||||||
|
|
||||||
return ErrorResult("data too long: maximum 4096 bytes per SPI transfer")
|
return ErrorResult("data too long: maximum 4096 bytes per SPI transfer")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
txBuf := make([]byte, len(dataRaw))
|
txBuf := make([]byte, len(dataRaw))
|
||||||
|
|
||||||
for i, v := range dataRaw {
|
for i, v := range dataRaw {
|
||||||
|
|
||||||
f, ok := v.(float64)
|
f, ok := v.(float64)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
|
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b := int(f)
|
b := int(f)
|
||||||
|
|
||||||
if b < 0 || b > 255 {
|
if b < 0 || b > 255 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
|
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
txBuf[i] = byte(b)
|
txBuf[i] = byte(b)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
devPath := fmt.Sprintf("/dev/spidev%s", dev)
|
devPath := fmt.Sprintf("/dev/spidev%s", dev)
|
||||||
|
|
||||||
fd, errResult := configureSPI(devPath, mode, bits, speed)
|
fd, errResult := configureSPI(devPath, mode, bits, speed)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer syscall.Close(fd)
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
rxBuf := make([]byte, len(txBuf))
|
rxBuf := make([]byte, len(txBuf))
|
||||||
|
|
||||||
xfer := spiTransfer{
|
xfer := spiTransfer{
|
||||||
|
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
|
||||||
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
|
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
|
||||||
|
length: uint32(len(txBuf)),
|
||||||
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
|
speedHz: speed,
|
||||||
|
|
||||||
length: uint32(len(txBuf)),
|
|
||||||
|
|
||||||
speedHz: speed,
|
|
||||||
|
|
||||||
bitsPerWord: bits,
|
bitsPerWord: bits,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocMessage1, uintptr(unsafe.Pointer(&xfer)))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocMessage1, uintptr(unsafe.Pointer(&xfer)))
|
||||||
|
|
||||||
runtime.KeepAlive(txBuf)
|
runtime.KeepAlive(txBuf)
|
||||||
|
|
||||||
runtime.KeepAlive(rxBuf)
|
runtime.KeepAlive(rxBuf)
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("SPI transfer failed: %v", errno))
|
return ErrorResult(fmt.Sprintf("SPI transfer failed: %v", errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format received bytes
|
// Format received bytes
|
||||||
|
|
||||||
hexBytes := make([]string, len(rxBuf))
|
hexBytes := make([]string, len(rxBuf))
|
||||||
|
|
||||||
intBytes := make([]int, len(rxBuf))
|
intBytes := make([]int, len(rxBuf))
|
||||||
|
|
||||||
for i, b := range rxBuf {
|
for i, b := range rxBuf {
|
||||||
|
|
||||||
hexBytes[i] = fmt.Sprintf("0x%02x", b)
|
hexBytes[i] = fmt.Sprintf("0x%02x", b)
|
||||||
|
|
||||||
intBytes[i] = int(b)
|
intBytes[i] = int(b)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]any{
|
result, _ := json.MarshalIndent(map[string]any{
|
||||||
|
"device": devPath,
|
||||||
"device": devPath,
|
"sent": len(txBuf),
|
||||||
|
|
||||||
"sent": len(txBuf),
|
|
||||||
|
|
||||||
"received": intBytes,
|
"received": intBytes,
|
||||||
|
"hex": hexBytes,
|
||||||
"hex": hexBytes,
|
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
|
|
||||||
return SilentResult(string(result))
|
return SilentResult(string(result))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readDevice reads bytes from SPI by sending zeros (read-only, no confirm needed)
|
// readDevice reads bytes from SPI by sending zeros (read-only, no confirm needed).
|
||||||
|
|
||||||
func (t *SPITool) readDevice(args map[string]any) *ToolResult {
|
func (t *SPITool) readDevice(args map[string]any) *ToolResult {
|
||||||
|
|
||||||
dev, speed, mode, bits, errMsg := parseSPIArgs(args)
|
dev, speed, mode, bits, errMsg := parseSPIArgs(args)
|
||||||
|
|
||||||
if errMsg != "" {
|
if errMsg != "" {
|
||||||
|
|
||||||
return ErrorResult(errMsg)
|
return ErrorResult(errMsg)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
length := 0
|
length := 0
|
||||||
|
|
||||||
if l, ok := args["length"].(float64); ok {
|
if l, ok := args["length"].(float64); ok {
|
||||||
|
|
||||||
length = int(l)
|
length = int(l)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if length < 1 || length > 4096 {
|
if length < 1 || length > 4096 {
|
||||||
|
|
||||||
return ErrorResult("length is required for read (1-4096)")
|
return ErrorResult("length is required for read (1-4096)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
devPath := fmt.Sprintf("/dev/spidev%s", dev)
|
devPath := fmt.Sprintf("/dev/spidev%s", dev)
|
||||||
|
|
||||||
fd, errResult := configureSPI(devPath, mode, bits, speed)
|
fd, errResult := configureSPI(devPath, mode, bits, speed)
|
||||||
|
|
||||||
if errResult != nil {
|
if errResult != nil {
|
||||||
|
|
||||||
return errResult
|
return errResult
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer syscall.Close(fd)
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
txBuf := make([]byte, length) // zeros
|
txBuf := make([]byte, length) // zeros
|
||||||
|
|
||||||
rxBuf := make([]byte, length)
|
rxBuf := make([]byte, length)
|
||||||
|
|
||||||
xfer := spiTransfer{
|
xfer := spiTransfer{
|
||||||
|
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
|
||||||
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
|
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
|
||||||
|
length: uint32(length),
|
||||||
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
|
speedHz: speed,
|
||||||
|
|
||||||
length: uint32(length),
|
|
||||||
|
|
||||||
speedHz: speed,
|
|
||||||
|
|
||||||
bitsPerWord: bits,
|
bitsPerWord: bits,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocMessage1, uintptr(unsafe.Pointer(&xfer)))
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocMessage1, uintptr(unsafe.Pointer(&xfer)))
|
||||||
|
|
||||||
runtime.KeepAlive(txBuf)
|
runtime.KeepAlive(txBuf)
|
||||||
|
|
||||||
runtime.KeepAlive(rxBuf)
|
runtime.KeepAlive(rxBuf)
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
|
||||||
return ErrorResult(fmt.Sprintf("SPI read failed: %v", errno))
|
return ErrorResult(fmt.Sprintf("SPI read failed: %v", errno))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hexBytes := make([]string, len(rxBuf))
|
hexBytes := make([]string, len(rxBuf))
|
||||||
|
|
||||||
intBytes := make([]int, len(rxBuf))
|
intBytes := make([]int, len(rxBuf))
|
||||||
|
|
||||||
for i, b := range rxBuf {
|
for i, b := range rxBuf {
|
||||||
|
|
||||||
hexBytes[i] = fmt.Sprintf("0x%02x", b)
|
hexBytes[i] = fmt.Sprintf("0x%02x", b)
|
||||||
|
|
||||||
intBytes[i] = int(b)
|
intBytes[i] = int(b)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]any{
|
result, _ := json.MarshalIndent(map[string]any{
|
||||||
|
|
||||||
"device": devPath,
|
"device": devPath,
|
||||||
|
"bytes": intBytes,
|
||||||
"bytes": intBytes,
|
"hex": hexBytes,
|
||||||
|
|
||||||
"hex": hexBytes,
|
|
||||||
|
|
||||||
"length": len(rxBuf),
|
"length": len(rxBuf),
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
|
|
||||||
return SilentResult(string(result))
|
return SilentResult(string(result))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue