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 }