New upstream version 1.21
This commit is contained in:
132
sgio/ata.go
Normal file
132
sgio/ata.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/benmcclelland/sgio"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
sgAta16 = 0x85 // ATA PASS-THROUGH(16)
|
||||
sgAta12 = 0xa1 // ATA PASS-THROUGH (12)
|
||||
|
||||
sgAtaProtoNonData = 3 << 1
|
||||
ataUsingLba = 1 << 6
|
||||
|
||||
ataOpStandbyNow1 = 0xe0 // https://wiki.osdev.org/ATA/ATAPI_Power_Management
|
||||
ataOpStandbyNow2 = 0x94 // Retired in ATA4. Did not coexist with ATAPI.
|
||||
)
|
||||
|
||||
func StopAtaDevice(device string, debug bool) error {
|
||||
f, err := openDevice(device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch NewAtaDevice(device, debug).deviceType() {
|
||||
case Jmicron:
|
||||
if err = sendSgio(f, jmicronGetRegisters(), debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if debug {
|
||||
fmt.Println(" issuing standby command")
|
||||
}
|
||||
if err = sendSgio(f, jmicronStandby(), debug); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
if debug {
|
||||
fmt.Println(" issuing standby command")
|
||||
}
|
||||
if err = sendAtaCommand(f, ataOpStandbyNow1, debug); err != nil {
|
||||
if err = sendAtaCommand(f, ataOpStandbyNow2, debug); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close file %s. Error: %s", device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jmicronGetRegisters() []uint8 {
|
||||
cbd := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // len 12
|
||||
cbd[0] = 0xdf
|
||||
cbd[1] = 0x10 // read
|
||||
cbd[4] = 0x01
|
||||
cbd[6] = 0x72
|
||||
cbd[7] = 0x0f
|
||||
cbd[11] = 0xfd
|
||||
return cbd
|
||||
}
|
||||
|
||||
func jmicronStandby() []uint8 {
|
||||
cbd := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // len 12
|
||||
cbd[0] = 0xdf
|
||||
cbd[1] = 0x10
|
||||
cbd[10] = 0xa0 // device port. either 0xa0 or 0xb0
|
||||
cbd[11] = ataOpStandbyNow1
|
||||
return cbd
|
||||
}
|
||||
|
||||
func sendAtaCommand(f *os.File, command uint8, debug bool) error {
|
||||
cbd := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // len 16
|
||||
cbd[0] = sgAta16
|
||||
cbd[1] = sgAtaProtoNonData
|
||||
cbd[13] = ataUsingLba
|
||||
cbd[14] = command
|
||||
return sendSgio(f, cbd, debug)
|
||||
}
|
||||
|
||||
func sendSgio(f *os.File, inqCmdBlk []uint8, debug bool) error {
|
||||
senseBuf := make([]byte, sgio.SENSE_BUF_LEN)
|
||||
ioHdr := &sgio.SgIoHdr{
|
||||
InterfaceID: 'S', // 0 4
|
||||
DxferDirection: SgDxferNone, // 4 4
|
||||
CmdLen: uint8(len(inqCmdBlk)), // 8 1
|
||||
MxSbLen: sgio.SENSE_BUF_LEN, // 9 1
|
||||
Cmdp: &inqCmdBlk[0], // 24 8
|
||||
Sbp: &senseBuf[0], // 32 8
|
||||
Timeout: 0, // 40 4
|
||||
}
|
||||
|
||||
if debug {
|
||||
dumpBytes(inqCmdBlk)
|
||||
}
|
||||
|
||||
if err := sgio.SgioSyscall(f, ioHdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sgio.CheckSense(ioHdr, &senseBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpBytes(p []uint8) {
|
||||
fmt.Print("outgoing cdb: ")
|
||||
for i := range p {
|
||||
fmt.Printf("%02x ", p[i])
|
||||
}
|
||||
fmt.Print("\n")
|
||||
}
|
||||
47
sgio/common.go
Normal file
47
sgio/common.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/benmcclelland/sgio"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const SgDxferNone = -1
|
||||
|
||||
func openDevice(fname string) (*os.File, error) {
|
||||
f, err := os.OpenFile(fname, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var version uint32
|
||||
if (ioctl(f.Fd(), sgio.SG_GET_VERSION_NUM, uintptr(unsafe.Pointer(&version))) != nil) || (version < 30000) {
|
||||
return nil, fmt.Errorf("device does not appear to be an sg device")
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func ioctl(fd, cmd, ptr uintptr) error {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
63
sgio/scsi.go
Normal file
63
sgio/scsi.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2018 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/benmcclelland/sgio"
|
||||
)
|
||||
|
||||
// https://en.wikipedia.org/wiki/SCSI_command
|
||||
const startStopUnit = 0x1b
|
||||
|
||||
func StartStopScsiDevice(device string, powerCondition uint8) error {
|
||||
f, err := openDevice(device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
senseBuf := make([]byte, sgio.SENSE_BUF_LEN)
|
||||
//See https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf - 3.49 START STOP UNIT command
|
||||
inqCmdBlk := []uint8{
|
||||
startStopUnit,
|
||||
0, //Reserved (7 bit) + IMMED
|
||||
0, //Reserved (8 bit)
|
||||
0, //Reserved (4 bit) + POWER CONDITION MODIFER
|
||||
powerCondition << 4, //POWER CONDITION + Reserved (1 bit) + NO_ FLUSH + LOEJ + LOEJ
|
||||
0} //CONTROL
|
||||
ioHdr := &sgio.SgIoHdr{
|
||||
InterfaceID: 'S',
|
||||
DxferDirection: SgDxferNone,
|
||||
Cmdp: &inqCmdBlk[0],
|
||||
CmdLen: uint8(len(inqCmdBlk)),
|
||||
Sbp: &senseBuf[0],
|
||||
MxSbLen: sgio.SENSE_BUF_LEN,
|
||||
}
|
||||
|
||||
if err := sgio.SgioSyscall(f, ioHdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sgio.CheckSense(ioHdr, &senseBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close file %s. Error: %s", device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
127
sgio/type.go
Normal file
127
sgio/type.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// hd-idle - spin down idle hard disks
|
||||
// Copyright (C) 2023 Andoni del Olmo
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
Jmicron = iota
|
||||
Unknown = iota
|
||||
|
||||
sysblock = "/sys/block"
|
||||
|
||||
jmicron = "152d"
|
||||
)
|
||||
|
||||
type AtaDevice struct {
|
||||
device string
|
||||
debug bool
|
||||
fsRoot string
|
||||
}
|
||||
|
||||
func NewAtaDevice(device string, debug bool) AtaDevice {
|
||||
return AtaDevice{
|
||||
device: device,
|
||||
debug: debug,
|
||||
fsRoot: sysblock,
|
||||
}
|
||||
}
|
||||
|
||||
type apt struct {
|
||||
idVendor, idProduct, bcdDevice string
|
||||
}
|
||||
|
||||
func (a apt) isJmicron() bool {
|
||||
if a.idVendor != jmicron {
|
||||
return false
|
||||
}
|
||||
switch a.idProduct {
|
||||
case "2329", "2336", "2338", "2339":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ad AtaDevice) deviceType() int {
|
||||
a, err := ad.identifyDevice(ad.device)
|
||||
if err != nil {
|
||||
if ad.debug {
|
||||
fmt.Println("APT: Unsupported device")
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
if a.isJmicron() {
|
||||
if ad.debug {
|
||||
fmt.Println("APT: Found supported device jmicron")
|
||||
}
|
||||
return Jmicron
|
||||
}
|
||||
|
||||
if ad.debug {
|
||||
fmt.Println("APT: Unsupported device")
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
|
||||
func (ad AtaDevice) identifyDevice(device string) (apt, error) {
|
||||
diskname := strings.Split(device, "/")[2]
|
||||
sysblockdisk := filepath.Join(ad.fsRoot, diskname)
|
||||
idVendor, err := findSystemFile(sysblockdisk, "idVendor")
|
||||
if err != nil {
|
||||
return apt{}, err
|
||||
}
|
||||
idProduct, err := findSystemFile(sysblockdisk, "idProduct")
|
||||
if err != nil {
|
||||
return apt{}, err
|
||||
}
|
||||
bcdDevice, err := findSystemFile(sysblockdisk, "bcdDevice")
|
||||
if err != nil {
|
||||
return apt{}, err
|
||||
}
|
||||
if ad.debug {
|
||||
fmt.Printf("APT: USB ID = 0x%s:0x%s (0x%3s)\n", idVendor, idProduct, bcdDevice)
|
||||
}
|
||||
return apt{
|
||||
idVendor: idVendor,
|
||||
idProduct: idProduct,
|
||||
bcdDevice: bcdDevice,
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func findSystemFile(systemRoot, filename string) (string, error) {
|
||||
_, err := os.ReadFile(filepath.Join(systemRoot, filename))
|
||||
relativeDir := ""
|
||||
var content []byte
|
||||
|
||||
depth := 0
|
||||
for depth < 20 {
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
relativeDir += "/.."
|
||||
content, err = os.ReadFile(systemRoot + relativeDir + "/" + filename)
|
||||
depth++
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("device not found")
|
||||
}
|
||||
80
sgio/type_test.go
Normal file
80
sgio/type_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package sgio
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
tmpDir = "/tmp/hd-idle/ata"
|
||||
)
|
||||
|
||||
func TestAtaDevice_deviceType(t *testing.T) {
|
||||
type fields struct {
|
||||
device string
|
||||
debug bool
|
||||
fsRoot string
|
||||
idVendor, idProduct, bcdDevice string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "find jmicron controller",
|
||||
fields: fields{
|
||||
device: "/dev/sde",
|
||||
debug: true,
|
||||
fsRoot: filepath.Join(tmpDir, "sys", "block"),
|
||||
idVendor: "152d",
|
||||
idProduct: "2339",
|
||||
bcdDevice: "100",
|
||||
},
|
||||
want: Jmicron,
|
||||
},
|
||||
{
|
||||
name: "unknown device",
|
||||
fields: fields{
|
||||
device: "/dev/sde",
|
||||
debug: true,
|
||||
fsRoot: filepath.Join(tmpDir, "sys", "block"),
|
||||
idVendor: "1058",
|
||||
idProduct: "25a3",
|
||||
bcdDevice: "1021",
|
||||
},
|
||||
want: Unknown,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ad := AtaDevice{
|
||||
device: tt.fields.device,
|
||||
debug: tt.fields.debug,
|
||||
fsRoot: tt.fields.fsRoot,
|
||||
}
|
||||
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
infoDir := filepath.Join(tmpDir, "/sys/devices/pci0000:00/0000:00:15.0/usb2/2-2/2-2.3/2-2.3.2")
|
||||
diskname := strings.Split(tt.fields.device, "/")[2]
|
||||
deviceRoot := infoDir + "/2-2.3.2:1.0/host5/target5:0:0/5:0:0:0/block/" + diskname
|
||||
_ = os.MkdirAll(deviceRoot, 0755)
|
||||
_ = os.WriteFile(filepath.Join(infoDir, "idVendor"), []byte(tt.fields.idVendor), 0666)
|
||||
_ = os.WriteFile(filepath.Join(infoDir, "idProduct"), []byte(tt.fields.idProduct), 0666)
|
||||
_ = os.WriteFile(filepath.Join(infoDir, "bcdDevice"), []byte(tt.fields.bcdDevice), 0666)
|
||||
_ = os.MkdirAll(filepath.Join(tmpDir, "/sys/block"), 0755)
|
||||
if err = os.Symlink(deviceRoot, filepath.Join(tmpDir, "/sys/block", diskname)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if got := ad.deviceType(); got != tt.want {
|
||||
t.Errorf("deviceType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user