#1147538 smartmontools: cannot read smart data for 4k disk behind jms579 controller

Package:
smartmontools
Source:
smartmontools
Description:
control and monitor storage systems using S.M.A.R.T.
Submitter:
Marc Lehmann
Date:
2026-09-12 20:33:05 UTC
Severity:
normal
#1147538#5
Date:
2026-09-12 20:30:21 UTC
From:
To:
Dear Maintainer,

I have a 4Kn drive behind a JMS579 bridge (using the Zibri firmware with
default settings), and smartctl causes a timeout when used:

[1382412.928875] usb 2-9.3: Product: JMS579
[1382412.928878] usb 2-9.3: Manufacturer: JMICRON
[1382412.928881] usb 2-9.3: SerialNumber: A2524175
[1382412.940766] scsi host19: uas
[1382412.941315] scsi 19:0:0:0: Direct-Access     ST18000N M000J-2TV103     0414 PQ: 0 ANSI: 6
[1382412.943701] sd 19:0:0:0: Attached scsi generic sg8 type 0
[1382412.965435] sd 19:0:0:0: [sdj] 4394582016 4096-byte logical blocks: (18.0 TB/16.4 TiB)

   # smartctl -A /dev/sdj
   smartctl 7.4 2023-08-01 r5530 [x86_64-linux-6.12.100+deb13-amd64] (local build)
   Copyright (C) 2002-23, Bruce Allen, Christian Franke, www.smartmontools.org

   Read Device Identity failed: empty IDENTIFY data

   A mandatory SMART command failed: exiting. To continue, add one or more '-T permissive' options.
   [Exit 2]

This seems to be simply one of the many jmicron firmware bugs not being
abke to handle this 4K disk. However, I found if you request 512 bytes
instead of one block, it does work.

   # sg_raw -v -r 1 /dev/sg8 \
   >   85 08 0a 00 00 00 01 00 00 00 00 00 00 00 ec 00
       cdb to send: [85 08 0a 00 00 00 01 00 00 00 00 00 00 00 ec 00]
   >>> transport error: Host_status=0x07 [DID_ERROR]

   SCSI Status: Good

   Error 99 occurred, no data received
   Some other error
   [Exit 99]
   # sg_raw -v -r 512 /dev/sg8 \
   >   85 09 0a 00 00 02 00 00 00 00 00 00 00 00 ec 00
       cdb to send: [85 09 0a 00 00 02 00 00 00 00 00 00 00 00 ec 00]
   SCSI Status: Good

   Received 512 bytes of data:
    00     5a 0c ff 3f 37 c8 10 00  00 00 00 00 3f 00 00 00    Z..?7.......?...
    ...

As a proof of concept, this perl script works on this disk behind this
bridge, so should contain all info needed to add support/workaround,
if desired:

#############################################################################

#!/usr/bin/perl
use strict;
use warnings;

my $dev = shift // '/dev/sg8';

# Common-ish ATA SMART attribute names.
# Some Seagate attributes are vendor-specific; unknown ones are left numeric.
my %name = (
      1 => 'Raw_Read_Error_Rate',
      3 => 'Spin_Up_Time',
      4 => 'Start_Stop_Count',
      5 => 'Reallocated_Sector_Ct',
      7 => 'Seek_Error_Rate',
      9 => 'Power_On_Hours',
     10 => 'Spin_Retry_Count',
     12 => 'Power_Cycle_Count',
    187 => 'Reported_Uncorrect',
    188 => 'Command_Timeout',
    190 => 'Airflow_Temperature_Cel',
    192 => 'Power-Off_Retract_Count',
    193 => 'Load_Cycle_Count',
    194 => 'Temperature_Celsius',
    197 => 'Current_Pending_Sector',
    198 => 'Offline_Uncorrectable',
    199 => 'UDMA_CRC_Error_Count',
    200 => 'Write_Error_Rate',
    240 => 'Head_Flying_Hours',
    241 => 'Total_LBAs_Written',
    242 => 'Total_LBAs_Read',
);

sub run_512 {
    my (@cdb) = @_;

    die "internal error: CDB isn't 16 bytes\n" unless @cdb == 16;

    my @cmd = (
        'sg_raw',
        '-r', '512',
        '-o', '-',
        $dev,
        map { sprintf("%02x", $_) } @cdb
    );

    open(my $fh, '-|', @cmd)
        or die "cannot execute sg_raw: $!\n";

    binmode($fh);

    local $/;
    my $buf = <$fh>;
    $buf = '' unless defined $buf;

    close($fh);
    my $rc = $? >> 8;

    die "sg_raw failed, exit status $rc\n" if $rc;
    die "sg_raw returned ".length($buf)." bytes, expected 512\n"
        unless length($buf) == 512;

    return $buf;
}

sub identify {
    # Working 4Kn/JMS579 workaround:
    #
    # 85 = ATA PASS-THROUGH(16)
    # 09 = PIO DATA-IN + EXTEND
    # 0a = T_DIR=1, BYT_BLOK=0, T_LENGTH=sector count
    # sector count = 0x0200 bytes
    # ec = ATA IDENTIFY DEVICE

    return run_512(
        0x85, 0x09, 0x0a,
        0x00, 0x00,       # features
        0x02, 0x00,       # transfer length = 0x0200 bytes
        0x00, 0x00,       # LBA low
        0x00, 0x00,       # LBA mid
        0x00, 0x00,       # LBA high
        0x00,             # device
        0xec,             # IDENTIFY DEVICE
        0x00
    );
}

sub smart_page {
    my ($feature) = @_;

    # SMART command:
    # B0 = SMART
    # feature D0 = READ DATA
    # feature D1 = READ THRESHOLDS
    # LBA mid/high = 4f/c2 SMART signature
    #
    # Sector-count pair is deliberately 0x0200 because our SAT
    # transfer length is expressed in BYTES rather than blocks.

    return run_512(
        0x85, 0x09, 0x0a,
        0x00, $feature,
        0x02, 0x00,
        0x00, 0x00,
        0x00, 0x4f,
        0x00, 0xc2,
        0x00,
        0xb0,
        0x00
    );
}

sub ata_string {
    my ($buf, $first_word, $words) = @_;

    my $s = substr($buf, $first_word * 2, $words * 2);
    my $out = '';

    for (my $i = 0; $i < length($s); $i += 2) {
        $out .= substr($s, $i + 1, 1);
        $out .= substr($s, $i,     1);
    }

    $out =~ s/\x00//g;
    $out =~ s/^\s+//;
    $out =~ s/\s+$//;
    return $out;
}

sub checksum_ok {
    my ($buf) = @_;
    my $sum = 0;
    $sum = ($sum + $_) & 0xff for unpack('C*', $buf);
    return $sum == 0;
}

sub raw48 {
    my ($six) = @_;

    my @b = unpack('C6', $six);
    my $v = 0;

    for my $i (0..5) {
        $v |= $b[$i] << (8 * $i);
    }

    return $v;
}

sub raw48_hex {
    my ($six) = @_;
    return join('', map { sprintf('%02x', $_) } reverse unpack('C6', $six));
}

print "Device: $dev\n\n";

print "Reading IDENTIFY DEVICE...\n";
my $id = identify();

printf "Model:    %s\n", ata_string($id, 27, 20);
printf "Serial:   %s\n", ata_string($id, 10, 10);
printf "Firmware: %s\n", ata_string($id, 23, 4);

# IDENTIFY word 106 describes physical/logical sector information.
my $w106 = unpack('v', substr($id, 106 * 2, 2));
printf "IDENTIFY word 106: 0x%04x\n", $w106;

print "\nReading SMART DATA...\n";
my $smart = smart_page(0xd0);

print "Reading SMART THRESHOLDS...\n";
my $thresh = smart_page(0xd1);

printf "SMART data revision:      %u\n",
    unpack('v', substr($smart,  0, 2));

printf "SMART threshold revision: %u\n",
    unpack('v', substr($thresh, 0, 2));

printf "SMART data checksum:      %s\n",
    checksum_ok($smart) ? 'OK' : 'BAD';

printf "SMART threshold checksum: %s\n\n",
    checksum_ok($thresh) ? 'OK' : 'BAD';

# Build threshold table indexed by attribute ID.
my %threshold;

for my $n (0..29) {
    my $off = 2 + $n * 12;
    my $aid = ord(substr($thresh, $off, 1));

    next unless $aid;
    $threshold{$aid} = ord(substr($thresh, $off + 1, 1));
}

printf "%-3s %-25s %5s %5s %6s %-6s %14s  %s\n",
    'ID', 'ATTRIBUTE', 'CUR', 'WORST', 'THRESH', 'FLAGS',
    'RAW', 'RAW_HEX';

printf "%-3s %-25s %5s %5s %6s %-6s %14s  %s\n",
    '---', '-------------------------', '-----', '-----',
    '------', '------', '--------------', '------------';

for my $n (0..29) {
    my $off = 2 + $n * 12;

    my $aid = ord(substr($smart, $off, 1));
    next unless $aid;

    my $flags = unpack('v', substr($smart, $off + 1, 2));
    my $cur   = ord(substr($smart, $off + 3, 1));
    my $worst = ord(substr($smart, $off + 4, 1));
    my $rawb  = substr($smart, $off + 5, 6);
    my $raw   = raw48($rawb);
    my $rawhex = raw48_hex($rawb);

    my $nm = $name{$aid} // "Unknown_Attribute_$aid";

    my $thr =
        exists($threshold{$aid})
        ? $threshold{$aid}
        : '-';

    my $failed = '';
    if ($thr ne '-' && $thr != 0 && $cur <= $thr) {
        $failed = ' FAILING_NOW';
    }

    printf "%3u %-25s %5u %5u %6s %04x   %14u  %s%s\n",
        $aid, $nm, $cur, $worst, $thr, $flags,
        $raw, $rawhex, $failed;
}

#############################################################################