github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/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 a PacketHeader for the SG device.
    61  // A pointer to this struct must be passed to the ioctl.
    62  // Note that some pointers are not word-aligned, i.e. the
    63  // compiler will insert padding; this struct is larger than
    64  // the sum of its parts. This struct has some information
    65  // also contained in the Command and Data Block.
    66  type packetHeader struct {
    67  	interfaceID       int32
    68  	direction         direction
    69  	cmdLen            uint8
    70  	maxStatusBlockLen uint8
    71  	iovCount          uint16
    72  	dataLen           uint32
    73  	data              uintptr
    74  	cdb               uintptr
    75  	sb                uintptr
    76  	timeout           uint32
    77  	flags             uint32
    78  	packID            uint32
    79  	usrPtr            uintptr
    80  	status            uint8
    81  	maskedStatus      uint8
    82  	msgStatus         uint8
    83  	sbLen             uint8
    84  	hostStatus        uint16
    85  	driverStatus      uint16
    86  	resID             int32
    87  	duration          uint32
    88  	info              uint32
    89  }
    90  
    91  // packet contains the packetHeader and other information.
    92  type packet struct {
    93  	packetHeader
    94  
    95  	// This is additional, per-request-type information
    96  	// needed to create a command and data block.
    97  	// It is assembled from both the Disk and the request type.
    98  	ataType  uint8 // almost always lba48
    99  	transfer uint8
   100  	category uint8
   101  	protocol uint8
   102  	features uint16
   103  	cmd      Cmd
   104  	dev      uint8
   105  	lba      uint64
   106  	nsect    uint16
   107  	dma      bool
   108  
   109  	// There are pointers in the packetHeader to this data.
   110  	// We maintain them here to ensure they don't
   111  	// get garbage collected, as the packetHeader only
   112  	// contains uintptrs to refer to them.
   113  	command commandDataBlock
   114  	status  statusBlock
   115  	block   dataBlock
   116  	word    wordBlock
   117  }
   118  
   119  // SGDisk implements a Disk using the Linux SG device
   120  type SGDisk struct {
   121  	f      *os.File
   122  	dev    uint8
   123  	packID uint32
   124  
   125  	// Timeuut is the timeout on a disk operation.
   126  	Timeout time.Duration
   127  }
   128  
   129  // NewSGDisk returns a Disk that uses the Linux SCSI Generic Device.
   130  // It also does an Identify to verify that the target name is a true
   131  // lba48 device.
   132  func NewSGDisk(n string, opt ...SGDiskOpt) (Disk, error) {
   133  	f, err := os.OpenFile(n, os.O_RDWR, 0)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	s := &SGDisk{f: f, Timeout: DefaultTimeout}
   138  	if _, err := s.Identify(); err != nil {
   139  		return nil, err
   140  	}
   141  	for _, o := range opt {
   142  		o(s)
   143  	}
   144  	return s, nil
   145  }
   146  
   147  // genCommandDataBlock creates a Command and Data Block used by
   148  // Linux SGDisk.
   149  func (p *packet) genCommandDataBlock() {
   150  	p.command[0] = ata16
   151  	p.command[1] = p.ataType | p.transfer | p.category | p.protocol
   152  	switch {
   153  	case p.dma && p.dataLen != 0:
   154  		p.command[1] |= protoDMA
   155  	case p.dma && p.dataLen == 0:
   156  		p.command[1] |= nonData
   157  	case !p.dma && p.dataLen != 0:
   158  		if p.direction == to {
   159  			p.command[1] |= pioOut
   160  		} else {
   161  			p.command[1] |= pioIn
   162  		}
   163  	case !p.dma && p.dataLen == 0:
   164  		p.command[1] |= nonData
   165  	}
   166  	// libata/AHCI workaround: don't demand sense data for IDENTIFY commands
   167  	// We learned this from hdparm.
   168  	if p.dataLen != 0 {
   169  		p.command[2] |= tlenNsect | tlenSectors
   170  		if p.direction == to {
   171  			p.command[2] |= tdirTo
   172  		} else {
   173  			p.command[2] |= tdirFrom
   174  		}
   175  	} else {
   176  		p.command[2] = checkCond
   177  	}
   178  	p.command[3] = uint8(p.features >> 8)
   179  	p.command[4] = uint8(p.features)
   180  	p.command[5] = uint8(p.nsect >> 8)
   181  	p.command[6] = uint8(p.nsect)
   182  	p.command[7] = uint8(p.lba >> 8)
   183  	p.command[8] = uint8(p.lba)
   184  	p.command[9] = uint8(p.lba >> 24)
   185  	p.command[10] = uint8(p.lba >> 16)
   186  	p.command[11] = uint8(p.lba >> 40)
   187  	p.command[12] = uint8(p.lba >> 32)
   188  	p.command[13] = p.dev
   189  	p.command[14] = uint8(p.cmd)
   190  }
   191  
   192  func (s *SGDisk) newPacket(cmd Cmd, direction direction, ataType uint8) *packet {
   193  	var p = &packet{}
   194  	// These are invariant across all uses of SGDisk.
   195  	p.interfaceID = 'S'
   196  	p.cmdLen = ata16Len
   197  	p.data = uintptr(unsafe.Pointer(&p.block[0]))
   198  	p.sb = uintptr(unsafe.Pointer(&p.status[0]))
   199  	p.cdb = uintptr(unsafe.Pointer(&p.command[0]))
   200  
   201  	// These are determined by the request.
   202  	p.cmd = cmd
   203  	p.dev = s.dev
   204  	p.packID = uint32(s.packID)
   205  	p.direction = direction
   206  	// Go 1.12 appears not to have Milliseconds.
   207  	p.timeout = uint32(s.Timeout.Seconds() * 1000)
   208  
   209  	// These are reasonable defaults which the caller
   210  	// can override.
   211  	p.maxStatusBlockLen = maxStatusBlockLen
   212  	p.iovCount = 0 // is this ever non-zero?
   213  	p.dataLen = uint32(oldSchoolBlockLen)
   214  	p.nsect = 1
   215  	p.ataType = ataType
   216  
   217  	return p
   218  }
   219  
   220  func (s *SGDisk) unlockPacket(password string, master bool) *packet {
   221  	p := s.newPacket(securityUnlock, to, lba48)
   222  	p.genCommandDataBlock()
   223  	if master {
   224  		p.block[1] = 1
   225  	}
   226  	copy(p.block[2:], []byte(password))
   227  	return p
   228  }
   229  
   230  // Unlock performs unlock requests for Linux SCSI Generic Disks
   231  func (s *SGDisk) Unlock(password string, master bool) error {
   232  	p := s.unlockPacket(password, master)
   233  	if err := s.operate(p); err != nil {
   234  		return err
   235  	}
   236  	return nil
   237  
   238  }
   239  
   240  func (s *SGDisk) identifyPacket() *packet {
   241  	p := s.newPacket(identify, from, 0)
   242  	p.genCommandDataBlock()
   243  	return p
   244  }
   245  
   246  // Identify returns identifying information for Linux SCSI Generic Disks.
   247  func (s *SGDisk) Identify() (*Info, error) {
   248  	p := s.identifyPacket()
   249  	if err := s.operate(p); err != nil {
   250  		return nil, err
   251  	}
   252  	return unpackIdentify(p.status, p.word)
   253  }
   254  
   255  func (s *SGDisk) operate(p *packet) error {
   256  	_, _, uerr := unix.Syscall(unix.SYS_IOCTL, uintptr(s.f.Fd()), uintptr(p.cmd), uintptr(unsafe.Pointer(&p.packetHeader)))
   257  	sb := p.status[0]
   258  	if uerr != 0 || sb != 0 {
   259  		return fmt.Errorf("SCSI generic error %v: drive error status: %#02x", uerr, sb)
   260  	}
   261  	w, err := p.block.toWordBlock()
   262  	if err != nil {
   263  		return err
   264  	}
   265  	// the drive must be ata48. The status should show that even if we did not issue an ata48 command.
   266  	if err := w.mustLBA(); err != nil {
   267  		return err
   268  	}
   269  	p.word = w
   270  	return nil
   271  }
   272  
   273  // SGDiskOpt allows callers of NewSGDisk to set values
   274  type SGDiskOpt func(*SGDisk)
   275  
   276  // WithTimeout returns an SGDiskOpt that allows setting a non-default TimeOut
   277  func WithTimeout(timeout time.Duration) SGDiskOpt {
   278  	return func(s *SGDisk) {
   279  		s.Timeout = timeout
   280  	}
   281  }