Skip to content

Commit

Permalink
Add support for MacOSX
Browse files Browse the repository at this point in the history
Implements #2
  • Loading branch information
Anatol Pomazau committed Feb 18, 2022
1 parent 4d973e3 commit 88ccdb9
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 48 deletions.
39 changes: 39 additions & 0 deletions examples/nvme_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package test

import (
"bytes"
"fmt"
"os/exec"
"testing"

"github.com/anatol/smart.go"
"github.com/stretchr/testify/require"
)

func TestNVMe(t *testing.T) {
path := "disk0"

out, err := exec.Command("smartctl", "-a", path).CombinedOutput()
fmt.Println(string(out))
//require.NoError(t, err) it fails at macosx because of GetLogPage()

dev, err := smart.OpenNVMe(path)
require.NoError(t, err)
defer dev.Close()

c, ns, err := dev.Identify()
require.NoError(t, err)

require.Equal(t, 0x1b36, int(c.VendorID))
require.Equal(t, 0x1af4, int(c.Ssvid))
require.Equal(t, "smarttest", string(bytes.TrimSpace(c.SerialNumber[:])))
require.Equal(t, "QEMU NVMe Ctrl", string(bytes.TrimSpace(c.ModelNumber[:])))
require.Equal(t, 256, int(c.Nn))

require.Len(t, ns, 1)
require.Equal(t, 0x14000, int(ns[0].Nsze))

sm, err := dev.ReadSMART()
require.NoError(t, err)
require.Less(t, uint16(300), sm.Temperature)
}
6 changes: 3 additions & 3 deletions examples/other_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// go:build !linux
//go:build !linux
// +build !linux
// go:build !linux&& !darwin
//go:build !linux && !darwin
// +build !linux,!darwin

package test

Expand Down
File renamed without changes.
27 changes: 23 additions & 4 deletions nvme.go
Original file line number Diff line number Diff line change
Expand Up @@ -1188,10 +1188,29 @@ const (
nvmeLogDeviceSelftest = 0x6
)

type NVMeDevice struct {
fd int
}

func (d *NVMeDevice) Type() string {
return "nvme"
}

func (d *NVMeDevice) Identify() (*NvmeIdentController, []NvmeIdentNamespace, error) {
controller, err := d.readControllerIdentifyData()
if err != nil {
return nil, nil, err
}

var ns []NvmeIdentNamespace
// QEMU has 256 namespaces for some reason, TODO: clarify
for i := 0; i < int(controller.Nn); i++ {
namespace, err := d.readNamespaceIdentifyData(i + 1)
if err != nil {
return nil, nil, err
}
if namespace.Nsze == 0 {
continue
}

ns = append(ns, *namespace)
}

return controller, ns, nil
}
77 changes: 77 additions & 0 deletions nvme_darwin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT

#include "nvme_darwin.h"

#define kIONVMeSMARTUserClientTypeID \
CFUUIDGetConstantUUIDWithBytes(NULL, 0xAA, 0x0F, 0xA6, 0xF9, 0xC2, 0xD6, \
0x45, 0x7F, 0xB1, 0x0B, 0x59, 0xA1, 0x32, \
0x53, 0x29, 0x2F)

#define kIONVMeSMARTInterfaceID \
CFUUIDGetConstantUUIDWithBytes(NULL, 0xcc, 0xd1, 0xdb, 0x19, 0xfd, 0x9a, \
0x4d, 0xaf, 0xbf, 0x95, 0x12, 0x45, 0x4b, \
0x23, 0xa, 0xb6)

// *path is a string that has a format like "disk0".
unsigned int smart_nvme_open_darwin(const char *path, smart_nvme_darwin *nvme) {
// see also https://gist.github.com/AlanQuatermain/250538
SInt32 score = 0;
IOReturn kr;
HRESULT hr;

CFMutableDictionaryRef matcher =
IOBSDNameMatching(kIOMasterPortDefault, 0, path);
nvme->disk = IOServiceGetMatchingService(kIOMasterPortDefault, matcher);

kr = IOCreatePlugInInterfaceForService(
nvme->disk, kIONVMeSMARTUserClientTypeID, kIOCFPlugInInterfaceID,
&nvme->plugin, &score);

printf("err=0x%x system=0x%x subsystem=0x%x code=0x%x\n", kr,
err_get_system(kr), err_get_sub(kr), err_get_code(kr));

if (kr != kIOReturnSuccess)
goto exit1;

hr = (*nvme->plugin)
->QueryInterface(nvme->plugin,
CFUUIDGetUUIDBytes(kIONVMeSMARTInterfaceID),
(void **)nvme->smartIfNVMe);
if (hr != S_OK)
goto exit2;

return 0;

exit2:
IODestroyPlugInInterface(nvme->plugin);
exit1:
IOObjectRelease(nvme->disk);
return -1;
}

unsigned int smart_nvme_identify_darwin(smart_nvme_darwin *nvme, void *buffer,
unsigned int nsid) {
IOReturn err = (*nvme->smartIfNVMe)
->GetIdentifyData(nvme->smartIfNVMe,
(struct nvme_id_ctrl *)buffer, nsid);
if (err)
return ENOSYS;

return 0;
}

unsigned int smart_nvme_readlog_darwin(smart_nvme_darwin *nvme, void *buffer) {
IOReturn err =
(*nvme->smartIfNVMe)
->SMARTReadData(nvme->smartIfNVMe, (struct nvme_smart_log *)buffer);
if (err)
return ENOSYS;

return 0;
}

void smart_nvme_close_darwin(smart_nvme_darwin *nvme) {
(*nvme->smartIfNVMe)->Release(nvme->smartIfNVMe);
IODestroyPlugInInterface(nvme->plugin);
IOObjectRelease(nvme->disk);
}
56 changes: 56 additions & 0 deletions nvme_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package smart

// #cgo darwin LDFLAGS: -framework IOKit -framework CoreFoundation
// #include "nvme_darwin.h"
import "C"

import (
"fmt"
"unsafe"
)

type NVMeDevice struct {
data C.smart_nvme_darwin
}

func OpenNVMe(name string) (*NVMeDevice, error) {
dev := NVMeDevice{}

if res := C.smart_nvme_open_darwin(C.CString(name), &dev.data); res != 0 {
return nil, fmt.Errorf("open darwin device error: 0x%x", res)
}

return &dev, nil
}

func (d *NVMeDevice) Close() error {
C.smart_nvme_close_darwin(&d.data)
return nil
}

func (d *NVMeDevice) ReadSMART() (*NvmeSMARTLog, error) {
var l NvmeSMARTLog
if err := C.smart_nvme_readlog_darwin(&d.data, unsafe.Pointer(&l)); err != 0 {
return nil, fmt.Errorf("smart_nvme_readlog_darwin: %v", err)
}

return &l, nil
}

func (d *NVMeDevice) readControllerIdentifyData() (*NvmeIdentController, error) {
var c NvmeIdentController
if err := C.smart_nvme_identify_darwin(&d.data, unsafe.Pointer(&c), 0); err != 0 {
return nil, fmt.Errorf("smart_nvme_identify_darwin: %v", err)
}

return &c, nil
}

func (d *NVMeDevice) readNamespaceIdentifyData(nsid int) (*NvmeIdentNamespace, error) {
var n NvmeIdentNamespace
if err := C.smart_nvme_identify_darwin(&d.data, unsafe.Pointer(&n), C.uint(nsid)); err != 0 {
return nil, fmt.Errorf("smart_nvme_identify_darwin: %X", err)
}

return &n, nil
}
68 changes: 68 additions & 0 deletions nvme_darwin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOKitLib.h>

#ifdef __cplusplus
extern "C" {
#endif

// TODO: figure out what header contains IONVMeSMARTInterface declaration
typedef struct IONVMeSMARTInterface {
IUNKNOWN_C_GUTS;

UInt16 version;
UInt16 revision;

// NVMe smart data, returns nvme_smart_log structure
IOReturn (*SMARTReadData)(void *interface,
void /* struct nvme_smart_log */ *NVMeSMARTData);

// NVMe IdentifyData, returns nvme_id_ctrl per namespace
IOReturn (*GetIdentifyData)(
void *interface,
void /* struct nvme_id_ctrl */ *NVMeIdentifyControllerStruct,
unsigned int ns);
UInt64 reserved0;
UInt64 reserved1;

// NumDWords Number of dwords for log page data, zero based.
IOReturn (*GetLogPage)(void *interface, void *data, unsigned int logPageId,
unsigned int numDWords);

UInt64 reserved2;
UInt64 reserved3;
UInt64 reserved4;
UInt64 reserved5;
UInt64 reserved6;
UInt64 reserved7;
UInt64 reserved8;
UInt64 reserved9;
UInt64 reserved10;
UInt64 reserved11;
UInt64 reserved12;
UInt64 reserved13;
UInt64 reserved14;
UInt64 reserved15;
UInt64 reserved16;
UInt64 reserved17;
UInt64 reserved18;
UInt64 reserved19;
} IONVMeSMARTInterface;

typedef struct smart_nvme_darwin {
IONVMeSMARTInterface **smartIfNVMe;
IOCFPlugInInterface **plugin;
io_object_t disk;
} smart_nvme_darwin;

unsigned int smart_nvme_open_darwin(const char *path, smart_nvme_darwin *nvme);
unsigned int smart_nvme_identify_darwin(smart_nvme_darwin *nvme, void *buffer,
unsigned int nsid);
unsigned int smart_nvme_readlog_darwin(smart_nvme_darwin *nvme, void *buffer);
void smart_nvme_close_darwin(smart_nvme_darwin *nvme);

#ifdef __cplusplus
}
#endif
69 changes: 34 additions & 35 deletions nvme_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ type nvmePassthruCmd64 struct {
result uint64
}

type NVMeDevice struct {
fd int
}

func OpenNVMe(name string) (*NVMeDevice, error) {
fd, err := unix.Open(name, unix.O_RDWR, 0600)
if err != nil {
Expand All @@ -54,37 +58,6 @@ func (d *NVMeDevice) Close() error {
return unix.Close(d.fd)
}

func (d *NVMeDevice) Identify() (*NvmeIdentController, []NvmeIdentNamespace, error) {
buf := make([]byte, 4096)
if err := nvmeReadIdentify(d.fd, 0, 1, buf); err != nil {
return nil, nil, err
}
var controller NvmeIdentController
if err := binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, &controller); err != nil {
return nil, nil, err
}

var ns []NvmeIdentNamespace
// QEMU has 256 namespaces for some reason, TODO: clarify
for i := 0; i < int(controller.Nn); i++ {
buf2 := make([]byte, 4096)
var n NvmeIdentNamespace
if err := nvmeReadIdentify(d.fd, uint32(i+1), 0, buf2); err != nil {
return nil, nil, err
}
if err := binary.Read(bytes.NewBuffer(buf2), binary.LittleEndian, &n); err != nil {
return nil, nil, err
}
if n.Nsze == 0 {
continue
}

ns = append(ns, n)
}

return &controller, ns, nil
}

func (d *NVMeDevice) ReadSMART() (*NvmeSMARTLog, error) {
buf3 := make([]byte, 512)
if err := nvmeReadLogPage(d.fd, nvmeLogSmartInformation, buf3); err != nil {
Expand Down Expand Up @@ -116,14 +89,40 @@ func nvmeReadLogPage(fd int, logID uint8, buf []byte) error {
return ioctl(uintptr(fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
}

func nvmeReadIdentify(fd int, nsid, cns uint32, data []byte) error {
func (d *NVMeDevice) readIdentifyData(nsid, cns int, data []byte) error {
cmd := nvmePassthruCmd64{
opcode: nvmeAdminIdentify,
nsid: nsid,
nsid: uint32(nsid),
addr: uint64(uintptr(unsafe.Pointer(&data[0]))),
dataLen: uint32(len(data)),
cdw10: cns,
cdw10: uint32(cns),
}

return ioctl(uintptr(fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
return ioctl(uintptr(d.fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
}

func (d *NVMeDevice) readControllerIdentifyData() (*NvmeIdentController, error) {
buf := make([]byte, 4096)
if err := d.readIdentifyData(0, 1, buf); err != nil {
return nil, err
}
var controller NvmeIdentController
if err := binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, &controller); err != nil {
return nil, err
}

return &controller, nil
}

func (d *NVMeDevice) readNamespaceIdentifyData(nsid int) (*NvmeIdentNamespace, error) {
buf := make([]byte, 4096)
if err := d.readIdentifyData(nsid, 0, buf); err != nil {
return nil, err
}
var namespace NvmeIdentNamespace
if err := binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, &namespace); err != nil {
return nil, err
}

return &namespace, nil
}
Loading

0 comments on commit 88ccdb9

Please sign in to comment.