在go语言里调用Linux设备驱动不像在C语言那样简单方便,C语言中直接像普通文件那样open就好了,而且可以直接使用ioctl去控制配置设备,在go中要实现驱动调用又另外封装了一层且与打开普通文件也不一样。
go其实没有ioctl,最终调用的都是:
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
需要使用的话得自己封装一下,这里是封装好的:
package go_ioctl
import (
"syscall"
)
// Generic ioctl constants
const (
IOC_NONE = 0x0
IOC_WRITE = 0x1
IOC_READ = 0x2
IOC_NRBITS = 8
IOC_TYPEBITS = 8
IOC_SIZEBITS = 14
IOC_DIRBITS = 2
IOC_NRSHIFT = 0
IOC_TYPESHIFT = IOC_NRSHIFT + IOC_NRBITS
IOC_SIZESHIFT = IOC_TYPESHIFT + IOC_TYPEBITS
IOC_DIRSHIFT = IOC_SIZESHIFT + IOC_SIZEBITS
IOC_NRMASK = ((1 << IOC_NRBITS) - 1)
IOC_TYPEMASK = ((1 << IOC_TYPEBITS) - 1)
IOC_SIZEMASK = ((1 << IOC_SIZEBITS) - 1)
IOC_DIRMASK = ((1 << IOC_DIRBITS) - 1)
)
// Some useful additional ioctl constanst
const (
IOC_IN = IOC_WRITE << IOC_DIRSHIFT
IOC_OUT = IOC_READ << IOC_DIRSHIFT
IOC_INOUT = (IOC_WRITE | IOC_READ) << IOC_DIRSHIFT
IOCSIZE_MASK = IOC_SIZEMASK << IOC_SIZESHIFT
IOCSIZE_SHIFT = IOC_SIZESHIFT
)
// IOC generate IOC
func IOC(dir, t, nr, size uintptr) uintptr {
return (dir << IOC_DIRSHIFT) | (t << IOC_TYPESHIFT) |
(nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT)
}
// IOR generate IOR
func IOR(t, nr, size uintptr) uintptr {
return IOC(IOC_READ, t, nr, size)
}
// IOW generate IOW
func IOW(t, nr, size uintptr) uintptr {
return IOC(IOC_WRITE, t, nr, size)
}
// IOWR generate IOWR
func IOWR(t, nr, size uintptr) uintptr {
return IOC(IOC_READ|IOC_WRITE, t, nr, size)
}
// IO generate IO
func IO(t, nr uintptr) uintptr {
return IOC(IOC_NONE, t, nr, 0)
}
// IOCTL send ioctl
func IOCTL(fd, name, data uintptr) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, name, data)
if err != 0 {
return syscall.Errno(err)
}
return nil
}
现在这个 func IOCTL(fd, name, data uintptr) 就很像C的写法了,不过与具体设备的驱动交互还得增加命令,下面是封装好的SPI驱动的部分ioctl命令,方便调用
const spiIoctlMAGIC = 'k'
// Read of SPI mode (SPI_MODE_0..SPI_MODE_3)
func SPI_IOC_RD_MODE() uintptr {
return IOR(spiIoctlMAGIC, 1, 1)
}
// Write of SPI mode (SPI_MODE_0..SPI_MODE_3)
func SPI_IOC_WR_MODE() uintptr {
return IOW(spiIoctlMAGIC, 1, 1)
}
// Read SPI bit justification
func SPI_IOC_RD_LSB_FIRST() uintptr {
return IOR(spiIoctlMAGIC, 2, 1)
}
// Write SPI bit justification
func SPI_IOC_WR_LSB_FIRST() uintptr {
return IOW(spiIoctlMAGIC, 2, 1)
}
// Read SPI device word length (1..N)
func SPI_IOC_RD_BITS_PER_WORD() uintptr {
return IOR(spiIoctlMAGIC, 3, 1)
}
// Write SPI device word length (1..N)
func SPI_IOC_WR_BITS_PER_WORD() uintptr {
return IOW(spiIoctlMAGIC, 3, 1)
}
// Read SPI device default max speed hz
func SPI_IOC_RD_MAX_SPEED_HZ() uintptr {
return IOR(spiIoctlMAGIC, 4, 4)
}
// Write SPI device default max speed hz
func SPI_IOC_WR_MAX_SPEED_HZ() uintptr {
return IOW(spiIoctlMAGIC, 4, 4)
}
这是go一个与操作系统进行低级交互的一个包,驱动操作位于内核态,其地址空间与用户态隔离,在用户态得使用系统调用函数才能访问内核态数据,是内核与用户的桥梁。
当然,用syscall也是可以访问普通文件的。
syscall打开文件获得文件描述符之后就能使用上面封装好的IOCTL来配置设备了
package main
import (
"fmt"
"go_ioctl"
"syscall"
"unsafe"
)
const DEVICE string = "/dev/spidev0.0" /* 设备文件*/
var fd int
var err error
func main() {
//参数:文件路径,打开方式,打开模式(权限)
fd, err = syscall.Open(DEVICE, syscall.O_RDWR, 0777)
if err != nil {
fmt.Printf("device open failed\r\n")
syscall.Close(fd)
fmt.Println(fd, err)
}else{
speed := 8192000
//参数:文件描述符,命令,数据
err = go_ioctl.IOCTL(uintptr(fd), go_ioctl.SPI_IOC_WR_MAX_SPEED_HZ(), uintptr(unsafe.Pointer(&speed))) /*设置SPI时钟频率*/
if err != nil {
fmt.Printf("can't set spi speed\r\n")
syscall.Close(fd)
}else{
var filedata = make([]byte, 64)
len, _ := syscall.Read(fd, filedata)
if len > 0 {
fmt.Printf("filedata: %v\n", filedata)
}
}
}
}