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 }