forked from dswarbrick/smart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
megaraid.go
446 lines (365 loc) · 12 KB
/
megaraid.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
// Copyright 2017-18 Daniel Swarbrick. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Broadcom (formerly Avago, LSI) MegaRAID ioctl functions.
// TODO:
// - Improve code comments, refer to in-kernel structs
// - Use newer MR_DCMD_PD_LIST_QUERY if possible
package megaraid
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"unsafe"
"golang.org/x/sys/unix"
"github.com/ccdeer/smart/ata"
"github.com/ccdeer/smart/drivedb"
"github.com/ccdeer/smart/ioctl"
"github.com/ccdeer/smart/scsi"
"github.com/ccdeer/smart/utils"
)
const (
SYSFS_SCSI_HOST_DIR = "/sys/class/scsi_host"
MAX_IOCTL_SGE = 16
MFI_CMD_PD_SCSI_IO = 0x04
MFI_CMD_DCMD = 0x05
MR_DCMD_CTRL_GET_INFO = 0x01010000
MR_DCMD_PD_GET_LIST = 0x02010000 // Obsolete / deprecated command
MR_DCMD_PD_LIST_QUERY = 0x02010100
MFI_FRAME_DIR_NONE = 0x0000
MFI_FRAME_DIR_WRITE = 0x0008
MFI_FRAME_DIR_READ = 0x0010
MFI_FRAME_DIR_BOTH = 0x0018
)
type megasas_sge64 struct {
phys_addr uint32
length uint32
_padding uint32
}
type Iovec struct {
Base uint64 // FIXME: This is not portable to 32-bit platforms!
Len uint64
}
type megasas_dcmd_frame struct {
cmd uint8
reserved_0 uint8
cmd_status uint8
reserved_1 [4]uint8
sge_count uint8
context uint32
pad_0 uint32
flags uint16
timeout uint16
data_xfer_len uint32
opcode uint32
mbox [12]byte // FIXME: This is actually a union of [12]uint8 / [6]uint16 / [3]uint32
sgl megasas_sge64 // FIXME: This is actually a union of megasas_sge64 / megasas_sge32
}
type megasas_pthru_frame struct {
cmd uint8
sense_len uint8
cmd_status uint8
scsi_status uint8
target_id uint8
lun uint8
cdb_len uint8
sge_count uint8
context uint32
pad_0 uint32
flags uint16
timeout uint16
data_xfer_len uint32
sense_buf_phys_addr_lo uint32
sense_buf_phys_addr_hi uint32
cdb [16]byte
sgl megasas_sge64
}
// megasas_iocpacket struct - caution: megasas driver expects packet struct
type megasas_iocpacket struct {
host_no uint16
__pad1 uint16
sgl_off uint32
sge_count uint32
sense_off uint32
sense_len uint32
frame [128]byte // FIXME: actually a union of megasas_header / megasas_pthru_frame / megasas_dcmd_frame
sgl [MAX_IOCTL_SGE]Iovec
}
// Megasas physical device address
type MegasasPDAddress struct {
DeviceId uint16
EnclosureId uint16
EnclosureIndex uint8
SlotNumber uint8
SCSIDevType uint8
ConnectPortBitmap uint8
SASAddr [2]uint64
}
// Holder for megaraid_sas ioctl device
type MegasasIoctl struct {
DeviceMajor uint32
fd int
}
type MegasasDevice struct {
Name string
hostNum uint16
deviceId uint16
ctl *MegasasIoctl
}
var (
// 0xc1944d01 - Beware: cannot use unsafe.Sizeof(megasas_iocpacket{}) due to Go struct padding!
MEGASAS_IOC_FIRMWARE = ioctl.Iowr('M', 1, uintptr(binary.Size(megasas_iocpacket{})))
)
// PackedBytes is a convenience method that will pack a megasas_iocpacket struct in little-endian
// format and return it as a byte slice
func (ioc *megasas_iocpacket) PackedBytes() []byte {
b := new(bytes.Buffer)
binary.Write(b, utils.NativeEndian, ioc)
return b.Bytes()
}
// CreateMegasasIoctl determines the device ID for the MegaRAID SAS ioctl device, creates it
// if necessary, and returns a MegasasIoctl struct to interact with the megaraid_sas driver.
func CreateMegasasIoctl() (MegasasIoctl, error) {
var (
m MegasasIoctl
err error
)
// megaraid_sas driver does not automatically create ioctl device node, so find out the device
// major number and create it.
if file, err := os.Open("/proc/devices"); err == nil {
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if strings.HasSuffix(scanner.Text(), "megaraid_sas_ioctl") {
if _, err := fmt.Sscanf(scanner.Text(), "%d", &m.DeviceMajor); err == nil {
break
}
}
}
if m.DeviceMajor == 0 {
log.Println("Could not determine megaraid major number!")
return m, nil
}
unix.Mknod("/dev/megaraid_sas_ioctl_node", unix.S_IFCHR, int(unix.Mkdev(m.DeviceMajor, 0)))
} else {
return m, err
}
m.fd, err = unix.Open("/dev/megaraid_sas_ioctl_node", unix.O_RDWR, 0600)
if err != nil {
return m, err
}
return m, nil
}
// Close closes the file descriptor of the MegasasIoctl instance
func (m *MegasasIoctl) Close() {
unix.Close(m.fd)
}
// MFI sends a MegaRAID Firmware Interface (MFI) command to the specified host
func (m *MegasasIoctl) MFI(host uint16, opcode uint32, b []byte) error {
ioc := megasas_iocpacket{host_no: host}
// Approximation of C union behaviour
dcmd := (*megasas_dcmd_frame)(unsafe.Pointer(&ioc.frame))
dcmd.cmd = MFI_CMD_DCMD
dcmd.opcode = opcode
dcmd.data_xfer_len = uint32(len(b))
dcmd.sge_count = 1
ioc.sge_count = 1
ioc.sgl_off = uint32(unsafe.Offsetof(dcmd.sgl))
ioc.sgl[0] = Iovec{uint64(uintptr(unsafe.Pointer(&b[0]))), uint64(len(b))}
iocBuf := ioc.PackedBytes()
// Note pointer to first item in iocBuf buffer
if err := ioctl.Ioctl(uintptr(m.fd), MEGASAS_IOC_FIRMWARE, uintptr(unsafe.Pointer(&iocBuf[0]))); err != nil {
return err
}
return nil
}
// PassThru sends a SCSI command to a MegaRAID controller
func (m *MegasasIoctl) PassThru(host uint16, diskNum uint8, cdb []byte, buf []byte, dxfer_dir int) error {
ioc := megasas_iocpacket{host_no: host}
// Approximation of C union behaviour
pthru := (*megasas_pthru_frame)(unsafe.Pointer(&ioc.frame))
pthru.cmd_status = 0xff
pthru.cmd = MFI_CMD_PD_SCSI_IO
pthru.target_id = diskNum
pthru.cdb_len = uint8(len(cdb))
// FIXME: Don't use SG_* here
switch dxfer_dir {
case scsi.SG_DXFER_NONE:
pthru.flags = MFI_FRAME_DIR_NONE
case scsi.SG_DXFER_FROM_DEV:
pthru.flags = MFI_FRAME_DIR_READ
case scsi.SG_DXFER_TO_DEV:
pthru.flags = MFI_FRAME_DIR_WRITE
}
copy(pthru.cdb[:], cdb)
pthru.data_xfer_len = uint32(len(buf))
pthru.sge_count = 1
ioc.sge_count = 1
ioc.sgl_off = uint32(unsafe.Offsetof(pthru.sgl))
ioc.sgl[0] = Iovec{uint64(uintptr(unsafe.Pointer(&buf[0]))), uint64(len(buf))}
iocBuf := ioc.PackedBytes()
// Note pointer to first item in iocBuf buffer
return ioctl.Ioctl(uintptr(m.fd), MEGASAS_IOC_FIRMWARE, uintptr(unsafe.Pointer(&iocBuf[0])))
}
// GetPDList retrieves a list of physical devices attached to the specified host
func (m *MegasasIoctl) GetPDList(host uint16) ([]MegasasPDAddress, error) {
respBuf := make([]byte, 4096)
if err := m.MFI(0, MR_DCMD_PD_GET_LIST, respBuf); err != nil {
log.Println(err)
return nil, err
}
respCount := utils.NativeEndian.Uint32(respBuf[4:])
// Create a device array large enough to hold the specified number of devices
devices := make([]MegasasPDAddress, respCount)
binary.Read(bytes.NewBuffer(respBuf[8:]), utils.NativeEndian, &devices)
return devices, nil
}
// ScanHosts scans system for megaraid_sas controllers and returns a slice of host numbers
func (m *MegasasIoctl) ScanHosts() ([]uint16, error) {
var hosts []uint16
files, err := ioutil.ReadDir(SYSFS_SCSI_HOST_DIR)
if err != nil {
return hosts, err
}
for _, file := range files {
if file.Mode()&os.ModeSymlink != 0 {
b, err := ioutil.ReadFile(filepath.Join(SYSFS_SCSI_HOST_DIR, file.Name(), "proc_name"))
if err != nil {
continue
}
if string(bytes.Trim(b, "\n")) == "megaraid_sas" {
var hostNum uint16
if _, err := fmt.Sscanf(file.Name(), "host%d", &hostNum); err == nil {
hosts = append(hosts, hostNum)
}
}
}
}
return hosts, nil
}
// ScanDevices scans systme for (presumably) SMART-capable devices on all available host adapters
func (m *MegasasIoctl) ScanDevices() []MegasasDevice {
var mdevs []MegasasDevice
hosts, _ := m.ScanHosts()
for _, hostNum := range hosts {
devices, _ := m.GetPDList(hostNum)
for _, pd := range devices {
if pd.SCSIDevType == 0 { // SCSI disk
md := MegasasDevice{
Name: fmt.Sprintf("megaraid%d_%d", hostNum, pd.DeviceId),
hostNum: hostNum,
deviceId: pd.DeviceId,
ctl: m,
}
mdevs = append(mdevs, md)
}
}
}
return mdevs
}
// inquiry fetches a standard SCSI INQUIRY response from device
// TODO: Return error if unsuccessful
func (d *MegasasDevice) inquiry() scsi.InquiryResponse {
var inqBuf scsi.InquiryResponse
cdb := scsi.CDB6{scsi.SCSI_INQUIRY}
binary.BigEndian.PutUint16(cdb[3:], scsi.INQ_REPLY_LEN)
respBuf := make([]byte, 512)
if err := d.ctl.PassThru(d.hostNum, uint8(d.deviceId), cdb[:], respBuf, scsi.SG_DXFER_FROM_DEV); err != nil {
return inqBuf
}
binary.Read(bytes.NewReader(respBuf), utils.NativeEndian, &inqBuf)
return inqBuf
}
func OpenMegasasIoctl(host uint16, diskNum uint8) error {
var respBuf []byte
m, _ := CreateMegasasIoctl()
fmt.Printf("%#v\n", m)
defer m.Close()
md := MegasasDevice{
Name: fmt.Sprintf("megaraid%d_%d", host, diskNum),
hostNum: host,
deviceId: uint16(diskNum),
ctl: &m,
}
fmt.Printf("%#v\n", md)
// Send ATA IDENTIFY command as a CDB16 passthru command
cdb := scsi.CDB16{scsi.SCSI_ATA_PASSTHRU_16}
cdb[1] = 0x08 // ATA protocol (4 << 1, PIO data-in)
cdb[2] = 0x0e // BYT_BLOK = 1, T_LENGTH = 2, T_DIR = 1
cdb[14] = ata.ATA_IDENTIFY_DEVICE // command
respBuf = make([]byte, 512)
if err := m.PassThru(host, diskNum, cdb[:], respBuf, scsi.SG_DXFER_FROM_DEV); err != nil {
return err
}
ident_buf := ata.IdentifyDeviceData{}
binary.Read(bytes.NewBuffer(respBuf), utils.NativeEndian, &ident_buf)
fmt.Println("\nATA IDENTIFY data follows:")
fmt.Printf("Serial Number: %s\n", ident_buf.SerialNumber())
fmt.Printf("Firmware Revision: %s\n", ident_buf.FirmwareRevision())
fmt.Printf("Model Number: %s\n", ident_buf.ModelNumber())
db, err := drivedb.OpenDriveDb("drivedb.yaml")
if err != nil {
return err
}
thisDrive := db.LookupDrive(ident_buf.ModelNumber())
fmt.Printf("Drive DB contains %d entries. Using model: %s\n", len(db.Drives), thisDrive.Family)
// Send ATA SMART READ command as a CDB16 passthru command
cdb = scsi.CDB16{scsi.SCSI_ATA_PASSTHRU_16}
cdb[1] = 0x08 // ATA protocol (4 << 1, PIO data-in)
cdb[2] = 0x0e // BYT_BLOK = 1, T_LENGTH = 2, T_DIR = 1
cdb[4] = ata.SMART_READ_DATA // feature LSB
cdb[10] = 0x4f // low lba_mid
cdb[12] = 0xc2 // low lba_high
cdb[14] = ata.ATA_SMART // command
respBuf = make([]byte, 512)
if err := m.PassThru(host, diskNum, cdb[:], respBuf, scsi.SG_DXFER_FROM_DEV); err != nil {
return err
}
smart := ata.SmartPage{}
binary.Read(bytes.NewBuffer(respBuf[:362]), utils.NativeEndian, &smart)
ata.PrintSMARTPage(smart, thisDrive, os.Stdout)
return nil
}
// Scan system for MegaRAID adapters and their devices
func MegaScan() {
m, _ := CreateMegasasIoctl()
defer m.Close()
hosts, _ := m.ScanHosts()
for _, hostNum := range hosts {
devices, _ := m.GetPDList(hostNum)
fmt.Println("\nEncl. Slot Device Id SAS Address")
for _, pd := range devices {
if pd.SCSIDevType == 0 { // SCSI disk
fmt.Printf("%5d %3d %5d %#x\n", pd.EnclosureId, pd.SlotNumber, pd.DeviceId, pd.SASAddr[0])
}
}
fmt.Println()
for _, pd := range devices {
if pd.SCSIDevType == 0 { // SCSI disk
md := MegasasDevice{
Name: fmt.Sprintf("megaraid%d_%d", hostNum, pd.DeviceId),
hostNum: hostNum,
deviceId: uint16(pd.DeviceId),
ctl: &m,
}
fmt.Printf("diskNum: %d INQUIRY data: %s\n", pd.DeviceId, md.inquiry())
}
}
}
}