github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/smart/scsicommands.go (about)

     1  /*
     2  Copyright 2018 The OpenEBS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  // SCSI command definitions.
    15  
    16  package smart
    17  
    18  import (
    19  	"encoding/binary"
    20  	"fmt"
    21  	"unsafe"
    22  )
    23  
    24  // SCSI commands being used
    25  const (
    26  	SCSIModeSense                 = 0x1a // mode sense command
    27  	SCSIReadCapacity10            = 0x25 // read capacity (10) command
    28  	SCSIReadCapacity16            = 0x9e // read capacity (16) command
    29  	SCSIReadCapacityServiceAction = 0x10 // read capacity (16) service action
    30  	SCSIATAPassThru               = 0x85 // ata passthru command
    31  )
    32  
    33  // SCSI Command Descriptor Block types are the various type of scsi cdbs which are used
    34  // to specify the various parameters or data required to send a particular scsi command
    35  
    36  // CDB6 is an array of 6 byte
    37  type CDB6 [6]byte
    38  
    39  // CDB10 is an array of 10 byte
    40  type CDB10 [10]byte
    41  
    42  // CDB16 is an array of 16 byte
    43  type CDB16 [16]byte
    44  
    45  // getLBSize returns the logical block size of a SCSI device
    46  func (d *SCSIDev) getLBSize() (uint32, error) {
    47  	response := make([]byte, 8)
    48  	// Use cdb10 to send a scsi read capacity command to get the
    49  	// logical block size
    50  	cdb := CDB10{SCSIReadCapacity10}
    51  
    52  	// If sending scsi read capacity scsi command fails then return
    53  	// logical size value 0 with error
    54  	if err := d.sendSCSICDB(cdb[:], &response); err != nil {
    55  		return 0, err
    56  	}
    57  	LBsize := binary.BigEndian.Uint32(response[4:]) // logical block size
    58  
    59  	return LBsize, nil
    60  }
    61  
    62  // runSCSIGen executes SCSI generic commands i.e sgIO commands
    63  func (d *SCSIDev) runSCSIGen(header *sgIOHeader) error {
    64  	// send an scsi generic ioctl command to the given file descriptor,
    65  	// returns err if it fails to send it.
    66  
    67  	if err := Ioctl(uintptr(d.fd), SGIO, uintptr(unsafe.Pointer(header))); err != nil {
    68  		return err
    69  	}
    70  	// See http://www.t10.org/lists/2status.htm for SCSI status codes
    71  	// TODO : Decode the status codes and return descriptive errors
    72  	if header.info&SGInfoOkMask != SGInfoOk {
    73  		err := sgIOErr{
    74  			scsiStatus:   header.status,       // status code returned by an SCSI device
    75  			hostStatus:   header.hostStatus,   // status code returned by a host
    76  			driverStatus: header.driverStatus, // status code returned by a scsi driver
    77  		}
    78  		return err
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  // Error returns error string of error occurred while sending SGIO(scsi generic ioctl)
    85  // to a scsi device using the sgIOErr format
    86  func (s sgIOErr) Error() string {
    87  	return fmt.Sprintf("SCSI status: %#02x, host status: %#02x, driver status: %#02x",
    88  		s.scsiStatus, s.hostStatus, s.driverStatus)
    89  }
    90  
    91  // sendSCSICDB sends a SCSI Command Descriptor Block to the device and writes the response into the
    92  // supplied []byte pointer.
    93  func (d *SCSIDev) sendSCSICDB(cdb []byte, respBuf *[]byte) error {
    94  	senseBuf := make([]byte, 32)
    95  
    96  	// Populate all the required fields of "sg_io_hdr_t" struct while sending
    97  	// scsi command
    98  	header := sgIOHeader{
    99  		interfaceID:    'S',
   100  		dxferDirection: SGDxferFromDev,
   101  		cmdLen:         uint8(len(cdb)),
   102  		mxSBLen:        uint8(len(senseBuf)),
   103  		dxferLen:       uint32(len(*respBuf)),
   104  		dxferp:         uintptr(unsafe.Pointer(&(*respBuf)[0])),
   105  		cmdp:           uintptr(unsafe.Pointer(&cdb[0])),
   106  		sbp:            uintptr(unsafe.Pointer(&senseBuf[0])), // nosec
   107  		timeout:        DefaultTimeout,
   108  	}
   109  
   110  	return d.runSCSIGen(&header)
   111  }
   112  
   113  // modeSense function is used to send a SCSI MODE SENSE(6) command to a device.
   114  // TODO : Implement SCSI MODE SENSE(10) command also
   115  func (d *SCSIDev) modeSense(pageNo uint8, subPageNo uint8, pageCtrl uint8, disableBlockDesc bool) ([]byte, error) {
   116  	respBuf := make([]byte, 64)
   117  	var cdb1 uint8
   118  
   119  	// if disable block descriptor value set to true then set cdb1 value
   120  	// else it is 0
   121  	if disableBlockDesc {
   122  		cdb1 = (1 << 3)
   123  	}
   124  
   125  	// Populate all the required fields of cdb6 to send a scsi mode sense command
   126  	cdb := CDB6{SCSIModeSense}
   127  	cdb[0] = SCSIModeSense
   128  	cdb[1] = cdb1
   129  	cdb[2] = (pageCtrl << 6) | pageNo
   130  	cdb[3] = subPageNo
   131  	cdb[4] = uint8(len(respBuf))
   132  	cdb[5] = 0
   133  
   134  	// return error if sending mode sense command using cdb6 fails
   135  	// TODO: Implement mode sense 10 and mode sense 16 also in order to
   136  	// use them if sending mode sense 6 fails for a particular device for
   137  	// a particular device page
   138  	if err := d.sendSCSICDB(cdb[:], &respBuf); err != nil {
   139  		return respBuf, err
   140  	}
   141  
   142  	return respBuf, nil
   143  }