Compare commits

...

11 Commits
v0.1 ... master

Author SHA1 Message Date
Sergey Kiselev b1b291b1b2 SATA: implement historical standard ceiling for legacy drive links 3 months ago
Sergey Kiselev 6114f03313 Identity: implement native libcam versioning and path inquiry tracing 3 months ago
Sergey Kiselev 8d9b050dd9 Identity: implement generic error logging and permission checks 3 months ago
Sergey Kiselev b8569c9514 ATA/SATA: cross-verify logical & transport layers for robust versioning 3 months ago
Sergey Kiselev 17fa6c2c04 Fix: enforce strict backward compatibility for older Perl and Lua runtimes 3 months ago
Sergey Kiselev 9002d15a55 SAT: implement 12-byte fallback recovery for legacy JMicron bridges 3 months ago
Sergey Kiselev 66305be69a Docs: correct Perl example in README.md 3 months ago
Sergey Kiselev 6197e843b0 SRC: fix indents in sources 3 months ago
Sergey Kiselev eec307986e Merge branch 'master' of ssh://git.dc365.ru:2227/digital-freak/bsdiskinfo 3 months ago
Sergey Kiselev eba47b6f0f SAT: fix S.M.A.R.T. matrix extraction layout for USB storage bridges 3 months ago
Sergey Kiselev 1a97f75858 Docs: document custom FreeBSD ports overlay installation in README 3 months ago
  1. 75
      README.md
  2. 2
      examples/tree_view.lua
  3. 80
      include/bsdiskinfo.h
  4. 4
      modules/perl/bsdiskinfo.xs
  5. 53
      src/cli.c
  6. 420
      src/libbsdiskinfo.c
  7. 114
      src/smart.h

75
README.md

@ -27,24 +27,25 @@ firmware versions, WWN identifiers, and native sector geometry configs.
## Repository Structure
```text
bsdiskinfo/
├── include/
│ └── bsdiskinfo.h # Public standalone C API header
├── src/
│ ├── libbsdiskinfo.c # Core library implementation layers
│ ├── cli.c # POSIX-compliant administrative CLI testbed
│ └── smart.h # Shared structural S.M.A.R.T. hardware definitions
├── modules/
│ └── lua/
│ ├── Makefile # Dynamic Lua module build configurations
│ └── lua_bsdiskinfo.c # High-performance C-to-Lua binding translator
│ └── perl/
│ ├── Makefile.PL # Perl ExtUtils::MakeMaker build script
│ ├── bsdiskinfo.xs # Core C-to-Perl XS translator bridge
│ └── lib/
│ └── bsdiskinfo.pm # Public Perl module interface and POD docs
├── examples/
│ └── tree_view.lua # Complex hardware tree mapping layout example
bsdiskinfo
├── examples
│   ├── tree_view.lua # Complex hardware tree mapping layout example
│   └── tree_view.pl
├── include
│   └── bsdiskinfo.h # Public standalone C API header
├── modules
│   ├── lua
│   │   ├── lua_bsdiskinfo.c # High-performance C-to-Lua binding translator
│   │   └── Makefile # Dynamic Lua module build configurations
│   └── perl
│   ├── lib
│   │   └── bsdiskinfo.pm # Public Perl module interface and POD docs
│   ├── bsdiskinfo.xs # Core C-to-Perl XS translator bridge
│   └── Makefile.PL # Perl ExtUtils::MakeMaker build script
├── src
│   ├── cli.c # POSIX-compliant administrative CLI testbed
│   ├── libbsdiskinfo.c # Core library implementation layers
│   └── smart.h # Shared structural S.M.A.R.T. hardware definitions
├── LICENSE # BSD 2-Clause Simplified License
└── Makefile # Native FreeBSD root pmake script
```
@ -86,6 +87,26 @@ sudo make install
*Installs components under `/usr/local/` paths (`lib/`, `include/`, `sbin/`,
and corresponding Lua runtime folders).*
## Installation via FreeBSD Ports (Custom Overlay)
The project is packaged for FreeBSD through a dedicated, standalone ports
repository overlay. If you manage a local ports overlay or want to build a
clean native package via `poudriere`/`bmake`, add the port definition from
the tracking tree repository:
* **Ports Overlay Repository**: [https://git.dc365.ru/digital-freak/ports](https://git.dc365.ru/digital-freak/ports)
To build and install the toolkit from the overlay manually:
```bash
# Navigate to the port directory inside your overlay tree
cd /usr/ports/devel/bsdiskinfo
# Configure, build, and install with selected bindings (Lua/Perl)
sudo make config
sudo make install clean
```
## Usage Profiles
### Binary CLI Diagnostic Tool
@ -143,20 +164,20 @@ use bsdiskinfo;
use BSD::Sysctl; # Native FreeBSD kernel MIB traversal bindings
# Query available physical drives straight from the kernel tree
my \$disks_str = BSD::Sysctl::sysctl('kern.disks') // "";
my $disks_str = BSD::Sysctl::sysctl('kern.disks') || "";
for my \(disk (sort (split /\s+/,\)disks_str)) {
for my $disk (sort (split /\s+/,$disks_str)) {
# Protect routing logic by verifying character device existence
next unless bsdiskinfo::exists(\$disk);
next unless bsdiskinfo::exists($disk);
my \(props = get_properties(\)disk);
if (\$props) {
print "Drive: /dev/\$disk [Model: props->model, Type: props->{type}]\n";
my $props = get_properties($disk);
if ($props) {
print "Drive: /dev/$disk [Model: $props->{model}, Type: $props->{type}]\n";
# Traverse active slices and mounted partitions
my \(mounts = get_mounts(\)disk) // [];
for my \(mnt (@\)mounts) {
print " └─ Partition \$mnt->{device} mounted at mnt->path (mnt->{fstype})\n";
my $mounts = get_mounts($disk) || [];
for my $mnt (@$mounts) {
print " └─ Partition $mnt->{device} mounted at $mnt->{path} ($mnt->{fstype})\n";
}
}
}

2
examples/tree_view.lua

@ -91,7 +91,7 @@ for _, disk in ipairs(disks) do
local temp = "N/A"
for _, attr in ipairs(smart) do
if attr.id == 194 or attr.id == 190 then
temp = string.format("%d°C", math.tointeger(attr.raw_val & 0xFF) or attr.raw_val)
temp = string.format("%d°C", math.floor(attr.raw_val % 256) or attr.raw_val)
break
end
end

80
include/bsdiskinfo.h

@ -33,34 +33,44 @@
#include <sys/ucred.h>
#include <sys/mount.h>
#define MAX_MODEL_LEN 256
#define MAX_SERIAL_LEN 64
#define MAX_TYPE_LEN 16
#define MAX_FW_LEN 16
#define MAX_WWN_LEN 32
#define MAX_MOUNTS 128
#define MAX_DISKS 64
#define MAX_MODEL_LEN 256 /* Buffer size for device model string */
#define MAX_SERIAL_LEN 64 /* Buffer size for drive serial number string */
#define MAX_TYPE_LEN 16 /* Buffer size for determined drive architecture type string */
#define MAX_FW_LEN 16 /* Buffer size for device firmware version revision string */
#define MAX_WWN_LEN 32 /* Buffer size for LU WWN Device Id hex string format */
#define MAX_FF_LEN 32 /* Buffer size for physical form factor string */
#define MAX_ATA_LEN 64 /* Buffer size for major/minor ATA standard specification */
#define MAX_SATA_LEN 64 /* Buffer size for SATA signaling capabilities and link speed */
#define MAX_SEC_LEN 32 /* Buffer size for internal ATA Security feature state */
#define MAX_MOUNTS 128 /* Maximum tracked active filesystem mount topologies */
#define MAX_DISKS 64 /* Maximum concurrent physical storage units allowed */
/*
* Base hardware identity properties (similar to smartctl -i output).
*/
struct disk_properties {
char model[MAX_MODEL_LEN]; /* Device model string */
char serial[MAX_SERIAL_LEN]; /* Serial number string */
char fw_version[MAX_FW_LEN]; /* Firmware version string */
char wwn[MAX_WWN_LEN]; /* LU WWN Device Id (hex string format) */
char type_str[MAX_TYPE_LEN]; /* Determined drive type: "ssd", "hdd", "usb", "sdcard", "nvme" */
uint64_t mediasize; /* Total storage capacity in bytes */
char model[MAX_MODEL_LEN]; /* Device model string */
char serial[MAX_SERIAL_LEN]; /* Serial number string */
char fw_version[MAX_FW_LEN]; /* Firmware version string */
char wwn[MAX_WWN_LEN]; /* LU WWN Device Id (hex string format) */
char type_str[MAX_TYPE_LEN]; /* Determined drive type: "ssd", "hdd", "usb", "sdcard", "nvme" */
uint64_t mediasize; /* Total storage capacity in bytes */
uint32_t logical_sector_size; /* Logical sector size in bytes */
uint32_t physical_sector_size; /* Physical sector size in bytes (Advanced Format) */
uint32_t logical_sector_size; /* Logical sector size in bytes */
uint32_t physical_sector_size; /* Physical sector size in bytes (Advanced Format) */
int rotation_rate; /* Nominal rotation rate: 0 = SSD, >0 = RPM, -1 = N/A */
int rotation_rate; /* Nominal rotation rate: 0 = SSD, >0 = RPM, -1 = N/A */
int smart_supported; /* S.M.A.R.T. capability support: 1 = Yes, 0 = No, -1 = N/A */
int smart_enabled; /* S.M.A.R.T. active enablement: 1 = Yes, 0 = No, -1 = N/A */
int smart_supported; /* S.M.A.R.T. capability support: 1 = Yes, 0 = No, -1 = N/A */
int smart_enabled; /* S.M.A.R.T. active enablement: 1 = Yes, 0 = No, -1 = N/A */
/* Advanced ATA hardware descriptors */
char form_factor[MAX_FF_LEN]; /* Nominal physical drive form factor dimensions */
char ata_version[MAX_ATA_LEN]; /* Major/Minor ATA/ATAPI specification standard tracking */
char sata_version[MAX_SATA_LEN]; /* Active SATA signaling speed capabilities and status */
char security_status[MAX_SEC_LEN];/* Internal firmware ATA Security Feature Set execution state */
};
/*
@ -68,19 +78,19 @@ struct disk_properties {
* slice/partition.
*/
struct disk_mount {
char device[MNAMELEN]; /* Partition node name (e.g., "ada0p2") */
char path[MNAMELEN]; /* Mounted target filesystem path (e.g., "/usr") */
char fstype[MFSNAMELEN]; /* Filesystem type name (e.g., "zfs", "ufs") */
char device[MNAMELEN]; /* Partition node name (e.g., "ada0p2") */
char path[MNAMELEN]; /* Mounted target filesystem path (e.g., "/usr") */
char fstype[MFSNAMELEN]; /* Filesystem type name (e.g., "zfs", "ufs") */
};
/*
* Decoded record for a single raw S.M.A.R.T. attribute vendor entry.
*/
struct smart_counter {
uint8_t id; /* Attribute ID tag (e.g., 0x05) */
uint8_t value; /* Current normalized value */
uint8_t worst; /* Worst historical normalized value */
uint64_t raw_val; /* Parsed 48-bit raw value (vendor specific data) */
uint8_t id; /* Attribute ID tag (e.g., 0x05) */
uint8_t value; /* Current normalized value */
uint8_t worst; /* Worst historical normalized value */
uint64_t raw_val; /* Parsed 48-bit raw value (vendor specific data) */
};
#ifdef __cplusplus
@ -100,8 +110,8 @@ bool diskinfo_exists(const char *base_disk);
* Returns 0 on success.
*/
int diskinfo_get_properties(
const char *base_disk,
struct disk_properties *props
const char *base_disk,
struct disk_properties *props
);
/*
@ -112,9 +122,9 @@ int diskinfo_get_properties(
* failures.
*/
int diskinfo_get_mounts(
const char *base_disk,
struct disk_mount *mounts_out,
int max_mounts
const char *base_disk,
struct disk_mount *mounts_out,
int max_mounts
);
/*
@ -126,9 +136,9 @@ int diskinfo_get_mounts(
* code on runtime failures.
*/
int diskinfo_get_smart_counters(
const char *base_disk,
struct smart_counter *counters_out,
int max_counters
const char *base_disk,
struct smart_counter *counters_out,
int max_counters
);
#ifdef __cplusplus

4
modules/perl/bsdiskinfo.xs

@ -51,8 +51,8 @@ get_properties(base_disk)
}
if (props.smart_supported >= 0) {
hv_store(hv, "smart_supported", 15, newSVbool(props.smart_supported == 1), 0);
hv_store(hv, "smart_enabled", 13, newSVbool(props.smart_enabled == 1), 0);
hv_store(hv, "smart_supported", 15, boolSV(props.smart_supported == 1), 0);
hv_store(hv, "smart_enabled", 13, boolSV(props.smart_enabled == 1), 0);
}
/* Return as a reference to the hash */

53
src/cli.c

@ -83,15 +83,34 @@ int main(int argc, char *argv[]) {
static void print_identity(const char *disk_name, struct disk_properties *props) {
printf("=== START OF INFORMATION SECTION (%s) ===\n", disk_name);
printf("Device Model: %s\n", strlen(props->model) > 0 ? props->model : "N/A");
printf("Serial Number: %s\n", strlen(props->serial) > 0 ? props->serial : "N/A");
printf("Firmware Version: %s\n", strlen(props->fw_version) > 0 ? props->fw_version : "N/A");
printf("LU WWN Device Id: %s\n", strlen(props->wwn) > 0 ? props->wwn : "N/A");
printf("Determined Type: %s\n", strlen(props->type_str) > 0 ? props->type_str : "unknown");
printf("User Capacity: %llu bytes\n", (unsigned long long)props->mediasize);
printf("Sector Sizes: %u bytes logical, %u bytes physical\n",
printf("Device Model: %s\n", props->model);
printf("Serial Number: %s\n", props->serial);
printf("Firmware Version: %s\n", props->fw_version);
printf("LU WWN Device Id: %s\n", props->wwn);
/* Display advanced ATA hardware descriptors unpacked by the core library */
printf("Form Factor: %s\n", props->form_factor); /* Nominal physical unit size dimensions */
printf("ATA Version: %s\n", props->ata_version); /* Major compliance standard tracking */
printf("SATA Version: %s\n", props->sata_version); /* Signalling standard interface capabilities */
printf("ATA Security: %s\n", props->security_status); /* Operational hardware security locks state */
printf("Determined Type: %s\n", props->type_str);
/* Render storage payload storage capacity with explicit units computing */
double gib = (double)props->mediasize / (1024.0 * 1024.0 * 1024.0);
if (gib >= 1024.0) {
printf("User Capacity: %llu bytes (%.2f TiB)\n",
(unsigned long long)props->mediasize, gib / 1024.0);
} else {
printf("User Capacity: %llu bytes (%.2f GiB)\n",
(unsigned long long)props->mediasize, gib);
}
/* Print drive layout formatting boundaries details */
printf("Sector Sizes: %u bytes logical, %u bytes physical\n",
props->logical_sector_size, props->physical_sector_size);
/* Parse nominal motor spin rotation specs layout */
if (props->rotation_rate == 0) {
printf("Rotation Rate: 0 rpm (SSD)\n");
} else if (props->rotation_rate > 0) {
@ -100,9 +119,15 @@ static void print_identity(const char *disk_name, struct disk_properties *props)
printf("Rotation Rate: N/A\n");
}
printf("SMART Support: %s\n", props->smart_supported == 1 ? "Available" : "Unavailable");
printf("SMART Status: %s\n", props->smart_supported != 1 ? "N/A" :
(props->smart_enabled == 1 ? "Enabled" : "Disabled"));
/* Process core hardware diagnostic state indicators */
if (props->smart_supported == 1) {
printf("SMART Support: Available\n");
printf("SMART Status: %s\n", props->smart_enabled == 1 ? "Enabled" : "Disabled");
} else if (props->smart_supported == 0) {
printf("SMART Support: Not available\n");
} else {
printf("SMART Support: N/A\n");
}
printf("=====================================================\n\n");
}
@ -166,6 +191,12 @@ static void print_smart(const char *disk_name) {
int min_lifetime = (int)((counters[i].raw_val >> 32) & 0xFF); /* Fixed strict & 0xFF byte-4 alignment masking */
int max_lifetime = (int)((counters[i].raw_val >> 40) & 0xFF); /* Fixed strict & 0xFF byte-5 alignment masking */
/* Fix for Toshiba/Samsung layouts where byte 4 is Max and byte 5 is empty */
if (min_lifetime > 0 && max_lifetime == 0) {
max_lifetime = min_lifetime;
min_lifetime = 0;
}
if (min_lifetime > 0 && min_lifetime < 100) {
if (max_lifetime > 0 && max_lifetime < 100 && max_lifetime > min_lifetime) {
printf("%-12llu (Min/Max %d/%d)\n", (unsigned long long)counters[i].raw_val, min_lifetime, max_lifetime);

420
src/libbsdiskinfo.c

@ -30,6 +30,7 @@
#define _GNU_SOURCE /* For strcasestr support */
#include <errno.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
@ -225,6 +226,125 @@ int diskinfo_get_mounts(const char *base_disk, struct disk_mount *mounts_out, in
return match_count; /* Return absolute count of populated records */
}
/*
* Extracts advanced ATA/SATA hardware descriptor properties from
* the provided native IDENTIFY parameter blocks using native libcam functions.
*/
static void parse_advanced_ata_fields(struct ata_params *buf, struct disk_properties *props, float current_speed) {
/* 1. Form Factor dimensions handling (Word 168) */
uint16_t ff = buf->form_factor;
if (ff == 2) strlcpy(props->form_factor, "3.5 inches", sizeof(props->form_factor));
else if (ff == 3) strlcpy(props->form_factor, "2.5 inches", sizeof(props->form_factor));
else if (ff == 4) strlcpy(props->form_factor, "1.8 inches", sizeof(props->form_factor));
else if (ff == 5) strlcpy(props->form_factor, "less than 1.8", sizeof(props->form_factor));
else strlcpy(props->form_factor, "N/A", sizeof(props->form_factor));
/* 2. Major ATA Version standard handling using native ata_version() from libcam.so */
int version = ata_version(buf->version_major);
if (buf->version_major == 0xFFFF || buf->version_major == 0x0000 || version < 0) {
strlcpy(props->ata_version, "Unknown", sizeof(props->ata_version));
} else {
switch (version) {
case 0:
strlcpy(props->ata_version, "ATA", sizeof(props->ata_version));
break;
case 8:
strlcpy(props->ata_version, "ATA8-ACS", sizeof(props->ata_version));
break;
default:
if (version <= 7) {
snprintf(props->ata_version, sizeof(props->ata_version), "ATA-%d", version);
} else {
snprintf(props->ata_version, sizeof(props->ata_version), "ACS-%d", version - 7);
}
break;
}
}
/* 3. Serial ATA Capabilities layout tracing limited by mapped ATA standard boundaries (Word 76 & Word 222) */
uint16_t sata_cap = buf->satacapabilities;
uint16_t transport = buf->transport_major;
if (sata_cap == 0xFFFF || sata_cap == 0x0000) {
strlcpy(props->sata_version, "Not a SATA device", sizeof(props->sata_version));
} else {
const char *ver = NULL;
float cap_speed = 1.5;
/* Extract logical transport revision from Word 222 using explicit camcontrol bit boundaries */
if (transport != 0xFFFF && transport != 0x0000 && (transport & 0x1000)) {
if (transport & 0x0400) ver = "SATA 3.5";
else if (transport & 0x0200) ver = "SATA 3.4";
else if (transport & 0x0100) ver = "SATA 3.3";
else if (transport & 0x0080) ver = "SATA 3.2";
else if (transport & 0x0040) ver = "SATA 3.1";
else if (transport & 0x0020) ver = "SATA 3.0";
else if (transport & 0x0010) ver = "SATA 2.6";
else if (transport & 0x0008) ver = "SATA 2.5";
}
/* Fallback to physical link generation signaling limits from Word 76 if transport is unmapped */
if (ver == NULL) {
if (strcmp(props->ata_version, "ATA-7") == 0 || strncmp(props->ata_version, "ATA-", 4) == 0) {
ver = "SATA 1.x";
cap_speed = 1.5;
} else {
if (sata_cap & 0x0006) {
ver = "SATA 3.x";
cap_speed = 6.0;
} else if (sata_cap & 0x0004) {
ver = "SATA 2.x";
cap_speed = 3.0;
} else {
ver = "SATA 1.x";
cap_speed = 1.5;
}
}
} else {
/* Determine maximum capable speed rate from Word 76 capabilities for modern units */
if (sata_cap & 0x0006) cap_speed = 6.0;
else if (sata_cap & 0x0004) cap_speed = 3.0;
else cap_speed = 1.5;
/*
* Architectural ceiling check for backwards compatibility (FreeBSD 11.4 legacy layers).
* If the drive is reporting legacy SATA 2.5/2.6 or if we are tracking older firmware lines,
* override fake controller-injected Gen3 speed limits using drive's genuine Word 76 bits.
*/
if (strncmp(ver, "SATA 2.", 7) == 0 && cap_speed > 3.0) {
if (sata_cap & 0x0004) cap_speed = 3.0;
else cap_speed = 1.5;
}
/* If running on older FreeBSD 11.4 where Word 222 output fluctuates, sanitize down to 1.x */
if (cap_speed == 1.5 && strncmp(ver, "SATA 3.", 7) != 0) {
ver = "SATA 1.x";
}
}
/* If CAM transport layout parsing returned empty/restricted, sync with capable speed */
if (current_speed <= 0.0) {
current_speed = cap_speed;
}
/* Generate final elegant string matches smartctl/camcontrol style */
snprintf(props->sata_version, sizeof(props->sata_version),
"%s, %.1f Gb/s (current: %.1f Gb/s)", ver, cap_speed, current_speed);
}
/* 4. ATA Security Feature Set state validation (Word 128) */
uint16_t sec = buf->security_status;
if (!(sec & 0x0001)) {
strlcpy(props->security_status, "Disabled", sizeof(props->security_status));
} else if (sec & 0x0004) {
strlcpy(props->security_status, "LOCKED", sizeof(props->security_status));
} else if (sec & 0x0008) {
strlcpy(props->security_status, "FROZEN", sizeof(props->security_status));
} else {
strlcpy(props->security_status, "NOT FROZEN", sizeof(props->security_status));
}
}
/*
* Internal helper to query the device via FreeBSD libcam.
* Handles SCSI SAT Pass-Through (16) for 'da' devices and native ATA for 'ada' devices.
@ -244,12 +364,90 @@ static void query_cam_properties(
*pt_failed = 0;
/* CRITICAL: Must use O_RDWR for transport-layer commands execution path to unlock */
/* Pre-initialize advanced hardware descriptors with default values */
strlcpy(props->form_factor, "N/A", sizeof(props->form_factor));
strlcpy(props->ata_version, "N/A", sizeof(props->ata_version));
strlcpy(props->sata_version, "N/A", sizeof(props->sata_version));
strlcpy(props->security_status, "N/A", sizeof(props->security_status));
/* CRITICAL Check: Permissions routing validation */
cam_dev = cam_open_device(base_disk, O_RDWR);
if (cam_dev == NULL) return;
if (cam_dev == NULL) {
if (errno == EACCES || errno == EPERM) {
fprintf(stderr, "[ERROR] %s: Permission denied. Accessing libcam transport layers requires root privileges (run with sudo).\n", base_disk);
} else {
fprintf(stderr, "[ERROR] %s: Failed to open CAM device node (errno: %d)\n", base_disk, errno);
}
return;
}
/* Extract current active signaling rate mirroring camcontrol.c camxferrate() flow exactly */
float current_speed = 0.0;
uint32_t speed = 0;
union ccb *ccb_cts = NULL;
struct ccb_pathinq cpi;
memset(&cpi, 0, sizeof(struct ccb_pathinq));
/* Step A: Fetch base speed path inquiry matching camcontrol get_cpi() */
union ccb *ccb_cpi = cam_getccb(cam_dev);
if (ccb_cpi != NULL) {
ccb_cpi->ccb_h.func_code = XPT_PATH_INQ;
ccb_cpi->ccb_h.flags = CAM_DIR_NONE;
ccb_cpi->ccb_h.retry_count = 1;
ccb_cpi->ccb_h.timeout = 1000;
if (cam_send_ccb(cam_dev, ccb_cpi) == 0 && (ccb_cpi->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
bcopy(&ccb_cpi->cpi, &cpi, sizeof(struct ccb_pathinq));
}
cam_freeccb(ccb_cpi);
}
/* Initialize baseline transfer speed value from Path Inquiry */
speed = cpi.base_transfer_speed;
/* Step B: Query exact active transfer settings from the CAM layer */
ccb_cts = cam_getccb(cam_dev);
if (ccb_cts != NULL) {
ccb_cts->ccb_h.func_code = XPT_GET_TRAN_SETTINGS;
ccb_cts->ccb_h.flags = CAM_DIR_NONE;
ccb_cts->ccb_h.retry_count = 1;
ccb_cts->ccb_h.timeout = 1000;
ccb_cts->ccb_h.target_id = cam_dev->target_id;
ccb_cts->ccb_h.target_lun = cam_dev->target_lun;
ccb_cts->cts.type = CTS_TYPE_CURRENT_SETTINGS; /* Force runtime active link preference layout fetch */
if (cam_send_ccb(cam_dev, ccb_cts) == 0 && (ccb_cts->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
/* Execute exact transport specific branching copied from camcontrol.c source */
if (ccb_cts->cts.transport == XPORT_ATA) {
struct ccb_trans_settings_pata *pata = &ccb_cts->cts.xport_specific.ata;
if (pata->valid & CTS_ATA_VALID_MODE) {
speed = ata_mode2speed(pata->mode);
}
} else if (ccb_cts->cts.transport == XPORT_SATA) {
struct ccb_trans_settings_sata *sata = &ccb_cts->cts.xport_specific.sata;
if (sata->valid & CTS_SATA_VALID_REVISION) {
speed = ata_revision2speed(sata->revision);
}
} else if (ccb_cts->cts.transport == XPORT_SAS) {
struct ccb_trans_settings_sas *sas = &ccb_cts->cts.xport_specific.sas;
if (sas->valid & 0x01) { /* CTS_SAS_VALID_SPEED */
speed = sas->bitrate;
}
}
}
cam_freeccb(ccb_cts);
}
/* Map finalized speed KB/s calculation to human-readable Gb/s standard format */
uint32_t mb = speed / 1000;
if (mb >= 550) current_speed = 6.0;
else if (mb >= 280) current_speed = 3.0;
else if (mb >= 140) current_speed = 1.5;
/* Allocate CCB structure for standard IDENTIFY command execution path below */
ccb = cam_getccb(cam_dev);
if (ccb == NULL) {
fprintf(stderr, "[ERROR] %s: Failed to allocate CAM CCB structure\n", base_disk);
cam_close_device(cam_dev);
return;
}
@ -278,7 +476,7 @@ static void query_cam_properties(
ccb->csio.cdb_io.cdb_bytes[4] = SHORT_INQUIRY_LENGTH;
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
if ((inq_data->device & SID_RMB) != 0) *is_removable = 1;
if ((inq_data->device & SID_RMB) != 0) *is_removable = 1;
char vendor[9], product[17];
snprintf(vendor, sizeof(vendor), "%.8s", inq_data->vendor);
snprintf(product, sizeof(product), "%.16s", inq_data->product);
@ -296,7 +494,38 @@ static void query_cam_properties(
uint8_t *cdb = ccb->csio.cdb_io.cdb_bytes;
cdb[0] = 0x85; cdb[1] = 4 << 1; cdb[2] = 0x08 | 0x02; cdb[4] = 1; cdb[14] = 0xEC;
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
int success = (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP);
if (success && ident_buf->model[0] == 0) {
success = 0;
}
if (!success) {
/*
* FALLBACK: 16-byte frame returned an empty layout.
* Downgrade transport to 12-byte ATA PASS-THROUGH (12) for legacy JMicron bridges.
*/
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio));
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE,
(uint8_t *)ident_buf, sizeof(struct ata_params), SSD_FULL_SIZE, 12, 5000);
uint8_t *cdb12 = ccb->csio.cdb_io.cdb_bytes;
cdb12[0] = 0xA1; cdb12[1] = 4 << 1; cdb12[2] = 0x2E; cdb12[4] = 1; cdb12[9] = 0xEC;
if (cam_send_ccb(cam_dev, ccb) == 0) {
uint32_t cam_status = ccb->ccb_h.status & CAM_STATUS_MASK;
if (cam_status == CAM_REQ_CMP || cam_status == CAM_SCSI_STATUS_ERROR) {
success = 1;
}
}
/* Filter out fake success on SAT(12) loop as well */
if (success && ident_buf->model[0] == 0) {
success = 0;
}
}
if (success) {
/* Extract Identity Block Data fields */
char real_model[41];
memcpy(real_model, ident_buf->model, sizeof(ident_buf->model));
@ -326,19 +555,24 @@ static void query_cam_properties(
/* Parse Rotation Rate and SSD state flags */
if (ident_buf->media_rotation_rate == 1) {
props->rotation_rate = 0;
*is_ssd = 1;
} else if (ident_buf->media_rotation_rate > 1 && ident_buf->media_rotation_rate < 0xFFFF) {
props->rotation_rate = ident_buf->media_rotation_rate;
*is_ssd = 0;
} else {
props->rotation_rate = -1;
*is_ssd = 0;
}
/* Parse Sector Sizes */
if ((ident_buf->pss & 0xC000) == 0x4000 && (ident_buf->pss & 0x1000)) {
props->rotation_rate = 0;
*is_ssd = 1;
} else if (ident_buf->media_rotation_rate > 1 && ident_buf->media_rotation_rate < 0xFFFF) {
props->rotation_rate = ident_buf->media_rotation_rate;
*is_ssd = 0;
} else {
props->rotation_rate = -1;
*is_ssd = 0;
}
/* Parse advanced hardware descriptors from the verified buffer */
parse_advanced_ata_fields(ident_buf, props, current_speed);
/* Parse Sector Sizes (Word 106: Physical sector size / Advanced Format) */
if ((ident_buf->pss & 0x4000) && (ident_buf->pss & 0x2000)) {
props->physical_sector_size = props->logical_sector_size * (1 << (ident_buf->pss & 0x000F));
} else {
props->physical_sector_size = props->logical_sector_size;
}
/* Parse S.M.A.R.T. enablement states */
@ -349,12 +583,13 @@ static void query_cam_properties(
props->smart_supported = 0; props->smart_enabled = 0;
}
} else {
fprintf(stderr, "[ERROR] %s: ATA Pass-Through rejected or returned empty payload (hardware timeout/fault)\n", base_disk);
*pt_failed = 1;
}
}
free(inq_buf);
}
}
/* --- PROTOCOL BRANCH 2: Native SATA/ATA Devices (ada) --- */
else if (strncmp(base_disk, "ada", 3) == 0) {
/* 2a: Pure ATA IDENTIFY */
@ -364,56 +599,67 @@ static void query_cam_properties(
ccb->ataio.cmd.command = 0xEC;
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
char real_model[41];
memcpy(real_model, ident_buf->model, sizeof(ident_buf->model));
real_model[sizeof(ident_buf->model)] = '\0';
ata_str_fix(real_model, sizeof(real_model));
if (strlen(real_model) > 0) strlcpy(props->model, real_model, sizeof(props->model));
char real_serial[21];
memcpy(real_serial, ident_buf->serial, sizeof(ident_buf->serial));
real_serial[sizeof(ident_buf->serial)] = '\0';
ata_str_fix(real_serial, sizeof(real_serial));
if (strlen(real_serial) > 0) strlcpy(props->serial, real_serial, sizeof(props->serial));
char real_fw[9];
memcpy(real_fw, ident_buf->revision, sizeof(ident_buf->revision));
real_fw[sizeof(ident_buf->revision)] = '\0';
ata_str_fix(real_fw, sizeof(real_fw));
if (strlen(real_fw) > 0) strlcpy(props->fw_version, real_fw, sizeof(props->fw_version));
/* Parse LU WWN Device ID */
uint64_t wwn_val = 0;
for (int w = 0; w < 4; w++) {
wwn_val |= ((uint64_t)ident_buf->wwn[w]) << ((3 - w) * 16);
}
if (wwn_val != 0) snprintf(props->wwn, sizeof(props->wwn), "%016llx", (unsigned long long)wwn_val);
else strlcpy(props->wwn, "N/A", sizeof(props->wwn));
/* Parse Rotation Rate and SSD state flags */
if (ident_buf->media_rotation_rate == 1) {
props->rotation_rate = 0;
*is_ssd = 1;
} else if (ident_buf->media_rotation_rate > 1 && ident_buf->media_rotation_rate < 0xFFFF) {
props->rotation_rate = ident_buf->media_rotation_rate;
*is_ssd = 0;
} else {
props->rotation_rate = -1;
*is_ssd = 0;
}
/* Parse Sector Sizes */
if ((ident_buf->pss & 0xC000) == 0x4000 && (ident_buf->pss & 0x1000)) {
props->physical_sector_size = props->logical_sector_size * (1 << (ident_buf->pss & 0x000F));
}
/* Parse S.M.A.R.T. enablement states */
if (ident_buf->support.command1 & 0x0001) {
props->smart_supported = 1;
props->smart_enabled = (ident_buf->enabled.command1 & 0x0001) ? 1 : 0;
if (ident_buf->model[0] == 0) {
fprintf(stderr, "[ERROR] %s: IDENTIFY DEVICE returned empty payload (hardware fault)\n", base_disk);
} else {
props->smart_supported = 0; props->smart_enabled = 0;
char real_model[41];
memcpy(real_model, ident_buf->model, sizeof(ident_buf->model));
real_model[sizeof(ident_buf->model)] = '\0';
ata_str_fix(real_model, sizeof(real_model));
if (strlen(real_model) > 0) strlcpy(props->model, real_model, sizeof(props->model));
char real_serial[21];
memcpy(real_serial, ident_buf->serial, sizeof(ident_buf->serial));
real_serial[sizeof(ident_buf->serial)] = '\0';
ata_str_fix(real_serial, sizeof(real_serial));
if (strlen(real_serial) > 0) strlcpy(props->serial, real_serial, sizeof(props->serial));
char real_fw[9];
memcpy(real_fw, ident_buf->revision, sizeof(ident_buf->revision));
real_fw[sizeof(ident_buf->revision)] = '\0';
ata_str_fix(real_fw, sizeof(real_fw));
if (strlen(real_fw) > 0) strlcpy(props->fw_version, real_fw, sizeof(props->fw_version));
/* Parse LU WWN Device ID */
uint64_t wwn_val = 0;
for (int w = 0; w < 4; w++) {
wwn_val |= ((uint64_t)ident_buf->wwn[w]) << ((3 - w) * 16);
}
if (wwn_val != 0) snprintf(props->wwn, sizeof(props->wwn), "%016llx", (unsigned long long)wwn_val);
else strlcpy(props->wwn, "N/A", sizeof(props->wwn));
/* Parse Rotation Rate and SSD state flags */
if (ident_buf->media_rotation_rate == 1) {
props->rotation_rate = 0;
*is_ssd = 1;
} else if (ident_buf->media_rotation_rate > 1 && ident_buf->media_rotation_rate < 0xFFFF) {
props->rotation_rate = ident_buf->media_rotation_rate;
*is_ssd = 0;
} else {
props->rotation_rate = -1;
*is_ssd = 0;
}
/* Parse advanced hardware descriptors from the direct AHCI buffer */
parse_advanced_ata_fields(ident_buf, props, current_speed);
/* Parse Sector Sizes (Word 106: Physical sector size / Advanced Format) */
if ((ident_buf->pss & 0x4000) && (ident_buf->pss & 0x2000)) {
props->physical_sector_size = props->logical_sector_size * (1 << (ident_buf->pss & 0x000F));
} else {
props->physical_sector_size = props->logical_sector_size;
}
/* Parse S.M.A.R.T. enablement states */
if (ident_buf->support.command1 & 0x0001) {
props->smart_supported = 1;
props->smart_enabled = (ident_buf->enabled.command1 & 0x0001) ? 1 : 0;
} else {
props->smart_supported = 0; props->smart_enabled = 0;
}
}
} else {
fprintf(stderr, "[ERROR] %s: Native ATA IDENTIFY command rejected by the controller\n", base_disk);
}
}
@ -555,25 +801,41 @@ int diskinfo_get_smart_counters(const char *base_disk, struct smart_counter *cou
/* --- PROTOCOL BRANCH 1: USB / SCSI Direct Access Devices (da) --- */
if (strncmp(base_disk, "da", 2) == 0) {
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio));
/* Using system-defined SSD_FULL_SIZE macro (252) from cam/scsi/scsi_all.h */
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE,
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE,
(uint8_t *)smart_buf, sizeof(struct smart_values), SSD_FULL_SIZE, 16, 5000);
uint8_t *cdb = ccb->csio.cdb_io.cdb_bytes;
cdb[0] = 0x85; /* ATA PASS-THROUGH (16) */
cdb[1] = 4 << 1; /* PIO Data-In protocol */
cdb[2] = 0x08 | 0x02; /* T_LENGTH = 1 (sectors), BY_BYTE = 0 (words) */
cdb[4] = 0xD0; /* SMART Feature register субкоманда */
cdb[6] = 1; /* Sector Count = 1 */
cdb[8] = 0x4F; /* LBA Low = 0x4F (SMART Magic) */
cdb[10] = 0xC2; /* LBA High = 0xC2 (SMART Magic) */
cdb[14] = 0xB0; /* SMART Opcode command */
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
data_fetched = true;
/* Strict T10 SAT-compliant 16-byte ATA PASS-THROUGH mapping */
cdb[0] = 0x85; /* Opcode: ATA PASS-THROUGH (16) */
cdb[1] = (4 << 1); /* Protocol: PIO Data-In */
cdb[2] = 0x2E; /* T_DIR=1 (In), BYTE_BLOCK=0 (Bytes), T_LENGTH=2 (Sector Count) */
cdb[3] = 0x00; /* Features (HO) */
cdb[4] = 0xD0; /* Features (LO): SMART READ DATA subcommand */
cdb[5] = 0x00; /* Sector Count (HO) */
cdb[6] = 0x01; /* Sector Count (LO): Read 1 sector */
cdb[7] = 0x00; /* LBA Low (HO) */
cdb[8] = 0x00; /* LBA Low (LO) */
cdb[9] = 0x00; /* LBA Mid (HO) */
cdb[10] = 0x4F; /* LBA Mid (LO): SMART Magic Signature */
cdb[11] = 0x00; /* LBA High (HO) */
cdb[12] = 0xC2; /* LBA High (LO): SMART Magic Signature */
cdb[13] = 0x00; /* Device */
cdb[14] = 0xB0; /* ATA Command Opcode: SMART Command */
cdb[15] = 0x00; /* Control */
if (cam_send_ccb(cam_dev, ccb) == 0) {
uint32_t cam_status = ccb->ccb_h.status & CAM_STATUS_MASK;
/*
* Accept standard completion (CAM_REQ_CMP) or T10 SAT-compliant
* auto-sense descriptors (CAM_SCSI_STATUS_ERROR with CHECK CONDITION).
*/
if (cam_status == CAM_REQ_CMP || cam_status == CAM_SCSI_STATUS_ERROR) {
data_fetched = true;
}
}
}
}
/* --- PROTOCOL BRANCH 2: Native SATA/ATA Devices (ada) --- */
else if (strncmp(base_disk, "ada", 3) == 0) {
memset(&ccb->ataio, 0, sizeof(struct ccb_ataio));

114
src/smart.h

@ -75,63 +75,63 @@ static inline void decode_seagate_attr194(uint64_t raw_val, int *cur, int *min)
* Declared as static inline to safely include across multiple compilation units.
*/
static inline const char *get_attr_name(uint8_t id) {
switch (id) {
case 1: return "Raw_Read_Error_Rate";
case 2: return "Throughput_Performance";
case 3: return "Spin_Up_Time";
case 4: return "Start_Stop_Count";
case 5: return "Reallocated_Sector_Ct";
case 7: return "Seek_Error_Rate";
case 8: return "Seek_Time_Performance";
case 9: return "Power_On_Hours";
case 10: return "Spin_Retry_Count";
case 12: return "Power_Cycle_Count";
case 16: return "Helium_Level_WD";
case 22: return "Current_Helium_Level"; /* HGST / Hitachi Helium Drives */
case 168: return "SATA_Phy_Error_Count";
case 169: return "Remaining_Lifetime_SSD"; /* Kingston / Phison Controllers */
case 170: return "Bad_Block_Count_SSD";
case 171: return "Program_Fail_Count_SSD";
case 172: return "Erase_Fail_Count_SSD";
case 173: return "Wear_Leveling_Count"; /* Universal SSD Wear Counter */
case 174: return "Unexpected_Power_Loss";
case 175: return "Power_Loss_Protect_Fail"; /* Intel SSD Capacitor Monitor */
case 176: return "Erase_Pool_Block_Count";
case 177: return "Wear_Range_Delta";
case 179: return "Used_Rsvd_Blk_Cnt_Tot";
case 180: return "Unused_Reserved_Blk_Cnt";
case 181: return "Program_Fail_Count_SSD";
case 182: return "Erase_Fail_Count_SSD";
case 183: return "SATA_Downshift_Error_Ct";
case 184: return "End-to-End_Error";
case 187: return "Reported_Uncorrect";
case 188: return "Command_Timeout";
case 189: return "High_Fly_Writes";
case 190: return "Airflow_Temperature_Cel";
case 192: return "Power-Off_Retract_Count";
case 193: return "Load_Cycle_Count";
case 194: return "Temperature_Celsius";
case 195: return "Hardware_ECC_Recovered";
case 196: return "Reallocation_Event_Ct";
case 197: return "Current_Pending_Sector"; /* Crucial HDD Health Marker */
case 198: return "Offline_Uncorrectable";
case 199: return "UDMA_CRC_Error_Count"; /* Interface/Cable Error Counter */
case 200: return "Write_Error_Rate"; /* WD / Multi-vendor HDD Health */
case 201: return "Soft_Read_Error_Rate";
case 202: return "Percentage_Used_Life"; /* SSD Remaining Life % */
case 218: return "SATA_CRC_Error_Count";
case 231: return "SSD_Life_Left";
case 232: return "Available_Reserv_Space"; /* SSD Available Reserved Flash % */
case 233: return "Media_Wearout_Indicator";
case 234: return "NAND_Writes_GiB"; /* Silicon Motion / SandForce SSD */
case 235: return "POR_Recovery_Count"; /* Samsung SSD Panic Recovery Count */
case 240: return "Head_Flying_Hours";
case 241: return "Total_LBAs_Written"; /* NAND TBW Metric */
case 242: return "Total_LBAs_Read"; /* Host Reads Metric */
case 243: return "Total_LBAs_Written_Exp"; /* Expanded Write Counter */
case 244: return "Thermal_Throttle_Count"; /* SSD Overheating Throttling Events */
default: return "Vendor_Specific";
}
switch (id) {
case 1: return "Raw_Read_Error_Rate";
case 2: return "Throughput_Performance";
case 3: return "Spin_Up_Time";
case 4: return "Start_Stop_Count";
case 5: return "Reallocated_Sector_Ct";
case 7: return "Seek_Error_Rate";
case 8: return "Seek_Time_Performance";
case 9: return "Power_On_Hours";
case 10: return "Spin_Retry_Count";
case 12: return "Power_Cycle_Count";
case 16: return "Helium_Level_WD";
case 22: return "Current_Helium_Level"; /* HGST / Hitachi Helium Drives */
case 168: return "SATA_Phy_Error_Count";
case 169: return "Remaining_Lifetime_SSD"; /* Kingston / Phison Controllers */
case 170: return "Bad_Block_Count_SSD";
case 171: return "Program_Fail_Count_SSD";
case 172: return "Erase_Fail_Count_SSD";
case 173: return "Wear_Leveling_Count"; /* Universal SSD Wear Counter */
case 174: return "Unexpected_Power_Loss";
case 175: return "Power_Loss_Protect_Fail"; /* Intel SSD Capacitor Monitor */
case 176: return "Erase_Pool_Block_Count";
case 177: return "Wear_Range_Delta";
case 179: return "Used_Rsvd_Blk_Cnt_Tot";
case 180: return "Unused_Reserved_Blk_Cnt";
case 181: return "Program_Fail_Count_SSD";
case 182: return "Erase_Fail_Count_SSD";
case 183: return "SATA_Downshift_Error_Ct";
case 184: return "End-to-End_Error";
case 187: return "Reported_Uncorrect";
case 188: return "Command_Timeout";
case 189: return "High_Fly_Writes";
case 190: return "Airflow_Temperature_Cel";
case 192: return "Power-Off_Retract_Count";
case 193: return "Load_Cycle_Count";
case 194: return "Temperature_Celsius";
case 195: return "Hardware_ECC_Recovered";
case 196: return "Reallocation_Event_Ct";
case 197: return "Current_Pending_Sector"; /* Crucial HDD Health Marker */
case 198: return "Offline_Uncorrectable";
case 199: return "UDMA_CRC_Error_Count"; /* Interface/Cable Error Counter */
case 200: return "Write_Error_Rate"; /* WD / Multi-vendor HDD Health */
case 201: return "Soft_Read_Error_Rate";
case 202: return "Percentage_Used_Life"; /* SSD Remaining Life % */
case 218: return "SATA_CRC_Error_Count";
case 231: return "SSD_Life_Left";
case 232: return "Available_Reserv_Space"; /* SSD Available Reserved Flash % */
case 233: return "Media_Wearout_Indicator";
case 234: return "NAND_Writes_GiB"; /* Silicon Motion / SandForce SSD */
case 235: return "POR_Recovery_Count"; /* Samsung SSD Panic Recovery Count */
case 240: return "Head_Flying_Hours";
case 241: return "Total_LBAs_Written"; /* NAND TBW Metric */
case 242: return "Total_LBAs_Read"; /* Host Reads Metric */
case 243: return "Total_LBAs_Written_Exp"; /* Expanded Write Counter */
case 244: return "Thermal_Throttle_Count"; /* SSD Overheating Throttling Events */
default: return "Vendor_Specific";
}
}
#endif /* _DISKINFO_SMART_H_ */

Loading…
Cancel
Save