github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/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  // SGDisk implements a Disk using the Linux SG device
   124  type SGDisk struct {
   125  	f      *os.File
   126  	dev    uint8
   127  	packID uint32
   128  
   129  	// Timeuut is the timeout on a disk operation.
   130  	Timeout time.Duration
   131  }
   132  
   133  // NewSGDisk returns a Disk that uses the Linux SCSI Generic Device.
   134  // It also does an Identify to verify that the target name is a true
   135  // lba48 device.
   136  func NewSGDisk(n string, opt ...SGDiskOpt) (*SGDisk, error) {
   137  	f, err := os.OpenFile(n, os.O_RDWR, 0)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	s := &SGDisk{f: f, Timeout: DefaultTimeout}
   142  	if _, err := s.Identify(); err != nil {
   143  		return nil, err
   144  	}
   145  	for _, o := range opt {
   146  		o(s)
   147  	}
   148  	return s, nil
   149  }
   150  
   151  // Close closes any open FDs.
   152  func (s *SGDisk) Close() error {
   153  	return s.f.Close()
   154  }
   155  
   156  // genCommandDataBlock creates a Command and Data Block used by
   157  // Linux SGDisk.
   158  func (p *packet) genCommandDataBlock() {
   159  	p.command[0] = ata16
   160  	p.command[1] = p.ataType | p.transfer | p.category | p.protocol
   161  	switch {
   162  	case p.dma && p.dataLen != 0:
   163  		p.command[1] |= protoDMA
   164  	case p.dma && p.dataLen == 0:
   165  		p.command[1] |= nonData
   166  	case !p.dma && p.dataLen != 0:
   167  		if p.direction == _SG_DXFER_TO_DEV {
   168  			p.command[1] |= pioOut
   169  		} else {
   170  			p.command[1] |= pioIn
   171  		}
   172  	case !p.dma && p.dataLen == 0:
   173  		p.command[1] |= nonData
   174  	}
   175  	// libata/AHCI workaround: don't demand sense data for IDENTIFY commands
   176  	// We learned this from hdparm.
   177  	if p.dataLen != 0 {
   178  		p.command[2] |= tlenNsect | tlenSectors
   179  		if p.direction == _SG_DXFER_TO_DEV {
   180  			p.command[2] |= tdirTo
   181  		} else {
   182  			p.command[2] |= tdirFrom
   183  		}
   184  	} else {
   185  		p.command[2] = checkCond
   186  	}
   187  	p.command[3] = uint8(p.features >> 8)
   188  	p.command[4] = uint8(p.features)
   189  	p.command[5] = uint8(p.nsect >> 8)
   190  	p.command[6] = uint8(p.nsect)
   191  	p.command[7] = uint8(p.lba >> 8)
   192  	p.command[8] = uint8(p.lba)
   193  	p.command[9] = uint8(p.lba >> 24)
   194  	p.command[10] = uint8(p.lba >> 16)
   195  	p.command[11] = uint8(p.lba >> 40)
   196  	p.command[12] = uint8(p.lba >> 32)
   197  	p.command[13] = p.dev
   198  	p.command[14] = uint8(p.cmd)
   199  }
   200  
   201  func (s *SGDisk) newPacket(cmd Cmd, direction direction, ataType uint8) *packet {
   202  	var p = &packet{}
   203  	// These are invariant across all uses of SGDisk.
   204  	p.interfaceID = 'S'
   205  	p.cmdLen = uint8(len(p.command))
   206  	p.data = uintptr(unsafe.Pointer(&p.block[0]))
   207  	p.sb = uintptr(unsafe.Pointer(&p.status[0]))
   208  	p.cdb = uintptr(unsafe.Pointer(&p.command[0]))
   209  
   210  	// These are determined by the request.
   211  	p.cmd = cmd
   212  	p.dev = s.dev
   213  	p.packID = uint32(s.packID)
   214  	p.direction = direction
   215  	// Go 1.12 appears not to have Milliseconds.
   216  	p.timeout = uint32(s.Timeout.Seconds() * 1000)
   217  
   218  	// These are reasonable defaults which the caller
   219  	// can override.
   220  	p.maxStatusBlockLen = maxStatusBlockLen
   221  	p.iovCount = 0 // is this ever non-zero?
   222  	p.dataLen = uint32(oldSchoolBlockLen)
   223  	p.nsect = 1
   224  	p.ataType = ataType
   225  
   226  	return p
   227  }
   228  
   229  func (s *SGDisk) unlockPacket(password string, admin bool) *packet {
   230  	p := s.newPacket(unix.WIN_SECURITY_UNLOCK, _SG_DXFER_TO_DEV, lba48)
   231  	p.genCommandDataBlock()
   232  	if admin {
   233  		p.block[1] = 1
   234  	}
   235  	copy(p.block[2:], []byte(password))
   236  	return p
   237  }
   238  
   239  // Unlock performs unlock requests for Linux SCSI Generic Disks
   240  func (s *SGDisk) Unlock(password string, admin bool) error {
   241  	p := s.unlockPacket(password, admin)
   242  	if err := s.operate(p); err != nil {
   243  		return err
   244  	}
   245  	return nil
   246  }
   247  
   248  func (s *SGDisk) identifyPacket() *packet {
   249  	p := s.newPacket(unix.WIN_IDENTIFY, _SG_DXFER_FROM_DEV, 0)
   250  	p.genCommandDataBlock()
   251  	return p
   252  }
   253  
   254  // Identify returns identifying information for Linux SCSI Generic Disks.
   255  func (s *SGDisk) Identify() (*Info, error) {
   256  	p := s.identifyPacket()
   257  	if err := s.operate(p); err != nil {
   258  		return nil, err
   259  	}
   260  	return unpackIdentify(p.status, p.block, p.word), nil
   261  }
   262  
   263  // _SG_IO is the ioctl request number for SCSI operations.
   264  const _SG_IO = 0x2285
   265  
   266  func (s *SGDisk) operate(p *packet) error {
   267  	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(s.f.Fd()), _SG_IO, uintptr(unsafe.Pointer(&p.packetHeader)))
   268  	sb := p.status[0]
   269  	if errno != 0 || sb != 0 {
   270  		return &os.PathError{
   271  			Op:   "ioctl SG_IO",
   272  			Path: s.f.Name(),
   273  			Err:  fmt.Errorf("SCSI generic error %v and drive error status %#02x", errno, sb),
   274  		}
   275  	}
   276  	w, err := p.block.toWordBlock()
   277  	if err != nil {
   278  		return &os.PathError{
   279  			Op:   "converting SG_IO block output",
   280  			Path: s.f.Name(),
   281  			Err:  err,
   282  		}
   283  	}
   284  	// the drive must be ata48. The status should show that even if we did not issue an ata48 command.
   285  	/*if err := w.mustLBA(); err != nil {
   286  		return err
   287  	}*/
   288  	p.word = w
   289  	return nil
   290  }
   291  
   292  // SGDiskOpt allows callers of NewSGDisk to set values
   293  type SGDiskOpt func(*SGDisk)
   294  
   295  // WithTimeout returns an SGDiskOpt that allows setting a non-default TimeOut
   296  func WithTimeout(timeout time.Duration) SGDiskOpt {
   297  	return func(s *SGDisk) {
   298  		s.Timeout = timeout
   299  	}
   300  }