github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/mount/scuzz/sg_linux.go (about)

     1  // Copyright 2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package scuzz
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"time"
    11  	"unsafe"
    12  
    13  	"golang.org/x/sys/unix"
    14  )
    15  
    16  // SGDisk is the Linux SCSI Generic interface to SCSI/SATA devices.
    17  // Control is achieved by ioctls on an fd.
    18  // SG is extremely low level, requiring the assembly of Command and Data Blocks,
    19  // and occasionaly the disassembly of Status Blocks.
    20  //
    21  // SG can operate with any version of SCSI or ATA, starting from ATA1 to the present.
    22  // ATA packets became "16-bits wide and 64-bit aware in ATA6 standard in 2003.
    23  // Block addresses in this standard are 48 bits.
    24  // In our usage of SG on Linux, we only use ATA16 and LBA48.
    25  //
    26  // We figure that a standard defined in 2003 is
    27  // fairly universal now, and we don't care about
    28  // hardware older than that.
    29  //
    30  // In Linux, requests to SG are defined by a packet header, used by the kernel;
    31  // a Command and Data Block (cdb), a serialized opcode header for the disk;
    32  // and a block, always 512 bytes, containing data.
    33  //
    34  // We show the serialized format of an ATA16 packet below.
    35  // In this layout, following common nomenclature,
    36  // lob is low order byte, hob is high order byte.
    37  // Why is it done this way? In earlier ATA packets,
    38  // serialized over an 8 bit bus, the LBA was 3 bytes.
    39  // It seems when they doubled the bus, and doubled other
    40  // fields, they put the extra bytes "beside" the existing bytes,
    41  // with the result shown below.
    42  //
    43  // The first 3 bytes of the CDB are information about the request,
    44  // and the last 13 bytes are generic information.
    45  // Command and Data Block layout:
    46  // cdb[ 3] = hob_feat
    47  // cdb[ 4] = feat
    48  // cdb[ 5] = hob_nsect
    49  // cdb[ 6] = nsect
    50  // cdb[ 7] = hob_lbal
    51  // cdb[ 8] = lbal
    52  // cdb[ 9] = hob_lbam
    53  // cdb[10] = lbam
    54  // cdb[11] = hob_lbah
    55  // cdb[12] = lbah
    56  // cdb[13] = device
    57  // cdb[14] = command
    58  // Further, there is a direction which can be to, from, or none.
    59  
    60  // packetHeader is the Linux SCSI Generic driver version 3 header structure, or
    61  // sg_io_hdr_t in some code bases.
    62  //
    63  // A pointer to this struct must be passed to the SG_IO ioctl.
    64  //
    65  // Note that some pointers are not word-aligned, i.e. the
    66  // compiler will insert padding; this struct is larger than
    67  // the sum of its parts. This struct has some information
    68  // also contained in the Command and Data Block.
    69  type packetHeader struct {
    70  	interfaceID       int32
    71  	direction         direction
    72  	cmdLen            uint8
    73  	maxStatusBlockLen uint8
    74  	iovCount          uint16
    75  	dataLen           uint32
    76  	data              uintptr
    77  	cdb               uintptr
    78  	sb                uintptr
    79  	timeout           uint32
    80  	flags             uint32
    81  	packID            uint32
    82  	usrPtr            uintptr
    83  	status            uint8
    84  	maskedStatus      uint8
    85  	msgStatus         uint8
    86  	sbLen             uint8
    87  	hostStatus        uint16
    88  	driverStatus      uint16
    89  	resID             int32
    90  	duration          uint32
    91  	info              uint32
    92  }
    93  
    94  // packet contains the packetHeader and other information.
    95  type packet struct {
    96  	packetHeader
    97  
    98  	// This is additional, per-request-type information
    99  	// needed to create a command and data block.
   100  	// It is assembled from both the Disk and the request type.
   101  	ataType  uint8 // almost always lba48
   102  	transfer uint8
   103  	category uint8
   104  	protocol uint8
   105  	features uint16
   106  	cmd      Cmd
   107  	dev      uint8
   108  	lba      uint64
   109  	nsect    uint16
   110  	dma      bool
   111  
   112  	// There are pointers in the packetHeader to this data.
   113  	//
   114  	// We maintain them here to ensure they don't
   115  	// get garbage collected, as the packetHeader only
   116  	// contains uintptrs to refer to them.
   117  	command commandDataBlock
   118  	status  statusBlock
   119  	block   dataBlock
   120  	word    wordBlock
   121  }
   122  
   123  type diskFile interface {
   124  	Close() error
   125  	Name() string
   126  	Fd() uintptr
   127  }
   128  
   129  // SGDisk implements a Disk using the Linux SG device
   130  type SGDisk struct {
   131  	f      diskFile
   132  	dev    uint8
   133  	packID uint32
   134  
   135  	// Timeuut is the timeout on a disk operation.
   136  	Timeout time.Duration
   137  }
   138  
   139  // NewSGDisk returns a Disk that uses the Linux SCSI Generic Device.
   140  // It also does an Identify to verify that the target name is a true
   141  // lba48 device.
   142  func NewSGDisk(n string, opt ...SGDiskOpt) (*SGDisk, error) {
   143  	f, err := os.OpenFile(n, os.O_RDWR, 0)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	return NewSGDiskFromFile(f, opt...)
   148  }
   149  
   150  func NewSGDiskFromFile(f diskFile, opt ...SGDiskOpt) (*SGDisk, error) {
   151  	s := &SGDisk{f: f, Timeout: DefaultTimeout}
   152  	if _, err := s.Identify(); err != nil {
   153  		return nil, err
   154  	}
   155  	for _, o := range opt {
   156  		o(s)
   157  	}
   158  	return s, nil
   159  }
   160  
   161  // Close closes any open FDs.
   162  func (s *SGDisk) Close() error {
   163  	return s.f.Close()
   164  }
   165  
   166  // genCommandDataBlock creates a Command and Data Block used by
   167  // Linux SGDisk.
   168  func (p *packet) genCommandDataBlock() {
   169  	p.command[0] = ata16
   170  	p.command[1] = p.ataType | p.transfer | p.category | p.protocol
   171  	switch {
   172  	case p.dma && p.dataLen != 0:
   173  		p.command[1] |= protoDMA
   174  	case p.dma && p.dataLen == 0:
   175  		p.command[1] |= nonData
   176  	case !p.dma && p.dataLen != 0:
   177  		if p.direction == _SG_DXFER_TO_DEV {
   178  			p.command[1] |= pioOut
   179  		} else {
   180  			p.command[1] |= pioIn
   181  		}
   182  	case !p.dma && p.dataLen == 0:
   183  		p.command[1] |= nonData
   184  	}
   185  	// libata/AHCI workaround: don't demand sense data for IDENTIFY commands
   186  	// We learned this from hdparm.
   187  	if p.dataLen != 0 {
   188  		p.command[2] |= tlenNsect | tlenSectors
   189  		if p.direction == _SG_DXFER_TO_DEV {
   190  			p.command[2] |= tdirTo
   191  		} else {
   192  			p.command[2] |= tdirFrom
   193  		}
   194  	} else {
   195  		p.command[2] = checkCond
   196  	}
   197  	p.command[3] = uint8(p.features >> 8)
   198  	p.command[4] = uint8(p.features)
   199  	p.command[5] = uint8(p.nsect >> 8)
   200  	p.command[6] = uint8(p.nsect)
   201  	p.command[7] = uint8(p.lba >> 8)
   202  	p.command[8] = uint8(p.lba)
   203  	p.command[9] = uint8(p.lba >> 24)
   204  	p.command[10] = uint8(p.lba >> 16)
   205  	p.command[11] = uint8(p.lba >> 40)
   206  	p.command[12] = uint8(p.lba >> 32)
   207  	p.command[13] = p.dev
   208  	p.command[14] = uint8(p.cmd)
   209  }
   210  
   211  func (s *SGDisk) newPacket(cmd Cmd, direction direction, ataType uint8) *packet {
   212  	p := &packet{}
   213  	// These are invariant across all uses of SGDisk.
   214  	p.interfaceID = 'S'
   215  	p.cmdLen = uint8(len(p.command))
   216  	p.data = uintptr(unsafe.Pointer(&p.block[0]))
   217  	p.sb = uintptr(unsafe.Pointer(&p.status[0]))
   218  	p.cdb = uintptr(unsafe.Pointer(&p.command[0]))
   219  
   220  	// These are determined by the request.
   221  	p.cmd = cmd
   222  	p.dev = s.dev
   223  	p.packID = uint32(s.packID)
   224  	p.direction = direction
   225  	// Go 1.12 appears not to have Milliseconds.
   226  	p.timeout = uint32(s.Timeout.Seconds() * 1000)
   227  
   228  	// These are reasonable defaults which the caller
   229  	// can override.
   230  	p.maxStatusBlockLen = maxStatusBlockLen
   231  	p.iovCount = 0 // is this ever non-zero?
   232  	p.dataLen = uint32(oldSchoolBlockLen)
   233  	p.nsect = 1
   234  	p.ataType = ataType
   235  
   236  	return p
   237  }
   238  
   239  func (s *SGDisk) unlockPacket(password string, admin bool) *packet {
   240  	p := s.newPacket(unix.WIN_SECURITY_UNLOCK, _SG_DXFER_TO_DEV, lba48)
   241  	p.genCommandDataBlock()
   242  	if admin {
   243  		p.block[1] = 1
   244  	}
   245  	copy(p.block[2:], []byte(password))
   246  	return p
   247  }
   248  
   249  // Unlock performs unlock requests for Linux SCSI Generic Disks
   250  func (s *SGDisk) Unlock(password string, admin bool) error {
   251  	p := s.unlockPacket(password, admin)
   252  	if err := s.operate(p); err != nil {
   253  		return err
   254  	}
   255  	return nil
   256  }
   257  
   258  func (s *SGDisk) identifyPacket() *packet {
   259  	p := s.newPacket(unix.WIN_IDENTIFY, _SG_DXFER_FROM_DEV, 0)
   260  	p.genCommandDataBlock()
   261  	return p
   262  }
   263  
   264  // Identify returns identifying information for Linux SCSI Generic Disks.
   265  func (s *SGDisk) Identify() (*Info, error) {
   266  	p := s.identifyPacket()
   267  	if err := s.operate(p); err != nil {
   268  		return nil, err
   269  	}
   270  	return unpackIdentify(p.status, p.block, p.word), nil
   271  }
   272  
   273  // _SG_IO is the ioctl request number for SCSI operations.
   274  const _SG_IO = 0x2285
   275  
   276  func (s *SGDisk) operate(p *packet) error {
   277  	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(s.f.Fd()), _SG_IO, uintptr(unsafe.Pointer(&p.packetHeader)))
   278  	sb := p.status[0]
   279  	if errno != 0 || sb != 0 {
   280  		return &os.PathError{
   281  			Op:   "ioctl SG_IO",
   282  			Path: s.f.Name(),
   283  			Err:  fmt.Errorf("SCSI generic error %v and drive error status %#02x", errno, sb),
   284  		}
   285  	}
   286  	w, err := p.block.toWordBlock()
   287  	if err != nil {
   288  		return &os.PathError{
   289  			Op:   "converting SG_IO block output",
   290  			Path: s.f.Name(),
   291  			Err:  err,
   292  		}
   293  	}
   294  	// the drive must be ata48. The status should show that even if we did not issue an ata48 command.
   295  	/*if err := w.mustLBA(); err != nil {
   296  		return err
   297  	}*/
   298  	p.word = w
   299  	return nil
   300  }
   301  
   302  // SGDiskOpt allows callers of NewSGDisk to set values
   303  type SGDiskOpt func(*SGDisk)
   304  
   305  // WithTimeout returns an SGDiskOpt that allows setting a non-default TimeOut
   306  func WithTimeout(timeout time.Duration) SGDiskOpt {
   307  	return func(s *SGDisk) {
   308  		s.Timeout = timeout
   309  	}
   310  }