diff options
Diffstat (limited to 'toolbox/toolbox_linux.go')
| -rw-r--r-- | toolbox/toolbox_linux.go | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/toolbox/toolbox_linux.go b/toolbox/toolbox_linux.go new file mode 100644 index 0000000..d35425e --- /dev/null +++ b/toolbox/toolbox_linux.go @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2026 Terin Stock <terinjokes@gmail.com> +// SPDX-License-Identifier: EUPL-1.2 + +// Package toolbox is implemented on Linux using the SCSI Generic (sg) driver, version 3. This +// driver has been included since Linux 2.4, and harmonizes access to SCSI devices. This +// implementation supports access via the high level driver paths (eg, /dev/sr0, /dev/st0) as well +// as the generic paths (eg, /dev/sg0). Raw SCSI commands are sent to the device using the SCSI +// Generic SG_IO ioctl. +package toolbox + +import ( + "fmt" + "os" + "structs" + "unsafe" + + "golang.org/x/sys/unix" +) + +const ( + SG_GET_VERSION_NUM = 0x2282 + SG_IO = 0x2285 +) + +type sgIoHdr struct { + _ structs.HostLayout + + interfaceID int32 + dxferDirection int32 + cmdLen uint8 + mxSbLen uint8 + iovecCount uint16 + dxferLen uint32 + dxferp *byte + cmdp *uint8 + sbp *byte + timeout uint32 + flags uint32 + packId int32 + userPtr *byte + status uint8 + maskedStatus uint8 + msgStatus uint8 + sbLenWr uint8 + hostStatus uint16 + driverStatus uint16 + resId int32 + duration uint32 + info uint32 +} + +func ioctlSgIo(fd uintptr, hdr *sgIoHdr) error { + _, _, errNo := unix.Syscall(unix.SYS_IOCTL, fd, SG_IO, uintptr(unsafe.Pointer(hdr))) + if errNo != 0 { + return errNo + } + return nil +} + +// Device holds an open reference to teh sg device. +type Device struct { + f *os.File +} + +// Open creats a connection to the sg device at devpath, which can be a high level +// device path (such as /dev/sr0) or the generic device path (such as /dev/sg0). +// Returns an error if the device cannot be opened, is not a SCSI device, or if +// the sg driver is too old (older than Linux 2.4). +func Open(devpath string) (*Device, error) { + f, err := os.OpenFile(devpath, os.O_RDONLY, 0) + if err != nil { + return nil, err + } + + version, err := unix.IoctlGetUint32(int(f.Fd()), SG_GET_VERSION_NUM) + if err != nil { + return nil, err + } + if version < 30_000 { + return nil, fmt.Errorf("requires a SCSI device or newer sg dervier") + } + + return &Device{f: f}, nil +} + +// Close closes the refernce to the sg driver. +func (d *Device) Close() error { + return d.f.Close() +} + +// CountCDs returns the count of files recognized by the emulator. +func (d *Device) CountCDs() (int, error) { + dxferBuf := byte(0) + cmdp := []byte{TOOLBOX_COUNT_CDS, 0, 0, 0, 0, 0} + + ioHdr := &sgIoHdr{ + interfaceID: int32('S'), + cmdLen: uint8(len(cmdp)), + mxSbLen: 0, + dxferDirection: -3, + cmdp: &cmdp[0], + dxferp: &dxferBuf, + dxferLen: 1, + timeout: 20_000, + } + + err := ioctlSgIo(d.f.Fd(), ioHdr) + if err != nil { + return 0, err + } + return int(dxferBuf), nil +} + +// ListCDs returns [FileEntry] instances corresponding to recognized +// files on the emulator. +func (d *Device) ListCDs() ([]FileEntry, error) { + cdCount, err := d.CountCDs() + if err != nil { + return nil, err + } + + fileEntries := make([]FileEntry, cdCount) + cmdp := []byte{TOOLBOX_LIST_CDS, 0, 0, 0, 0, 0} + + ioHdr := &sgIoHdr{ + interfaceID: int32('S'), + cmdLen: uint8(len(cmdp)), + mxSbLen: 0, + dxferDirection: -3, + cmdp: &cmdp[0], + dxferp: (*byte)(unsafe.Pointer(&fileEntries[0])), + dxferLen: uint32(int(unsafe.Sizeof(FileEntry{})) * cdCount), + timeout: 20_000, + } + + err = ioctlSgIo(d.f.Fd(), ioHdr) + if err != nil { + return nil, err + } + + return fileEntries, nil +} + +// SetCDByIndex request the emulator to switch media to the specified index. +func (d *Device) SetCDByIndex(idx int) error { + cmdp := []byte{TOOLBOX_SET_NEXT_CD, byte(idx), 0, 0, 0, 0} + + ioHdr := &sgIoHdr{ + interfaceID: int32('S'), + cmdLen: uint8(len(cmdp)), + mxSbLen: 0, + dxferDirection: -3, + cmdp: &cmdp[0], + timeout: 20_000, + } + + return ioctlSgIo(d.f.Fd(), ioHdr) +} + +// ListDevices returns the device types of each emulated SCSI device. +func (d *Device) ListDevices() ([]DeviceType, error) { + devices := make([]DeviceType, 8) + cmdp := []byte{TOOLBOX_LIST_DEVICES, 0, 0, 0, 0, 0} + ioHdr := &sgIoHdr{ + interfaceID: int32('S'), + cmdLen: uint8(len(cmdp)), + mxSbLen: 0, + dxferDirection: -3, + cmdp: &cmdp[0], + dxferp: (*byte)(unsafe.Pointer(&devices[0])), + dxferLen: 8, + timeout: 20_000, + } + + err := ioctlSgIo(d.f.Fd(), ioHdr) + if err != nil { + return nil, err + } + + return devices, nil +} |
