Spaces:
Sleeping
Sleeping
File size: 8,476 Bytes
da590a7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | package tools
import (
"encoding/json"
"fmt"
"syscall"
"unsafe"
)
// I2C ioctl constants from Linux kernel headers (<linux/i2c-dev.h>, <linux/i2c.h>)
const (
i2cSlave = 0x0703 // Set slave address (fails if in use by driver)
i2cFuncs = 0x0705 // Query adapter functionality bitmask
i2cSmbus = 0x0720 // Perform SMBus transaction
// I2C_FUNC capability bits
i2cFuncSmbusQuick = 0x00010000
i2cFuncSmbusReadByte = 0x00020000
// SMBus transaction types
i2cSmbusRead = 0
i2cSmbusWrite = 1
// SMBus protocol sizes
i2cSmbusQuick = 0
i2cSmbusByte = 1
)
// 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).
type i2cSmbusData [34]byte
// i2cSmbusArgs matches the kernel struct i2c_smbus_ioctl_data.
type i2cSmbusArgs struct {
readWrite uint8
command uint8
size uint32
data *i2cSmbusData
}
// smbusProbe performs a single SMBus probe at the given address.
// Uses SMBus Quick Write (safest) or falls back to SMBus Read Byte for
// EEPROM address ranges where quick write can corrupt AT24RF08 chips.
// This matches i2cdetect's MODE_AUTO behavior.
func smbusProbe(fd int, addr int, hasQuick bool) bool {
// EEPROM ranges: use read byte (quick write can corrupt AT24RF08)
useReadByte := (addr >= 0x30 && addr <= 0x37) || (addr >= 0x50 && addr <= 0x5F)
if !useReadByte && hasQuick {
// SMBus Quick Write: [START] [ADDR|W] [ACK/NACK] [STOP]
// Safest probe — no data transferred
args := i2cSmbusArgs{
readWrite: i2cSmbusWrite,
command: 0,
size: i2cSmbusQuick,
data: nil,
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
return errno == 0
}
// SMBus Read Byte: [START] [ADDR|R] [ACK/NACK] [DATA] [STOP]
var data i2cSmbusData
args := i2cSmbusArgs{
readWrite: i2cSmbusRead,
command: 0,
size: i2cSmbusByte,
data: &data,
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
return errno == 0
}
// scan probes valid 7-bit addresses on a bus for connected devices.
// Uses the same hybrid probe strategy as i2cdetect's MODE_AUTO:
// SMBus Quick Write for most addresses, SMBus Read Byte for EEPROM ranges.
func (t *I2CTool) scan(args map[string]interface{}) *ToolResult {
bus, errResult := parseI2CBus(args)
if errResult != nil {
return errResult
}
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and i2c-dev module)", devPath, err))
}
defer syscall.Close(fd)
// Query adapter capabilities to determine available probe methods.
// I2C_FUNCS writes an unsigned long, which is word-sized on Linux.
var funcs uintptr
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cFuncs, uintptr(unsafe.Pointer(&funcs)))
if errno != 0 {
return ErrorResult(fmt.Sprintf("failed to query I2C adapter capabilities on %s: %v", devPath, errno))
}
hasQuick := funcs&i2cFuncSmbusQuick != 0
hasReadByte := funcs&i2cFuncSmbusReadByte != 0
if !hasQuick && !hasReadByte {
return ErrorResult(fmt.Sprintf("I2C adapter %s supports neither SMBus Quick nor Read Byte — cannot probe safely", devPath))
}
type deviceEntry struct {
Address string `json:"address"`
Status string `json:"status,omitempty"`
}
var found []deviceEntry
// Scan 0x08-0x77, skipping I2C reserved addresses 0x00-0x07
for addr := 0x08; addr <= 0x77; addr++ {
// Set slave address — EBUSY means a kernel driver owns this address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 {
if errno == syscall.EBUSY {
found = append(found, deviceEntry{
Address: fmt.Sprintf("0x%02x", addr),
Status: "busy (in use by kernel driver)",
})
}
continue
}
if smbusProbe(fd, addr, hasQuick) {
found = append(found, deviceEntry{
Address: fmt.Sprintf("0x%02x", addr),
})
}
}
if len(found) == 0 {
return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath))
}
result, _ := json.MarshalIndent(map[string]interface{}{
"bus": devPath,
"devices": found,
"count": len(found),
}, "", " ")
return SilentResult(fmt.Sprintf("Scan of %s:\n%s", devPath, string(result)))
}
// readDevice reads bytes from an I2C device, optionally at a specific register
func (t *I2CTool) readDevice(args map[string]interface{}) *ToolResult {
bus, errResult := parseI2CBus(args)
if errResult != nil {
return errResult
}
addr, errResult := parseI2CAddress(args)
if errResult != nil {
return errResult
}
length := 1
if l, ok := args["length"].(float64); ok {
length = int(l)
}
if length < 1 || length > 256 {
return ErrorResult("length must be between 1 and 256")
}
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
}
defer syscall.Close(fd)
// Set slave address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 {
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
}
// If register is specified, write it first
if regFloat, ok := args["register"].(float64); ok {
reg := int(regFloat)
if reg < 0 || reg > 255 {
return ErrorResult("register must be between 0x00 and 0xFF")
}
_, err := syscall.Write(fd, []byte{byte(reg)})
if err != nil {
return ErrorResult(fmt.Sprintf("failed to write register 0x%02x: %v", reg, err))
}
}
// Read data
buf := make([]byte, length)
n, err := syscall.Read(fd, buf)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to read from device 0x%02x: %v", addr, err))
}
// Format as hex bytes
hexBytes := make([]string, n)
intBytes := make([]int, n)
for i := 0; i < n; i++ {
hexBytes[i] = fmt.Sprintf("0x%02x", buf[i])
intBytes[i] = int(buf[i])
}
result, _ := json.MarshalIndent(map[string]interface{}{
"bus": devPath,
"address": fmt.Sprintf("0x%02x", addr),
"bytes": intBytes,
"hex": hexBytes,
"length": n,
}, "", " ")
return SilentResult(string(result))
}
// writeDevice writes bytes to an I2C device, optionally at a specific register
func (t *I2CTool) writeDevice(args map[string]interface{}) *ToolResult {
confirm, _ := args["confirm"].(bool)
if !confirm {
return ErrorResult("write operations require confirm: true. Please confirm with the user before writing to I2C devices, as incorrect writes can misconfigure hardware.")
}
bus, errResult := parseI2CBus(args)
if errResult != nil {
return errResult
}
addr, errResult := parseI2CAddress(args)
if errResult != nil {
return errResult
}
dataRaw, ok := args["data"].([]interface{})
if !ok || len(dataRaw) == 0 {
return ErrorResult("data is required for write (array of byte values 0-255)")
}
if len(dataRaw) > 256 {
return ErrorResult("data too long: maximum 256 bytes per I2C transaction")
}
data := make([]byte, 0, len(dataRaw)+1)
// If register is specified, prepend it to the data
if regFloat, ok := args["register"].(float64); ok {
reg := int(regFloat)
if reg < 0 || reg > 255 {
return ErrorResult("register must be between 0x00 and 0xFF")
}
data = append(data, byte(reg))
}
for i, v := range dataRaw {
f, ok := v.(float64)
if !ok {
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
}
b := int(f)
if b < 0 || b > 255 {
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
}
data = append(data, byte(b))
}
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
}
defer syscall.Close(fd)
// Set slave address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 {
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
}
// Write data
n, err := syscall.Write(fd, data)
if err != nil {
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))
}
|