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 }