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;
}
#############################################################################