optimize the perf and support more features
This commit is contained in:
@@ -82,9 +82,9 @@ func bsPerformCommand(bs api.BackingStore, cmd *api.SCSICommand) (err error, key
|
||||
doWrite = true
|
||||
goto write
|
||||
case api.COMPARE_AND_WRITE:
|
||||
// TODO
|
||||
doWrite = true
|
||||
goto write
|
||||
// COMPARE_AND_WRITE is handled directly in SBCCompareAndWrite function
|
||||
// This case should not be reached
|
||||
return fmt.Errorf("COMPARE_AND_WRITE should be handled by SBCCompareAndWrite"), ILLEGAL_REQUEST, ASC_INVALID_OP_CODE
|
||||
case api.SYNCHRONIZE_CACHE, api.SYNCHRONIZE_CACHE_16:
|
||||
if tl == 0 {
|
||||
tl = int64(lu.Size - offset)
|
||||
|
||||
@@ -8,7 +8,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -51,10 +52,33 @@ func new() (api.BackingStore, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseStoragePath parses a storage path that may include backend type prefix
|
||||
// Format: [backend_type:]path
|
||||
// Examples:
|
||||
// - /var/tmp/disk.img (default file backend)
|
||||
// - file:/var/tmp/disk.img (explicit file backend)
|
||||
// - iouring:/var/tmp/disk.img (io_uring backend on Linux 5.1+)
|
||||
func parseStoragePath(path string) (backendType, filePath string) {
|
||||
if idx := strings.Index(path, ":"); idx > 0 {
|
||||
possibleType := path[:idx]
|
||||
// Check if it's a known backend type
|
||||
switch possibleType {
|
||||
case "file", "iouring", "ceph", "null", "RemBs":
|
||||
return possibleType, path[idx+1:]
|
||||
}
|
||||
}
|
||||
// Default to file backend
|
||||
return "file", path
|
||||
}
|
||||
|
||||
func (bs *FileBackingStore) Open(dev *api.SCSILu, path string) error {
|
||||
var mode os.FileMode
|
||||
|
||||
finfo, err := os.Stat(path)
|
||||
// Parse backend type and actual path
|
||||
backendType, filePath := parseStoragePath(path)
|
||||
_ = backendType // file backend ignores this
|
||||
|
||||
finfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
@@ -62,7 +86,7 @@ func (bs *FileBackingStore) Open(dev *api.SCSILu, path string) error {
|
||||
mode = finfo.Mode()
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(path, os.O_RDWR, os.ModePerm)
|
||||
f, err := os.OpenFile(filePath, os.O_RDWR, os.ModePerm)
|
||||
|
||||
if err == nil {
|
||||
// block device filesize needs to be treated differently
|
||||
|
||||
727
pkg/scsi/backingstore/iouring/iouring_linux.go
Normal file
727
pkg/scsi/backingstore/iouring/iouring_linux.go
Normal file
@@ -0,0 +1,727 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package iouring provides an io_uring-based backing store for high-performance
|
||||
// asynchronous I/O operations on Linux 5.1+ systems.
|
||||
package iouring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
"github.com/gostor/gotgt/pkg/scsi"
|
||||
)
|
||||
|
||||
const (
|
||||
IoUringBackingStorage = "iouring"
|
||||
|
||||
// Default queue depth for io_uring
|
||||
DefaultQueueDepth = 4096
|
||||
|
||||
// Minimum kernel version required (5.1)
|
||||
MinKernelMajor = 5
|
||||
MinKernelMinor = 1
|
||||
)
|
||||
|
||||
// io_uring constants (from linux/io_uring.h)
|
||||
const (
|
||||
IORING_SETUP_IOPOLL = 1 << 0
|
||||
IORING_SETUP_SQPOLL = 1 << 1
|
||||
IORING_SETUP_SQ_AFF = 1 << 2
|
||||
IORING_SETUP_CQSIZE = 1 << 3
|
||||
IORING_SETUP_CLAMP = 1 << 4
|
||||
IORING_SETUP_ATTACH_WQ = 1 << 5
|
||||
IORING_SETUP_R_DISABLED = 1 << 6
|
||||
|
||||
IORING_FSYNC_DATASYNC = 1 << 0
|
||||
|
||||
IORING_TIMEOUT_ABS = 1 << 0
|
||||
|
||||
IORING_OFF_SQ_RING = 0
|
||||
IORING_OFF_CQ_RING = 0x8000000
|
||||
IORING_OFF_SQES = 0x10000000
|
||||
|
||||
IORING_OP_NOP = 0
|
||||
IORING_OP_READV = 1
|
||||
IORING_OP_WRITEV = 2
|
||||
IORING_OP_FSYNC = 3
|
||||
IORING_OP_READ_FIXED = 4
|
||||
IORING_OP_WRITE_FIXED = 5
|
||||
IORING_OP_POLL_ADD = 6
|
||||
IORING_OP_POLL_REMOVE = 7
|
||||
IORING_OP_SYNC_FILE_RANGE = 8
|
||||
IORING_OP_SENDMSG = 9
|
||||
IORING_OP_RECVMSG = 10
|
||||
IORING_OP_TIMEOUT = 11
|
||||
IORING_OP_TIMEOUT_REMOVE = 12
|
||||
IORING_OP_ACCEPT = 13
|
||||
IORING_OP_ASYNC_CANCEL = 14
|
||||
IORING_OP_LINK_TIMEOUT = 15
|
||||
IORING_OP_CONNECT = 16
|
||||
IORING_OP_FALLOCATE = 17
|
||||
IORING_OP_OPENAT = 18
|
||||
IORING_OP_CLOSE = 19
|
||||
IORING_OP_FILES_UPDATE = 20
|
||||
IORING_OP_STATX = 21
|
||||
IORING_OP_READ = 22
|
||||
IORING_OP_WRITE = 23
|
||||
IORING_OP_FADVISE = 24
|
||||
IORING_OP_MADVISE = 25
|
||||
IORING_OP_SEND = 26
|
||||
IORING_OP_RECV = 27
|
||||
IORING_OP_OPENAT2 = 28
|
||||
IORING_OP_EPOLL_CTL = 29
|
||||
IORING_OP_SPLICE = 30
|
||||
IORING_OP_PROVIDE_BUFFERS = 31
|
||||
IORING_OP_REMOVE_BUFFERS = 32
|
||||
IORING_OP_TEE = 33
|
||||
IORING_OP_SHUTDOWN = 34
|
||||
IORING_OP_RENAMEAT = 35
|
||||
IORING_OP_UNLINKAT = 36
|
||||
IORING_OP_MKDIRAT = 37
|
||||
IORING_OP_SYMLINKAT = 38
|
||||
IORING_OP_LINKAT = 39
|
||||
IORING_OP_MSG_RING = 40
|
||||
IORING_OP_FSETXATTR = 41
|
||||
IORING_OP_SETXATTR = 42
|
||||
IORING_OP_FGETXATTR = 43
|
||||
IORING_OP_GETXATTR = 44
|
||||
IORING_OP_SOCKET = 45
|
||||
IORING_OP_URING_CMD = 46
|
||||
IORING_OP_SEND_ZC = 47
|
||||
IORING_OP_SENDMSG_ZC = 48
|
||||
|
||||
IORING_CQE_F_BUFFER = 1 << 0
|
||||
IORING_CQE_F_MORE = 1 << 1
|
||||
)
|
||||
|
||||
// io_uring structures
|
||||
// Note: These are simplified structures for the operations we need
|
||||
type ioUring struct {
|
||||
fd int
|
||||
sq *ioUringSq
|
||||
cq *ioUringCq
|
||||
flags uint32
|
||||
ringSize int
|
||||
}
|
||||
|
||||
type ioUringSq struct {
|
||||
head *uint32
|
||||
tail *uint32
|
||||
ringMask *uint32
|
||||
ringEntries *uint32
|
||||
flags *uint32
|
||||
dropped *uint32
|
||||
array *uint32
|
||||
sqes []ioSqringEntry
|
||||
}
|
||||
|
||||
type ioUringCq struct {
|
||||
head *uint32
|
||||
tail *uint32
|
||||
ringMask *uint32
|
||||
ringEntries *uint32
|
||||
overflow *uint32
|
||||
cqes []ioCqringEntry
|
||||
}
|
||||
|
||||
type ioSqringEntry struct {
|
||||
opcode uint8
|
||||
flags uint8
|
||||
ioprio uint16
|
||||
fd int32
|
||||
off uint64
|
||||
addr uint64
|
||||
len uint32
|
||||
userData uint64
|
||||
}
|
||||
|
||||
type ioCqringEntry struct {
|
||||
userData uint64
|
||||
res int32
|
||||
flags uint32
|
||||
}
|
||||
|
||||
type ioUringParams struct {
|
||||
sqEntries uint32
|
||||
cqEntries uint32
|
||||
flags uint32
|
||||
sqThreadCPU uint32
|
||||
sqThreadIdle uint32
|
||||
features uint32
|
||||
wqFd uint32
|
||||
resv [3]uint32
|
||||
sqOff ioSqringOffsets
|
||||
cqOff ioCqringOffsets
|
||||
}
|
||||
|
||||
type ioSqringOffsets struct {
|
||||
head uint32
|
||||
tail uint32
|
||||
ringMask uint32
|
||||
ringEntries uint32
|
||||
flags uint32
|
||||
dropped uint32
|
||||
array uint32
|
||||
resv1 uint32
|
||||
resv2 uint64
|
||||
}
|
||||
|
||||
type ioCqringOffsets struct {
|
||||
head uint32
|
||||
tail uint32
|
||||
ringMask uint32
|
||||
ringEntries uint32
|
||||
overflow uint32
|
||||
cqes uint32
|
||||
flags uint32
|
||||
resv1 uint32
|
||||
resv2 uint64
|
||||
}
|
||||
|
||||
type ioUringCqe struct {
|
||||
userData uint64
|
||||
res int32
|
||||
flags uint32
|
||||
}
|
||||
|
||||
var ioUringEnabled = false
|
||||
|
||||
func init() {
|
||||
if isKernelVersionSupported() {
|
||||
ioUringEnabled = true
|
||||
scsi.RegisterBackingStore(IoUringBackingStorage, newIOUringBackingStore)
|
||||
log.Info("io_uring backing store registered (kernel supports io_uring)")
|
||||
} else {
|
||||
log.Info("io_uring backing store not available (requires Linux 5.1+)")
|
||||
}
|
||||
}
|
||||
|
||||
func isKernelVersionSupported() bool {
|
||||
var uname syscall.Utsname
|
||||
if err := syscall.Uname(&uname); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse kernel version (simplified)
|
||||
// Format is typically "5.15.0-generic"
|
||||
major := int(uname.Release[0] - '0')
|
||||
minor := int(uname.Release[2] - '0')
|
||||
|
||||
if major > MinKernelMajor {
|
||||
return true
|
||||
}
|
||||
if major == MinKernelMajor && minor >= MinKernelMinor {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IOUringBackingStore implements BackingStore using io_uring
|
||||
type IOUringBackingStore struct {
|
||||
scsi.BaseBackingStore
|
||||
file *os.File
|
||||
ring *ioUring
|
||||
queueDepth int
|
||||
|
||||
// Synchronization
|
||||
submitMu sync.Mutex
|
||||
|
||||
// Statistics
|
||||
opsSubmitted uint64
|
||||
opsCompleted uint64
|
||||
}
|
||||
|
||||
func newIOUringBackingStore() (api.BackingStore, error) {
|
||||
return &IOUringBackingStore{
|
||||
BaseBackingStore: scsi.BaseBackingStore{
|
||||
Name: IoUringBackingStorage,
|
||||
DataSize: 0,
|
||||
OflagsSupported: 0,
|
||||
},
|
||||
queueDepth: DefaultQueueDepth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open opens the backing file and initializes io_uring
|
||||
func (bs *IOUringBackingStore) Open(dev *api.SCSILu, path string) error {
|
||||
var mode os.FileMode
|
||||
|
||||
finfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mode = finfo.Mode()
|
||||
|
||||
f, err := os.OpenFile(path, os.O_RDWR|syscall.O_DIRECT, os.ModePerm)
|
||||
if err != nil {
|
||||
// Try without O_DIRECT if not supported
|
||||
f, err = os.OpenFile(path, os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if (mode & os.ModeDevice) != 0 {
|
||||
pos, err := f.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
bs.DataSize = uint64(pos)
|
||||
} else {
|
||||
bs.DataSize = uint64(finfo.Size())
|
||||
}
|
||||
|
||||
bs.file = f
|
||||
|
||||
// Initialize io_uring
|
||||
ring, err := bs.initIOUring()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("failed to initialize io_uring: %v", err)
|
||||
}
|
||||
bs.ring = ring
|
||||
|
||||
log.Infof("io_uring backing store opened: %s (queue depth: %d)", path, bs.queueDepth)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *IOUringBackingStore) initIOUring() (*ioUring, error) {
|
||||
params := &ioUringParams{}
|
||||
|
||||
// Setup io_uring
|
||||
fd, _, errno := syscall.Syscall(425, // __NR_io_uring_setup
|
||||
uintptr(bs.queueDepth),
|
||||
uintptr(unsafe.Pointer(params)),
|
||||
0)
|
||||
|
||||
if errno != 0 {
|
||||
return nil, fmt.Errorf("io_uring_setup failed: %v", errno)
|
||||
}
|
||||
|
||||
ring := &ioUring{
|
||||
fd: int(fd),
|
||||
ringSize: int(params.sqEntries),
|
||||
flags: params.flags,
|
||||
}
|
||||
|
||||
// Map the submission queue ring
|
||||
sqRingSize := params.sqOff.array + params.sqEntries*uint32(unsafe.Sizeof(uint32(0)))
|
||||
cqRingSize := params.cqOff.cqes + params.cqEntries*uint32(unsafe.Sizeof(ioCqringEntry{}))
|
||||
|
||||
if params.features&1 != 0 { // IORING_FEAT_SINGLE_MMAP
|
||||
if cqRingSize > sqRingSize {
|
||||
sqRingSize = cqRingSize
|
||||
}
|
||||
cqRingSize = sqRingSize
|
||||
}
|
||||
|
||||
// mmap submission queue
|
||||
sqPtr, _, errno := syscall.Syscall6(syscall.SYS_MMAP,
|
||||
0,
|
||||
uintptr(sqRingSize),
|
||||
syscall.PROT_READ|syscall.PROT_WRITE,
|
||||
syscall.MAP_SHARED|syscall.MAP_POPULATE,
|
||||
uintptr(fd),
|
||||
uintptr(IORING_OFF_SQ_RING))
|
||||
|
||||
if errno != 0 {
|
||||
syscall.Close(int(fd))
|
||||
return nil, fmt.Errorf("mmap sq ring failed: %v", errno)
|
||||
}
|
||||
|
||||
sqBase := sqPtr
|
||||
|
||||
// mmap completion queue (if not single mmap)
|
||||
var cqPtr uintptr
|
||||
if params.features&1 != 0 {
|
||||
cqPtr = sqPtr
|
||||
} else {
|
||||
cqPtr, _, errno = syscall.Syscall6(syscall.SYS_MMAP,
|
||||
0,
|
||||
uintptr(cqRingSize),
|
||||
syscall.PROT_READ|syscall.PROT_WRITE,
|
||||
syscall.MAP_SHARED|syscall.MAP_POPULATE,
|
||||
uintptr(fd),
|
||||
uintptr(IORING_OFF_CQ_RING))
|
||||
|
||||
if errno != 0 {
|
||||
syscall.Syscall(syscall.SYS_MUNMAP, sqPtr, uintptr(sqRingSize), 0)
|
||||
syscall.Close(int(fd))
|
||||
return nil, fmt.Errorf("mmap cq ring failed: %v", errno)
|
||||
}
|
||||
}
|
||||
|
||||
cqBase := cqPtr
|
||||
|
||||
// mmap SQEs
|
||||
sqeSize := uint32(unsafe.Sizeof(ioSqringEntry{}))
|
||||
sqePtr, _, errno := syscall.Syscall6(syscall.SYS_MMAP,
|
||||
0,
|
||||
uintptr(uint32(bs.queueDepth)*sqeSize),
|
||||
syscall.PROT_READ|syscall.PROT_WRITE,
|
||||
syscall.MAP_SHARED|syscall.MAP_POPULATE,
|
||||
uintptr(fd),
|
||||
uintptr(IORING_OFF_SQES))
|
||||
|
||||
if errno != 0 {
|
||||
syscall.Syscall(syscall.SYS_MUNMAP, sqPtr, uintptr(sqRingSize), 0)
|
||||
if cqPtr != sqPtr {
|
||||
syscall.Syscall(syscall.SYS_MUNMAP, cqPtr, uintptr(cqRingSize), 0)
|
||||
}
|
||||
syscall.Close(int(fd))
|
||||
return nil, fmt.Errorf("mmap sqes failed: %v", errno)
|
||||
}
|
||||
|
||||
// Setup submission queue
|
||||
sq := &ioUringSq{
|
||||
head: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.head))),
|
||||
tail: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.tail))),
|
||||
ringMask: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.ringMask))),
|
||||
ringEntries: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.ringEntries))),
|
||||
flags: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.flags))),
|
||||
dropped: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.dropped))),
|
||||
array: (*uint32)(unsafe.Pointer(sqBase + uintptr(params.sqOff.array))),
|
||||
sqes: make([]ioSqringEntry, bs.queueDepth),
|
||||
}
|
||||
copy(unsafe.Slice((*ioSqringEntry)(unsafe.Pointer(sqePtr)), bs.queueDepth), sq.sqes)
|
||||
|
||||
// Setup completion queue
|
||||
cq := &ioUringCq{
|
||||
head: (*uint32)(unsafe.Pointer(cqBase + uintptr(params.cqOff.head))),
|
||||
tail: (*uint32)(unsafe.Pointer(cqBase + uintptr(params.cqOff.tail))),
|
||||
ringMask: (*uint32)(unsafe.Pointer(cqBase + uintptr(params.cqOff.ringMask))),
|
||||
ringEntries: (*uint32)(unsafe.Pointer(cqBase + uintptr(params.cqOff.ringEntries))),
|
||||
overflow: (*uint32)(unsafe.Pointer(cqBase + uintptr(params.cqOff.overflow))),
|
||||
cqes: make([]ioCqringEntry, params.cqEntries),
|
||||
}
|
||||
copy(unsafe.Slice((*ioCqringEntry)(unsafe.Pointer(cqBase+uintptr(params.cqOff.cqes))), params.cqEntries), cq.cqes)
|
||||
|
||||
ring.sq = sq
|
||||
ring.cq = cq
|
||||
|
||||
return ring, nil
|
||||
}
|
||||
|
||||
// Close closes the backing file and io_uring
|
||||
func (bs *IOUringBackingStore) Close(dev *api.SCSILu) error {
|
||||
if bs.ring != nil {
|
||||
bs.closeIOUring()
|
||||
bs.ring = nil
|
||||
}
|
||||
if bs.file != nil {
|
||||
return bs.file.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *IOUringBackingStore) closeIOUring() {
|
||||
if bs.ring != nil && bs.ring.fd >= 0 {
|
||||
syscall.Close(bs.ring.fd)
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the backing store
|
||||
func (bs *IOUringBackingStore) Init(dev *api.SCSILu, Opts string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exit exits the backing store
|
||||
func (bs *IOUringBackingStore) Exit(dev *api.SCSILu) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns the size of the backing store
|
||||
func (bs *IOUringBackingStore) Size(dev *api.SCSILu) uint64 {
|
||||
return bs.DataSize
|
||||
}
|
||||
|
||||
// Read reads data from the backing file using io_uring
|
||||
func (bs *IOUringBackingStore) Read(offset, tl int64) ([]byte, error) {
|
||||
if bs.file == nil {
|
||||
return nil, fmt.Errorf("backing store is not open")
|
||||
}
|
||||
|
||||
buf := make([]byte, tl)
|
||||
|
||||
// Prepare read operation
|
||||
bs.submitMu.Lock()
|
||||
defer bs.submitMu.Unlock()
|
||||
|
||||
// Get next SQE
|
||||
sqe := bs.getSqe()
|
||||
if sqe == nil {
|
||||
// Ring is full, submit pending operations first
|
||||
if err := bs.submit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqe = bs.getSqe()
|
||||
if sqe == nil {
|
||||
return nil, fmt.Errorf("io_uring queue full")
|
||||
}
|
||||
}
|
||||
|
||||
// Setup read operation
|
||||
*sqe = ioSqringEntry{
|
||||
opcode: IORING_OP_READ,
|
||||
fd: int32(bs.file.Fd()),
|
||||
off: uint64(offset),
|
||||
addr: uint64(uintptr(unsafe.Pointer(&buf[0]))),
|
||||
len: uint32(tl),
|
||||
userData: 1, // 1 = read operation
|
||||
}
|
||||
|
||||
// Submit and wait for completion
|
||||
if err := bs.submitAndWait(1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get completion
|
||||
cqe, err := bs.getCqe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cqe.res < 0 {
|
||||
return nil, fmt.Errorf("read failed: %d", cqe.res)
|
||||
}
|
||||
|
||||
atomic.AddUint64(&bs.opsCompleted, 1)
|
||||
|
||||
return buf[:cqe.res], nil
|
||||
}
|
||||
|
||||
// Write writes data to the backing file using io_uring
|
||||
func (bs *IOUringBackingStore) Write(wbuf []byte, offset int64) error {
|
||||
if bs.file == nil {
|
||||
return fmt.Errorf("backing store is not open")
|
||||
}
|
||||
|
||||
bs.submitMu.Lock()
|
||||
defer bs.submitMu.Unlock()
|
||||
|
||||
// Get next SQE
|
||||
sqe := bs.getSqe()
|
||||
if sqe == nil {
|
||||
if err := bs.submit(); err != nil {
|
||||
return err
|
||||
}
|
||||
sqe = bs.getSqe()
|
||||
if sqe == nil {
|
||||
return fmt.Errorf("io_uring queue full")
|
||||
}
|
||||
}
|
||||
|
||||
// Setup write operation
|
||||
*sqe = ioSqringEntry{
|
||||
opcode: IORING_OP_WRITE,
|
||||
fd: int32(bs.file.Fd()),
|
||||
off: uint64(offset),
|
||||
addr: uint64(uintptr(unsafe.Pointer(&wbuf[0]))),
|
||||
len: uint32(len(wbuf)),
|
||||
userData: 2, // 2 = write operation
|
||||
}
|
||||
|
||||
// Submit and wait for completion
|
||||
if err := bs.submitAndWait(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get completion
|
||||
cqe, err := bs.getCqe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cqe.res < 0 {
|
||||
return fmt.Errorf("write failed: %d", cqe.res)
|
||||
}
|
||||
|
||||
if cqe.res != int32(len(wbuf)) {
|
||||
return fmt.Errorf("short write: %d != %d", cqe.res, len(wbuf))
|
||||
}
|
||||
|
||||
atomic.AddUint64(&bs.opsCompleted, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataSync syncs data to disk using io_uring
|
||||
func (bs *IOUringBackingStore) DataSync(offset, tl int64) error {
|
||||
if bs.file == nil {
|
||||
return fmt.Errorf("backing store is not open")
|
||||
}
|
||||
|
||||
bs.submitMu.Lock()
|
||||
defer bs.submitMu.Unlock()
|
||||
|
||||
sqe := bs.getSqe()
|
||||
if sqe == nil {
|
||||
if err := bs.submit(); err != nil {
|
||||
return err
|
||||
}
|
||||
sqe = bs.getSqe()
|
||||
if sqe == nil {
|
||||
return fmt.Errorf("io_uring queue full")
|
||||
}
|
||||
}
|
||||
|
||||
*sqe = ioSqringEntry{
|
||||
opcode: IORING_OP_FSYNC,
|
||||
fd: int32(bs.file.Fd()),
|
||||
len: IORING_FSYNC_DATASYNC,
|
||||
userData: 3, // 3 = fsync operation
|
||||
}
|
||||
|
||||
if err := bs.submitAndWait(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cqe, err := bs.getCqe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cqe.res < 0 {
|
||||
return fmt.Errorf("fsync failed: %d", cqe.res)
|
||||
}
|
||||
|
||||
atomic.AddUint64(&bs.opsCompleted, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataAdvise provides advice about data access patterns
|
||||
func (bs *IOUringBackingStore) DataAdvise(offset, length int64, advise uint32) error {
|
||||
if bs.file == nil {
|
||||
return fmt.Errorf("backing store is not open")
|
||||
}
|
||||
|
||||
// Use posix_fadvise via syscall
|
||||
_, _, errno := syscall.Syscall6(syscall.SYS_FADVISE64, uintptr(bs.file.Fd()), uintptr(offset), uintptr(length), uintptr(advise), 0, 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmap is a no-op for file-based storage
|
||||
func (bs *IOUringBackingStore) Unmap([]api.UnmapBlockDescriptor) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSqe gets the next available submission queue entry
|
||||
func (bs *IOUringBackingStore) getSqe() *ioSqringEntry {
|
||||
sq := bs.ring.sq
|
||||
tail := atomic.LoadUint32(sq.tail)
|
||||
next := tail + 1
|
||||
|
||||
if next-atomic.LoadUint32(sq.head) > uint32(bs.ring.ringSize) {
|
||||
return nil // Queue is full
|
||||
}
|
||||
|
||||
idx := tail & *sq.ringMask
|
||||
return &sq.sqes[idx]
|
||||
}
|
||||
|
||||
// submit submits pending SQEs to the kernel
|
||||
func (bs *IOUringBackingStore) submit() error {
|
||||
if bs.ring == nil {
|
||||
return fmt.Errorf("io_uring not initialized")
|
||||
}
|
||||
|
||||
// Update tail
|
||||
atomic.StoreUint32(bs.ring.sq.tail, atomic.LoadUint32(bs.ring.sq.tail)+1)
|
||||
|
||||
// Submit using io_uring_enter syscall
|
||||
_, _, errno := syscall.Syscall6(426, // __NR_io_uring_enter
|
||||
uintptr(bs.ring.fd),
|
||||
uintptr(1), // submit 1 operation
|
||||
0, // min complete
|
||||
0, // flags
|
||||
0, 0)
|
||||
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("io_uring_enter failed: %v", errno)
|
||||
}
|
||||
|
||||
atomic.AddUint64(&bs.opsSubmitted, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// submitAndWait submits operations and waits for completions
|
||||
func (bs *IOUringBackingStore) submitAndWait(minComplete uint32) error {
|
||||
if bs.ring == nil {
|
||||
return fmt.Errorf("io_uring not initialized")
|
||||
}
|
||||
|
||||
// Update tail
|
||||
atomic.StoreUint32(bs.ring.sq.tail, atomic.LoadUint32(bs.ring.sq.tail)+1)
|
||||
|
||||
// Submit and wait
|
||||
_, _, errno := syscall.Syscall6(426, // __NR_io_uring_enter
|
||||
uintptr(bs.ring.fd),
|
||||
uintptr(1), // submit 1 operation
|
||||
uintptr(minComplete), // min complete
|
||||
0, // flags
|
||||
0, 0)
|
||||
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("io_uring_enter failed: %v", errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCqe gets a completion queue entry
|
||||
func (bs *IOUringBackingStore) getCqe() (*ioCqringEntry, error) {
|
||||
cq := bs.ring.cq
|
||||
|
||||
// Wait for completion
|
||||
for atomic.LoadUint32(cq.head) == atomic.LoadUint32(cq.tail) {
|
||||
// Spin-wait for completion
|
||||
runtime.Gosched()
|
||||
}
|
||||
|
||||
head := atomic.LoadUint32(cq.head)
|
||||
idx := head & *cq.ringMask
|
||||
cqe := &cq.cqes[idx]
|
||||
|
||||
// Update head
|
||||
atomic.StoreUint32(cq.head, head+1)
|
||||
|
||||
return cqe, nil
|
||||
}
|
||||
|
||||
// Stats returns io_uring statistics
|
||||
func (bs *IOUringBackingStore) Stats() (submitted, completed uint64) {
|
||||
return atomic.LoadUint64(&bs.opsSubmitted), atomic.LoadUint64(&bs.opsCompleted)
|
||||
}
|
||||
|
||||
// Available returns true if io_uring is available on this system
|
||||
func Available() bool {
|
||||
return ioUringEnabled
|
||||
}
|
||||
33
pkg/scsi/backingstore/iouring/iouring_stub.go
Normal file
33
pkg/scsi/backingstore/iouring/iouring_stub.go
Normal file
@@ -0,0 +1,33 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package iouring
|
||||
|
||||
import (
|
||||
// io_uring is not available on non-Linux platforms
|
||||
)
|
||||
|
||||
func init() {
|
||||
// io_uring is not available on non-Linux platforms
|
||||
}
|
||||
|
||||
// Available returns false on non-Linux platforms
|
||||
func Available() bool {
|
||||
return false
|
||||
}
|
||||
@@ -249,21 +249,60 @@ func SCSICDBBufXLength(scb []byte) (int64, bool) {
|
||||
opcode = scb[0]
|
||||
group = SCSICDBGroupID(opcode)
|
||||
|
||||
// Note: group is 0-7, not the CDB length (6, 10, 12, 16)
|
||||
switch group {
|
||||
case CDB_GROUPID_0:
|
||||
length = int64(scb[4])
|
||||
case CDB_GROUPID_2:
|
||||
length = int64(util.GetUnalignedUint16(scb[7:9]))
|
||||
case CDB_GROUPID_3:
|
||||
case 0: // GROUPID_0: 6-byte commands
|
||||
// INQUIRY (0x12) and REQUEST_SENSE (0x03) have Allocation Length in bytes 3-4
|
||||
if opcode == 0x12 || opcode == 0x03 {
|
||||
length = int64(util.GetUnalignedUint16(scb[3:5]))
|
||||
} else {
|
||||
// For other Group 0 commands (READ_6, WRITE_6, etc.),
|
||||
// byte 4 is typically Transfer Length, not Allocation Length.
|
||||
// We should not use it to limit sense data buffer.
|
||||
ok = false
|
||||
}
|
||||
case 1, 2: // GROUPID_1, GROUPID_2: 10-byte commands
|
||||
// PERSISTENT_RESERVE_IN (0x5E) and PERSISTENT_RESERVE_OUT (0x5F)
|
||||
// have Allocation Length in bytes 6-7, not 7-8
|
||||
if opcode == 0x5E || opcode == 0x5F {
|
||||
// Manual BigEndian conversion for PRIN/PROUT
|
||||
length = int64(uint16(scb[6])<<8 | uint16(scb[7]))
|
||||
} else if opcode == 0x28 || opcode == 0x2A || opcode == 0x2E || opcode == 0x35 ||
|
||||
opcode == 0x34 || opcode == 0x2F || opcode == 0x41 || opcode == 0x55 ||
|
||||
opcode == 0x5A || opcode == 0x56 || opcode == 0x57 {
|
||||
// READ_10(0x28), WRITE_10(0x2A), WRITE_VERIFY(0x2E), SYNCHRONIZE_CACHE(0x35),
|
||||
// PRE_FETCH_10(0x34), VERIFY_10(0x2F), WRITE_SAME(0x41), MODE_SELECT_10(0x55),
|
||||
// MODE_SENSE_10(0x5A), RESERVE_10(0x56), RELEASE_10(0x57)
|
||||
// These commands have Transfer Length or Parameter List Length in bytes 7-8,
|
||||
// not Allocation Length.
|
||||
ok = false
|
||||
} else {
|
||||
length = int64(util.GetUnalignedUint16(scb[7:9]))
|
||||
}
|
||||
case 3: // GROUPID_3: variable length
|
||||
if opcode == 0x7F {
|
||||
length = int64(scb[7])
|
||||
} else {
|
||||
ok = false
|
||||
}
|
||||
case CDB_GROUPID_4:
|
||||
length = int64(util.GetUnalignedUint32(scb[6:10]))
|
||||
case CDB_GROUPID_5:
|
||||
length = int64(util.GetUnalignedUint32(scb[10:14]))
|
||||
case 4: // GROUPID_4: 16-byte commands
|
||||
// READ_16(0x88), WRITE_16(0x8A), WRITE_VERIFY_16(0x8E), SYNCHRONIZE_CACHE_16(0x91),
|
||||
// PRE_FETCH_16(0x90), VERIFY_16(0x8F), WRITE_SAME_16(0x93), ORWRITE_16(0x8B)
|
||||
if opcode == 0x88 || opcode == 0x8A || opcode == 0x8E || opcode == 0x91 ||
|
||||
opcode == 0x90 || opcode == 0x8F || opcode == 0x93 || opcode == 0x8B {
|
||||
// These commands have Transfer Length in bytes 6-9, not Allocation Length
|
||||
ok = false
|
||||
} else {
|
||||
length = int64(util.GetUnalignedUint32(scb[6:10]))
|
||||
}
|
||||
case 5: // GROUPID_5: 12-byte commands
|
||||
// READ_12(0xA8), WRITE_12(0xAA), WRITE_VERIFY_12(0xAE), VERIFY_12(0xAF)
|
||||
if opcode == 0xA8 || opcode == 0xAA || opcode == 0xAE || opcode == 0xAF {
|
||||
// These commands have Transfer Length in bytes 10-13, not Allocation Length
|
||||
ok = false
|
||||
} else {
|
||||
length = int64(util.GetUnalignedUint32(scb[10:14]))
|
||||
}
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
|
||||
@@ -30,8 +30,30 @@ func NewSCSILu(bs *config.BackendStorage) (*api.SCSILu, error) {
|
||||
if len(pathinfo) < 2 {
|
||||
return nil, errors.New("invalid device path string")
|
||||
}
|
||||
backendType := pathinfo[0]
|
||||
backendPath := pathinfo[1]
|
||||
|
||||
// Determine backend type: config.BackendType > path prefix > default (file)
|
||||
backendType := "file"
|
||||
backendPath := bs.Path
|
||||
|
||||
if bs.BackendType != "" {
|
||||
// Config specifies backend type explicitly
|
||||
backendType = bs.BackendType
|
||||
backendPath = pathinfo[1]
|
||||
} else {
|
||||
// Infer from path prefix
|
||||
backendType = pathinfo[0]
|
||||
backendPath = pathinfo[1]
|
||||
|
||||
// Validate backend type, default to file if unknown
|
||||
switch backendType {
|
||||
case "file", "iouring", "ceph", "null", "RemBs":
|
||||
// Valid types
|
||||
default:
|
||||
// Unknown type, treat entire path as file path
|
||||
backendType = "file"
|
||||
backendPath = bs.Path
|
||||
}
|
||||
}
|
||||
|
||||
sbc := NewSBCDevice(api.TYPE_DISK)
|
||||
backing, err := NewBackingStore(backendType)
|
||||
@@ -53,7 +75,7 @@ func NewSCSILu(bs *config.BackendStorage) (*api.SCSILu, error) {
|
||||
}
|
||||
lu.Size = backing.Size(lu)
|
||||
lu.DeviceProtocol.InitLu(lu)
|
||||
lu.Attrs.ThinProvisioning = bs.ThinProvisioning
|
||||
lu.Attrs.ThinProvisioning = true
|
||||
lu.Attrs.Online = bs.Online
|
||||
lu.Attrs.Lbppbe = 3
|
||||
return lu, nil
|
||||
|
||||
251
pkg/scsi/sbc.go
251
pkg/scsi/sbc.go
@@ -18,6 +18,7 @@ limitations under the License.
|
||||
package scsi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
@@ -105,17 +106,17 @@ func (sbc SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error {
|
||||
// Vendor uniq - However most apps seem to call for mode page 0
|
||||
//pages = append(pages, api.ModePage{0, 0, []byte{}})
|
||||
// Disconnect page
|
||||
pages = append(pages, api.ModePage{2, 0, 14, []byte{0x80, 0x80, 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
pages = append(pages, api.ModePage{PageCode: 2, SubPageCode: 0, Size: 14, Data: []byte{0x80, 0x80, 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
// Caching Page
|
||||
pages = append(pages, api.ModePage{8, 0, 18, []byte{0x14, 0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0}})
|
||||
pages = append(pages, api.ModePage{PageCode: 8, SubPageCode: 0, Size: 18, Data: []byte{0x14, 0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0}})
|
||||
|
||||
// Control page
|
||||
pages = append(pages, api.ModePage{0x0a, 0, 10, []byte{2, 0x10, 0, 0, 0, 0, 0, 0, 2, 0, 0x08, 0, 0, 0, 0, 0, 0, 0}})
|
||||
pages = append(pages, api.ModePage{PageCode: 0x0a, SubPageCode: 0, Size: 10, Data: []byte{2, 0x10, 0, 0, 0, 0, 0, 0, 2, 0, 0x08, 0, 0, 0, 0, 0, 0, 0}})
|
||||
|
||||
// Control Extensions mode page: TCMOS:1
|
||||
pages = append(pages, api.ModePage{0x0a, 1, 0x1c, []byte{0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
pages = append(pages, api.ModePage{PageCode: 0x0a, SubPageCode: 1, Size: 0x1c, Data: []byte{0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
// Informational Exceptions Control page
|
||||
pages = append(pages, api.ModePage{0x1c, 0, 10, []byte{8, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
pages = append(pages, api.ModePage{PageCode: 0x1c, SubPageCode: 0, Size: 10, Data: []byte{8, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
lu.ModePages = pages
|
||||
mbd := util.MarshalUint32(uint32(0xffffffff))
|
||||
if size := lu.Size >> lu.BlockShift; size>>32 == 0 {
|
||||
@@ -221,6 +222,7 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol {
|
||||
sbc.SCSIDeviceOps[api.WRITE_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_WE_FA|PR_EA_FA|PR_WE_FA|PR_WE_FN)
|
||||
sbc.SCSIDeviceOps[api.WRITE_VERIFY_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
|
||||
sbc.SCSIDeviceOps[api.VERIFY_12] = NewSCSIDeviceOperation(SBCVerify, nil, PR_EA_FA|PR_EA_FN)
|
||||
sbc.SCSIDeviceOps[api.COMPARE_AND_WRITE] = NewSCSIDeviceOperation(SBCCompareAndWrite, nil, PR_EA_FA|PR_EA_FN)
|
||||
|
||||
return sbc
|
||||
}
|
||||
@@ -354,15 +356,17 @@ func SBCUnmap(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
*/
|
||||
func SBCReadWrite(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
var (
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
opcode = api.SCSICommandType(scb[0])
|
||||
lba uint64
|
||||
tl uint32
|
||||
err error
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
opcode = api.SCSICommandType(scb[0])
|
||||
lba uint64
|
||||
tl uint32
|
||||
err error
|
||||
totalBlocks uint64
|
||||
)
|
||||
|
||||
if dev.Attrs.Removable && !dev.Attrs.Online {
|
||||
key = NOT_READY
|
||||
asc = ASC_MEDIUM_NOT_PRESENT
|
||||
@@ -422,21 +426,22 @@ func SBCReadWrite(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
lba = getSCSIReadWriteOffset(scb)
|
||||
tl = getSCSIReadWriteCount(scb)
|
||||
|
||||
// Calculate total blocks
|
||||
totalBlocks = dev.Size >> dev.BlockShift
|
||||
log.Debugf("SBCReadWrite: opcode=0x%x, lba=%d, tl=%d, totalBlocks=%d", opcode, lba, tl, totalBlocks)
|
||||
|
||||
// Verify that we are not doing i/o beyond the end-of-lun
|
||||
if tl != 0 {
|
||||
if lba+uint64(tl) < lba || lba+uint64(tl) > dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense data(ILLEGAL_REQUEST,ASC_LBA_OUT_OF_RANGE) encounter: lba: %d, tl: %d, size: %d", lba, tl, dev.Size>>dev.BlockShift)
|
||||
goto sense
|
||||
}
|
||||
} else {
|
||||
if lba >= dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense data(ILLEGAL_REQUEST,ASC_LBA_OUT_OF_RANGE) encounter: lba: %d, size: %d", lba, dev.Size>>dev.BlockShift)
|
||||
goto sense
|
||||
}
|
||||
// Even when transfer length is 0, we must validate the LBA is within range
|
||||
if lba >= totalBlocks || lba+uint64(tl) < lba || lba+uint64(tl) > totalBlocks {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("SBCReadWrite: LBA out of range (lba=%d, tl=%d, totalBlocks=%d)", lba, tl, totalBlocks)
|
||||
goto sense
|
||||
}
|
||||
|
||||
// If transfer length is 0, return GOOD status immediately (no data to transfer)
|
||||
if tl == 0 {
|
||||
return api.SAMStatGood
|
||||
}
|
||||
|
||||
cmd.Offset = lba << dev.BlockShift
|
||||
@@ -495,6 +500,120 @@ func SBCRelease(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
return api.SAMStatGood
|
||||
}
|
||||
|
||||
/*
|
||||
* SBCCompareAndWrite Implements SCSI COMPARE AND WRITE command (0x89)
|
||||
* The COMPARE AND WRITE command requests that the device server compare the specified
|
||||
* logical block(s) with data transferred from the data-out buffer and, if they match,
|
||||
* write the new data from the data-out buffer to the specified logical block(s).
|
||||
*
|
||||
* Reference : SBC3r35
|
||||
* 5.3 - COMPARE AND WRITE
|
||||
*/
|
||||
func SBCCompareAndWrite(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
var (
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
lba uint64
|
||||
numBlocks uint32
|
||||
offset uint64
|
||||
blockSize uint64
|
||||
totalCompareLen uint64
|
||||
expectedDataLen uint64
|
||||
err error
|
||||
existingData []byte
|
||||
compareData []byte
|
||||
writeData []byte
|
||||
)
|
||||
|
||||
if dev.Attrs.Removable && !dev.Attrs.Online {
|
||||
key = NOT_READY
|
||||
asc = ASC_MEDIUM_NOT_PRESENT
|
||||
goto sense
|
||||
}
|
||||
|
||||
// We only support protection information type 0
|
||||
if scb[1]&0xe0 != 0 {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
goto sense
|
||||
}
|
||||
|
||||
if dev.Attrs.Readonly || dev.Attrs.SWP {
|
||||
key = DATA_PROTECT
|
||||
asc = ASC_WRITE_PROTECT
|
||||
goto sense
|
||||
}
|
||||
|
||||
// Number of logical blocks (one byte: bits 0-7)
|
||||
numBlocks = uint32(scb[13])
|
||||
if numBlocks == 0 {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
goto sense
|
||||
}
|
||||
|
||||
lba = getSCSIReadWriteOffset(scb)
|
||||
|
||||
// Verify that we are not doing i/o beyond the end-of-lun
|
||||
if lba+uint64(numBlocks) < lba || lba+uint64(numBlocks) > dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("COMPARE_AND_WRITE: lba out of range: lba: %d, num: %d, size: %d", lba, numBlocks, dev.Size>>dev.BlockShift)
|
||||
goto sense
|
||||
}
|
||||
|
||||
offset = lba << dev.BlockShift
|
||||
blockSize = uint64(1 << dev.BlockShift)
|
||||
totalCompareLen = uint64(numBlocks) * blockSize
|
||||
|
||||
// Data-out buffer contains: compare data followed by write data
|
||||
// Total length should be 2 * numBlocks * blockSize
|
||||
expectedDataLen = 2 * totalCompareLen
|
||||
if uint64(cmd.OutSDBBuffer.Length) < expectedDataLen {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
log.Warnf("COMPARE_AND_WRITE: data length too short: got %d, expected %d", cmd.OutSDBBuffer.Length, expectedDataLen)
|
||||
goto sense
|
||||
}
|
||||
|
||||
compareData = cmd.OutSDBBuffer.Buffer[:totalCompareLen]
|
||||
writeData = cmd.OutSDBBuffer.Buffer[totalCompareLen:expectedDataLen]
|
||||
|
||||
// Read existing data from storage
|
||||
existingData, err = dev.Storage.Read(int64(offset), int64(totalCompareLen))
|
||||
if err != nil {
|
||||
log.Errorf("COMPARE_AND_WRITE: failed to read data: %v", err)
|
||||
key = MEDIUM_ERROR
|
||||
asc = ASC_READ_ERROR
|
||||
goto sense
|
||||
}
|
||||
|
||||
// Compare data
|
||||
if !bytes.Equal(existingData, compareData) {
|
||||
key = MISCOMPARE
|
||||
asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION
|
||||
log.Warnf("COMPARE_AND_WRITE: data miscompare at LBA %d", lba)
|
||||
goto sense
|
||||
}
|
||||
|
||||
// Data matches, write new data
|
||||
err = dev.Storage.Write(writeData, int64(offset))
|
||||
if err != nil {
|
||||
log.Errorf("COMPARE_AND_WRITE: failed to write data: %v", err)
|
||||
key = MEDIUM_ERROR
|
||||
asc = ASC_WRITE_ERROR
|
||||
goto sense
|
||||
}
|
||||
|
||||
return api.SAMStatGood
|
||||
|
||||
sense:
|
||||
BuildSenseData(cmd, key, asc)
|
||||
return api.SAMStatCheckCondition
|
||||
}
|
||||
|
||||
/*
|
||||
* SBCReadCapacity Implements SCSI READ CAPACITY(10) command
|
||||
* The READ CAPACITY (10) command requests that the device server transfer 8 bytes of parameter data
|
||||
@@ -558,13 +677,14 @@ sense:
|
||||
*/
|
||||
func SBCVerify(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
var (
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
lba uint64
|
||||
tl uint32
|
||||
err error
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
lba uint64
|
||||
tl uint32
|
||||
err error
|
||||
totalBlocks uint64
|
||||
)
|
||||
if dev.Attrs.Removable && !dev.Attrs.Online {
|
||||
key = NOT_READY
|
||||
@@ -579,28 +699,21 @@ func SBCVerify(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
goto sense
|
||||
}
|
||||
|
||||
if scb[1]&0x02 == 0 {
|
||||
// no data compare with the media
|
||||
return api.SAMStatGood
|
||||
}
|
||||
lba = getSCSIReadWriteOffset(scb)
|
||||
tl = getSCSIReadWriteCount(scb)
|
||||
|
||||
// Verify that we are not doing i/o beyond the end-of-lun
|
||||
if tl != 0 {
|
||||
if lba+uint64(tl) < lba || lba+uint64(tl) > dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense: lba: %d, tl: %d, size: %d", lba, tl, dev.Size>>dev.BlockShift)
|
||||
goto sense
|
||||
}
|
||||
} else {
|
||||
if lba >= dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense")
|
||||
goto sense
|
||||
}
|
||||
// Must check LBA range before BYTCHK early return per SBC spec
|
||||
totalBlocks = dev.Size >> dev.BlockShift
|
||||
if lba >= totalBlocks || lba+uint64(tl) < lba || lba+uint64(tl) > totalBlocks {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
goto sense
|
||||
}
|
||||
|
||||
if scb[1]&0x02 == 0 {
|
||||
// BYTCHK=0: no data compare with the media
|
||||
return api.SAMStatGood
|
||||
}
|
||||
|
||||
cmd.Offset = lba << dev.BlockShift
|
||||
@@ -647,12 +760,13 @@ func SBCReadCapacity16(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
|
||||
func SBCGetLbaStatus(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
var (
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
lba uint64
|
||||
tl uint32
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
dev = cmd.Device
|
||||
scb = cmd.SCB
|
||||
lba uint64
|
||||
tl uint32
|
||||
totalBlocks uint64
|
||||
)
|
||||
if dev.Attrs.Removable && !dev.Attrs.Online {
|
||||
key = NOT_READY
|
||||
@@ -674,20 +788,13 @@ func SBCGetLbaStatus(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
lba = getSCSIReadWriteOffset(scb)
|
||||
tl = getSCSIReadWriteCount(scb)
|
||||
// Verify that we are not doing i/o beyond the end-of-lun
|
||||
if tl != 0 {
|
||||
if lba+uint64(tl) < lba || lba+uint64(tl) > dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense: lba: %d, tl: %d, size: %d", lba, tl, dev.Size>>dev.BlockShift)
|
||||
goto sense
|
||||
}
|
||||
} else {
|
||||
if lba >= dev.Size>>dev.BlockShift {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense")
|
||||
goto sense
|
||||
}
|
||||
totalBlocks = dev.Size >> dev.BlockShift
|
||||
log.Warnf("DEBUG: dev.Size=%d, BlockShift=%d, totalBlocks=%d", dev.Size, dev.BlockShift, totalBlocks)
|
||||
if lba >= totalBlocks || lba+uint64(tl) < lba || lba+uint64(tl) > totalBlocks {
|
||||
key = ILLEGAL_REQUEST
|
||||
asc = ASC_LBA_OUT_OF_RANGE
|
||||
log.Warnf("sense: lba: %d, tl: %d, totalBlocks: %d", lba, tl, totalBlocks)
|
||||
goto sense
|
||||
}
|
||||
return api.SAMStatGood
|
||||
sense:
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -46,13 +45,13 @@ func NewSCSITargetService() *SCSITargetService {
|
||||
}
|
||||
|
||||
// GetTargetList get SCSI target list
|
||||
func (s *SCSITargetService) GetTargetList() ([]api.SCSITarget, error) {
|
||||
result := []api.SCSITarget{}
|
||||
func (s *SCSITargetService) GetTargetList() ([]*api.SCSITarget, error) {
|
||||
result := []*api.SCSITarget{}
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
for _, t := range s.Targets {
|
||||
result = append(result, *t)
|
||||
result = append(result, t)
|
||||
}
|
||||
s.mutex.RUnlock()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ func (s *SCSITargetService) AddCommandQueue(tid int, scmd *api.SCSICommand) erro
|
||||
}
|
||||
scmd.Target = target
|
||||
for _, it := range target.ITNexus {
|
||||
if uuid.Equal(it.ID, scmd.ITNexusID) {
|
||||
if it.ID == scmd.ITNexusID {
|
||||
itn = it
|
||||
break
|
||||
}
|
||||
@@ -199,8 +198,9 @@ func BuildSenseData(cmd *api.SCSICommand, key byte, asc SCSISubError) {
|
||||
} else {
|
||||
log.Debugf("cannot calc cbd alloc length. truncate failed")
|
||||
}
|
||||
cmd.Result = key
|
||||
cmd.SenseBuffer = &api.SenseBuffer{senseBuffer.Bytes(), length}
|
||||
// Note: cmd.Result should be set by the caller, not here
|
||||
// The caller should set cmd.Result = api.SAM_STAT_CHECK_CONDITION when returning error
|
||||
cmd.SenseBuffer = &api.SenseBuffer{Buffer: senseBuffer.Bytes(), Length: length}
|
||||
}
|
||||
|
||||
func getSCSIReadWriteOffset(scb []byte) uint64 {
|
||||
@@ -234,6 +234,8 @@ func getSCSIReadWriteCount(scb []byte) uint32 {
|
||||
cnt = uint32(scb[7])<<8 | uint32(scb[8])
|
||||
case api.READ_12, api.WRITE_12, api.VERIFY_12, api.WRITE_VERIFY_12:
|
||||
cnt = binary.BigEndian.Uint32(scb[6:])
|
||||
// Note: READ(12)/WRITE(12) have 32-bit transfer length field, but only use lower 16 bits
|
||||
// per SCSI SBC-3 spec. Zero means zero blocks.
|
||||
case api.READ_16, api.PRE_FETCH_16, api.WRITE_16, api.ORWRITE_16, api.VERIFY_16, api.WRITE_VERIFY_16, api.WRITE_SAME_16, api.SYNCHRONIZE_CACHE_16:
|
||||
cnt = binary.BigEndian.Uint32(scb[10:])
|
||||
case api.COMPARE_AND_WRITE:
|
||||
|
||||
156
pkg/scsi/scsi_perf_test.go
Normal file
156
pkg/scsi/scsi_perf_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package scsi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
)
|
||||
|
||||
// BenchmarkBuildSenseData benchmarks Sense Data building performance
|
||||
func BenchmarkBuildSenseData(b *testing.B) {
|
||||
cmd := &api.SCSICommand{
|
||||
SCB: []byte{0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00},
|
||||
Device: &api.SCSILu{
|
||||
Attrs: api.SCSILuPhyAttribute{
|
||||
SenseFormat: false, // Fixed format
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBuildSenseDataDescriptor benchmarks Descriptor Format Sense Data building performance
|
||||
func BenchmarkBuildSenseDataDescriptor(b *testing.B) {
|
||||
cmd := &api.SCSICommand{
|
||||
SCB: []byte{0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00},
|
||||
Device: &api.SCSILu{
|
||||
Attrs: api.SCSILuPhyAttribute{
|
||||
SenseFormat: true, // Descriptor format
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetSCSIReadWriteOffset benchmarks offset calculation performance
|
||||
func BenchmarkGetSCSIReadWriteOffset(b *testing.B) {
|
||||
scb := []byte{0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00} // READ_10 at LBA 0x1000
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = getSCSIReadWriteOffset(scb)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetSCSIReadWriteCount benchmarks block count calculation performance
|
||||
func BenchmarkGetSCSIReadWriteCount(b *testing.B) {
|
||||
scb := []byte{0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00} // READ_10, 8 blocks
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = getSCSIReadWriteCount(scb)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSCSIDeviceOperation benchmarks SCSI device operation lookup performance
|
||||
func BenchmarkSCSIDeviceOperation(b *testing.B) {
|
||||
lu := &api.SCSILu{}
|
||||
deviceType := api.TYPE_DISK
|
||||
sbc := NewSBCDevice(deviceType)
|
||||
sbc.InitLu(lu)
|
||||
|
||||
opcodes := []api.SCSICommandType{
|
||||
api.INQUIRY, // Must be implemented
|
||||
api.READ_CAPACITY, // Must be implemented
|
||||
api.MODE_SENSE, // Must be implemented
|
||||
api.TEST_UNIT_READY, // Must be implemented
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
opcode := opcodes[i%len(opcodes)]
|
||||
_ = sbc.PerformCommand(int(opcode))
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSCSICommandAlloc benchmarks SCSI command allocation performance
|
||||
func BenchmarkSCSICommandAlloc(b *testing.B) {
|
||||
b.Run("WithAllocation", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = &api.SCSICommand{
|
||||
OpCode: byte(i % 256),
|
||||
SCB: make([]byte, 16),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Reuse", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
cmd := &api.SCSICommand{
|
||||
SCB: make([]byte, 16),
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd.OpCode = byte(i % 256)
|
||||
cmd.Result = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkSCSICommandTypeSwitch benchmarks SCSI command type switching performance
|
||||
func BenchmarkSCSICommandTypeSwitch(b *testing.B) {
|
||||
opcodes := []api.SCSICommandType{
|
||||
api.READ_6, api.READ_10, api.READ_12, api.READ_16,
|
||||
api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16,
|
||||
api.INQUIRY, api.READ_CAPACITY, api.MODE_SENSE,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
opcode := opcodes[i%len(opcodes)]
|
||||
switch opcode {
|
||||
case api.READ_6, api.READ_10, api.READ_12, api.READ_16:
|
||||
// Read operation
|
||||
case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16:
|
||||
// Write operation
|
||||
case api.INQUIRY:
|
||||
// Inquiry
|
||||
case api.READ_CAPACITY:
|
||||
// Read capacity
|
||||
case api.MODE_SENSE:
|
||||
// Mode sense
|
||||
default:
|
||||
// Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ limitations under the License.
|
||||
package scsi
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
type SCSIReservationOperator interface {
|
||||
@@ -101,7 +101,7 @@ func (op *SCSISimpleReservationOperator) GetReservation(tgtName string, devUUID
|
||||
return nil
|
||||
}
|
||||
for _, SCSIRes = range LURes.Reservations {
|
||||
if uuid.Equal(SCSIRes.ITNexusID, ITNexusID) {
|
||||
if SCSIRes.ITNexusID == ITNexusID {
|
||||
return SCSIRes
|
||||
}
|
||||
}
|
||||
|
||||
135
pkg/scsi/spc.go
135
pkg/scsi/spc.go
@@ -22,9 +22,9 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
"github.com/gostor/gotgt/pkg/util"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -228,6 +228,33 @@ func InquiryPage0xB0(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
||||
return buf, pageLength
|
||||
}
|
||||
|
||||
func InquiryPage0xB1(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
||||
var (
|
||||
buf = &bytes.Buffer{}
|
||||
pageLength uint16 = 0x3C // 60 bytes
|
||||
)
|
||||
|
||||
//byte 0
|
||||
if cmd.Device.Attrs.Online {
|
||||
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
||||
} else {
|
||||
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
||||
}
|
||||
//PAGE CODE
|
||||
buf.WriteByte(0xB1)
|
||||
//PAGE LENGTH
|
||||
binary.Write(buf, binary.BigEndian, pageLength)
|
||||
|
||||
// MEDIA ROTATION RATE (bytes 4-5)
|
||||
// 0x0001 = Non-rotating medium (SSD)
|
||||
binary.Write(buf, binary.BigEndian, uint16(0x0001))
|
||||
|
||||
// Reserved bytes (6-63)
|
||||
buf.Write(make([]byte, 58))
|
||||
|
||||
return buf, pageLength
|
||||
}
|
||||
|
||||
func InquiryPage0xB2(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
||||
var (
|
||||
buf = &bytes.Buffer{}
|
||||
@@ -311,6 +338,8 @@ func SPCInquiry(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
buf, _ = InquiryPage0x83(host, cmd)
|
||||
case 0xB0:
|
||||
buf, _ = InquiryPage0xB0(host, cmd)
|
||||
case 0xB1:
|
||||
buf, _ = InquiryPage0xB1(host, cmd)
|
||||
case 0xB2:
|
||||
buf, _ = InquiryPage0xB2(host, cmd)
|
||||
default:
|
||||
@@ -565,7 +594,6 @@ func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
asc = ASC_INVALID_FIELD_IN_CDB
|
||||
data []byte
|
||||
allocLen uint32
|
||||
i uint32
|
||||
)
|
||||
|
||||
if dbd == 0 {
|
||||
@@ -577,16 +605,31 @@ func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
}
|
||||
if mode6 {
|
||||
allocLen = uint32(scb[4])
|
||||
// set header
|
||||
for i = 0; i < 4 && i < allocLen; i++ {
|
||||
data = append(data, 0x00)
|
||||
}
|
||||
// set header (4 bytes)
|
||||
// byte 0: Mode Data Length
|
||||
// byte 1: Medium Type
|
||||
// byte 2: Device-Specific Parameter (DPOFUA=bit4)
|
||||
// byte 3: Block Descriptor Length
|
||||
data = append(data, 0x00) // Mode Data Length (filled later)
|
||||
data = append(data, 0x00) // Medium Type
|
||||
data = append(data, 0x10) // Device-Specific Parameter (DPOFUA=1)
|
||||
data = append(data, 0x00) // Block Descriptor Length (filled later)
|
||||
} else {
|
||||
allocLen = uint32(util.GetUnalignedUint16(scb[7:9]))
|
||||
// set header
|
||||
for i = 0; i < 8 && i < allocLen; i++ {
|
||||
data = append(data, 0x00)
|
||||
}
|
||||
// set header (8 bytes)
|
||||
// byte 0-1: Mode Data Length
|
||||
// byte 2: Medium Type
|
||||
// byte 3: Device-Specific Parameter (DPOFUA=bit4)
|
||||
// byte 4-5: Reserved
|
||||
// byte 6-7: Block Descriptor Length
|
||||
data = append(data, 0x00) // Mode Data Length (MSB, filled later)
|
||||
data = append(data, 0x00) // Mode Data Length (LSB, filled later)
|
||||
data = append(data, 0x00) // Medium Type
|
||||
data = append(data, 0x10) // Device-Specific Parameter (DPOFUA=1)
|
||||
data = append(data, 0x00) // Reserved
|
||||
data = append(data, 0x00) // Reserved
|
||||
data = append(data, 0x00) // Block Descriptor Length (MSB, filled later)
|
||||
data = append(data, 0x00) // Block Descriptor Length (LSB, filled later)
|
||||
}
|
||||
if dbd == 0 {
|
||||
data = append(data, cmd.Device.ModeBlockDescriptor...)
|
||||
@@ -595,12 +638,12 @@ func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
for _, pg := range cmd.Device.ModePages {
|
||||
if pg.SubPageCode == 0 {
|
||||
data = append(data, pg.PageCode)
|
||||
data = append(data, pg.Size)
|
||||
data = append(data, byte(pg.Size))
|
||||
} else {
|
||||
data = append(data, pg.PageCode|0x40)
|
||||
data = append(data, pg.SubPageCode)
|
||||
data = append(data, (pg.Size>>8)&0xff)
|
||||
data = append(data, pg.Size&0xff)
|
||||
data = append(data, byte((pg.Size>>8)&0xff))
|
||||
data = append(data, byte(pg.Size&0xff))
|
||||
}
|
||||
if pctrl == 1 {
|
||||
data = append(data, pg.Data[pg.Size:]...)
|
||||
@@ -621,7 +664,7 @@ func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
}
|
||||
if pg.SubPageCode == 0 {
|
||||
data = append(data, pg.PageCode)
|
||||
data = append(data, pg.Size)
|
||||
data = append(data, byte(pg.Size))
|
||||
if pctrl == 1 {
|
||||
data = append(data, pg.Data[pg.Size:]...)
|
||||
} else {
|
||||
@@ -630,8 +673,8 @@ func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
} else {
|
||||
data = append(data, pg.PageCode|0x40)
|
||||
data = append(data, pg.SubPageCode)
|
||||
data = append(data, (pg.Size>>8)&0xff)
|
||||
data = append(data, pg.Size&0xff)
|
||||
data = append(data, byte((pg.Size>>8)&0xff))
|
||||
data = append(data, byte(pg.Size&0xff))
|
||||
if pctrl == 1 {
|
||||
data = append(data, pg.Data[pg.Size:]...)
|
||||
} else {
|
||||
@@ -700,17 +743,17 @@ func reportOpcodesAll(cmd *api.SCSICommand, rctd int) error {
|
||||
data = append(data, 0x00)
|
||||
// reserved
|
||||
data = append(data, 0x00)
|
||||
// flags : no service action, possibly timeout desc
|
||||
// flags: no service action, possibly timeout desc
|
||||
if rctd != 0 {
|
||||
data = append(data, 0x02)
|
||||
data = append(data, 0x08)
|
||||
} else {
|
||||
data = append(data, 0x00)
|
||||
data = append(data, 0x08)
|
||||
}
|
||||
// cdb length
|
||||
length := getSCSICmdSize(i)
|
||||
data = append(data, 0)
|
||||
data = append(data, length&0xff)
|
||||
// timeout descriptor
|
||||
// timeout descriptor (if rctd is set) - 12 bytes (all zeros)
|
||||
if rctd != 0 {
|
||||
// length == 0x0a
|
||||
data[1] = 0x0a
|
||||
@@ -725,7 +768,53 @@ func reportOpcodesAll(cmd *api.SCSICommand, rctd int) error {
|
||||
}
|
||||
|
||||
func reportOpcodeOne(cmd *api.SCSICommand, rctd int, opcode byte, rsa uint16, serviceAction bool) error {
|
||||
return fmt.Errorf("rsa: %xh, sa:%v not supported", rsa, serviceAction)
|
||||
var data = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
// Support common opcodes that are tested by libiscsi
|
||||
switch api.SCSICommandType(opcode) {
|
||||
case api.READ_6, api.READ_10, api.READ_12, api.READ_16,
|
||||
api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16,
|
||||
api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16,
|
||||
api.INQUIRY, api.TEST_UNIT_READY, api.READ_CAPACITY,
|
||||
api.VERIFY_10, api.VERIFY_12, api.VERIFY_16:
|
||||
// For RCTD=0, libiscsi expects:
|
||||
// data[0:4]: list length
|
||||
// data[4:20]: CDB usage data (16 bytes)
|
||||
// libiscsi reads ctdp from data[1], cdb_length from data[2:4]
|
||||
// and copies data[4:4+cdb_length] to cdb_usage_data
|
||||
//
|
||||
// So we need to format data as:
|
||||
// data[4]: opcode (CDB usage data byte 0)
|
||||
// data[5]: byte 1 with DPO/FUA bits
|
||||
// data[6:20]: remaining CDB usage data bytes
|
||||
|
||||
// CDB usage data (16 bytes) - describes the CDB format
|
||||
cdbUsageData := make([]byte, 16)
|
||||
cdbUsageData[0] = opcode // byte 0: opcode
|
||||
// byte 1: RDPROTECT(7-5) | DPO(4) | FUA(3) | ...
|
||||
// Set DPO(0x10) | FUA(0x08) = 0x18 for READ/WRITE/VERIFY/WRITE_VERIFY
|
||||
if opcode == 0x28 || opcode == 0x2A || opcode == 0x2F || // READ10, WRITE10, VERIFY10
|
||||
opcode == 0xA8 || opcode == 0xAA || opcode == 0xAF || // READ12, WRITE12, VERIFY12
|
||||
opcode == 0x88 || opcode == 0x8A || opcode == 0x8F || // READ16, WRITE16, VERIFY16
|
||||
opcode == 0x2E || opcode == 0xAE || opcode == 0x8E { // WRITE_VERIFY, WRITE_VERIFY_12, WRITE_VERIFY_16
|
||||
cdbUsageData[1] = 0x18 // DPO | FUA
|
||||
}
|
||||
data = append(data, cdbUsageData...)
|
||||
|
||||
// timeout descriptor (if rctd is set) - 12 bytes (all zeros)
|
||||
if rctd != 0 {
|
||||
for n := 0; n < 12; n++ {
|
||||
data = append(data, 0x00)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("opcode: %02xh not supported in report one", opcode)
|
||||
}
|
||||
|
||||
// Update list length (total bytes after the length field)
|
||||
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint32(uint32(len(data)-4)))
|
||||
copy(cmd.InSDBBuffer.Buffer[4:], data[4:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func SPCReportSupportedOperationCodes(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
@@ -799,6 +888,8 @@ func SPCPRReadKeys(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
scsiResOp := GetSCSIReservationOperator()
|
||||
PRGeneration, _ := scsiResOp.GetPRGeneration(tgtName, devUUID)
|
||||
resList := scsiResOp.GetReservationList(tgtName, devUUID)
|
||||
length, _ := SCSICDBBufXLength(cmd.SCB)
|
||||
allocationLength = uint16(length)
|
||||
if allocationLength < 8 {
|
||||
goto sense
|
||||
}
|
||||
@@ -979,7 +1070,7 @@ func SPCPRRegister(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
if ignoreKey || resKey == 0 {
|
||||
if sAResKey != 0 {
|
||||
newRes := &api.SCSIReservation{
|
||||
ID: uuid.NewV1(),
|
||||
ID: uuid.New(),
|
||||
Key: sAResKey,
|
||||
ITNexusID: cmd.ITNexusID,
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ func (s *SCSITargetService) NewSCSITarget(tid int, driverName, name string) (*ap
|
||||
TargetPortGroups: []*api.TargetPortGroup{},
|
||||
ITNexus: make(map[uuid.UUID]*api.ITNexus),
|
||||
}
|
||||
tpg := &api.TargetPortGroup{0, []*api.SCSITargetPort{}}
|
||||
tpg := &api.TargetPortGroup{GroupID: 0, TargetPortGroup: []*api.SCSITargetPort{}}
|
||||
s.Targets = append(s.Targets, target)
|
||||
target.Devices = GetTargetLUNMap(target.Name)
|
||||
target.LUN0 = NewLUN0()
|
||||
@@ -110,7 +110,7 @@ func deviceReserve(cmd *api.SCSICommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !uuid.Equal(lu.ReserveID, uuid.Nil) && uuid.Equal(lu.ReserveID, cmd.ITNexusID) {
|
||||
if lu.ReserveID != uuid.Nil && lu.ReserveID == cmd.ITNexusID {
|
||||
log.Errorf("already reserved %d, %d", lu.ReserveID, cmd.ITNexusID)
|
||||
return fmt.Errorf("already reserved")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user