diff options
Diffstat (limited to 'dev_jmb39x_raid.cpp')
-rw-r--r-- | dev_jmb39x_raid.cpp | 741 |
1 files changed, 741 insertions, 0 deletions
diff --git a/dev_jmb39x_raid.cpp b/dev_jmb39x_raid.cpp new file mode 100644 index 0000000..c91ef41 --- /dev/null +++ b/dev_jmb39x_raid.cpp @@ -0,0 +1,741 @@ +/* + * dev_jmb39x_raid.cpp + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2019-22 Christian Franke + * + * Based on JMraidcon (same license): + * Copyright (C) 2010 Werner Johansson + * http://git.xnk.nu/?p=JMraidcon.git + * https://github.com/Vlad1mir-D/JMraidcon + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "dev_interface.h" +#include "dev_tunnelled.h" +#include "atacmds.h" +#include "scsicmds.h" +#include "sg_unaligned.h" +#include "static_assert.h" +#include "utility.h" + +#include <errno.h> + +const char * dev_jmb39x_raid_cpp_svnid = "$Id: dev_jmb39x_raid.cpp 5366 2022-04-30 15:26:22Z chrfranke $"; + +static void jmbassert_failed(int line, const char * expr) +{ + char msg[128]; + // Avoid __FILE__ as it may break reproducible builds + snprintf(msg, sizeof(msg), "dev_jmb39x_raid.cpp(%d): Assertion failed: %s", line, expr); + throw std::logic_error(msg); +} + +#define jmbassert(expr) (!(expr) ? jmbassert_failed(__LINE__, #expr) : (void)0) + +static void jmb_xor(uint8_t (& data)[512]) +{ + static const uint8_t xor_table[] = { + 0x08, 0xc1, 0x67, 0x44, 0x04, 0x91, 0x0d, 0x3d, 0x9c, 0x44, 0xdb, 0x61, 0xba, 0x63, 0x00, 0x5c, + 0x48, 0x78, 0xc4, 0x19, 0x9f, 0xc8, 0x8a, 0x1f, 0x8f, 0xa3, 0x7f, 0x83, 0x08, 0xcf, 0x7a, 0x71, + 0x89, 0xa4, 0x1d, 0xcd, 0xe7, 0xd2, 0x32, 0xe1, 0x27, 0xad, 0xd4, 0xfa, 0x0e, 0x03, 0x99, 0xeb, + 0xf7, 0x83, 0x50, 0x50, 0x11, 0x2d, 0x79, 0xbe, 0x3c, 0xb4, 0xf1, 0xe3, 0x8f, 0xd9, 0x3b, 0x9f, + 0xd9, 0xb0, 0xf3, 0x67, 0x87, 0x90, 0xe0, 0x5d, 0xff, 0xf9, 0xf0, 0x60, 0x61, 0x55, 0x1a, 0x2e, + 0x81, 0x52, 0xaf, 0x73, 0xee, 0x25, 0xad, 0xc7, 0x01, 0x6e, 0xce, 0x6b, 0x01, 0x8d, 0x49, 0x74, + 0x9c, 0x9e, 0xed, 0x7e, 0xe9, 0x3b, 0xf3, 0xa2, 0x8e, 0x45, 0xa0, 0x39, 0x0f, 0xcd, 0x96, 0x6b, + 0x90, 0x3c, 0xa7, 0xb4, 0x5a, 0x6f, 0x72, 0xba, 0x08, 0x6b, 0x58, 0x1f, 0x35, 0x42, 0x2a, 0xc6, + 0x4f, 0xf4, 0x51, 0xa2, 0xa1, 0x48, 0x6e, 0x89, 0xe9, 0x36, 0x6d, 0xc8, 0x3b, 0x12, 0xec, 0x3a, + 0xad, 0x89, 0x2f, 0x37, 0xab, 0x1a, 0xde, 0x63, 0x2f, 0xef, 0x74, 0xee, 0xc7, 0xa9, 0x51, 0xd1, + 0xae, 0x63, 0xad, 0x92, 0x1b, 0x78, 0x98, 0xf1, 0xb6, 0x40, 0xbb, 0xfa, 0x22, 0x07, 0xf3, 0x22, + 0x95, 0xb7, 0x46, 0xa3, 0xca, 0x2b, 0x16, 0x85, 0x40, 0x41, 0x0a, 0xc5, 0xf3, 0x61, 0xc7, 0xad, + 0x53, 0xfb, 0x1b, 0x65, 0xac, 0xc9, 0x55, 0xee, 0x73, 0xc1, 0x02, 0xa0, 0x29, 0xfe, 0x53, 0x15, + 0x8f, 0x1f, 0xad, 0x8d, 0x77, 0xde, 0x15, 0xef, 0x6b, 0xf3, 0x1b, 0xd8, 0x44, 0x96, 0xe3, 0xaa, + 0x5a, 0x2a, 0xdc, 0x10, 0x7b, 0x96, 0xda, 0x3c, 0x8b, 0xf2, 0x3d, 0x38, 0xa4, 0x81, 0xf3, 0x2c, + 0x58, 0x41, 0xf5, 0x54, 0x73, 0x45, 0x9d, 0x73, 0xc5, 0xfd, 0xe8, 0x2a, 0xbe, 0xc6, 0x30, 0x50, + 0x9e, 0x4f, 0x8f, 0xa0, 0x29, 0xed, 0x4a, 0xe9, 0x2f, 0x32, 0x03, 0xca, 0x13, 0xd8, 0x5b, 0x7a, + 0xae, 0x9d, 0x58, 0xe6, 0x88, 0x73, 0x22, 0x90, 0x0a, 0x43, 0x6c, 0x41, 0x5b, 0x17, 0xc4, 0x1a, + 0x27, 0x5e, 0xf9, 0xef, 0x63, 0x9f, 0x57, 0x23, 0x6c, 0x27, 0x97, 0x70, 0xf5, 0xa8, 0x5b, 0x7b, + 0x5d, 0xa9, 0x0f, 0x37, 0xae, 0xff, 0x8b, 0xb2, 0xc8, 0xca, 0xd9, 0x28, 0x8e, 0x5b, 0xb2, 0x46, + 0xbe, 0x80, 0x40, 0x38, 0xe4, 0xee, 0xbb, 0x2c, 0xd2, 0x82, 0xc1, 0x72, 0x5a, 0x11, 0x4f, 0x4b, + 0x54, 0xe2, 0xb9, 0xf1, 0x24, 0x96, 0x53, 0x3d, 0x33, 0x81, 0xf1, 0x50, 0x2e, 0x1a, 0x04, 0x71, + 0x80, 0xf9, 0xbf, 0x66, 0x69, 0x9c, 0x6f, 0x22, 0x44, 0xd0, 0x69, 0xbb, 0xad, 0x93, 0x84, 0x98, + 0x74, 0xaf, 0x67, 0x32, 0xb9, 0x8f, 0x65, 0xf3, 0x4b, 0x0f, 0xf4, 0x85, 0xef, 0xb5, 0xba, 0xff, + 0xe1, 0xda, 0x9e, 0x9e, 0x32, 0x96, 0xa9, 0x19, 0xb8, 0x4f, 0x43, 0xf7, 0xf6, 0x4c, 0x1c, 0x0f, + 0xce, 0xd2, 0x67, 0xb6, 0xe3, 0xe3, 0x8d, 0x27, 0x1e, 0x27, 0x98, 0x4c, 0x73, 0x37, 0x5c, 0xff, + 0xab, 0x16, 0xca, 0x64, 0x7d, 0x91, 0xc0, 0x6d, 0xae, 0x60, 0xf0, 0x1a, 0x43, 0x12, 0xe6, 0xf4, + 0xd6, 0xe8, 0xba, 0xc2, 0x9b, 0x2f, 0xe6, 0xce, 0x07, 0x08, 0x6a, 0x8d, 0x28, 0x62, 0xa7, 0x31, + 0xe9, 0x3d, 0x4b, 0x9b, 0x5b, 0x19, 0x18, 0x13, 0xd2, 0xa9, 0xc1, 0x08, 0xce, 0x62, 0x12, 0x8c, + 0x12, 0x64, 0xe3, 0x43, 0xbb, 0xe3, 0x59, 0x1c, 0x57, 0x7f, 0xcd, 0xb9, 0x72, 0x65, 0x47, 0xab, + 0xb8, 0xfe, 0x61, 0xc1, 0x08, 0xc2, 0xec, 0x25, 0x8e, 0xb9, 0x1c, 0x89, 0xdf, 0x6d, 0xd2, 0xa7, + 0x36, 0xa7, 0x10, 0x52, 0x2a, 0x21, 0x2d, 0xaa, 0x98, 0x31, 0xd1, 0x77, 0x35, 0xa8, 0x3b, 0x40, + }; + STATIC_ASSERT(sizeof(xor_table) == sizeof(data)); + + for (unsigned i = 0; i < sizeof(data); i++) { + data[i] ^= xor_table[i]; + } +} + +static uint32_t jmb_crc(const uint8_t (& data)[512]) +{ + static const uint32_t crc_table[] = { // Polynomial 0x04c11db7 + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + STATIC_ASSERT(sizeof(crc_table) == 256*sizeof(uint32_t)); + + uint32_t crc = 0x52325032; + for (unsigned i = 0; i < sizeof(data)/sizeof(uint32_t) - 1; i++) { + uint32_t dw = sg_get_unaligned_be32(data + i*sizeof(uint32_t)); + crc = crc_table[( dw & 0xff) ^ (crc >> 24)] ^ (crc << 8); + crc = crc_table[((dw>> 8) & 0xff) ^ (crc >> 24)] ^ (crc << 8); + crc = crc_table[((dw>>16) & 0xff) ^ (crc >> 24)] ^ (crc << 8); + crc = crc_table[( dw>>24 ) ^ (crc >> 24)] ^ (crc << 8); + } + return crc; +} + +static inline uint32_t jmb_get_crc(const uint8_t (& data)[512]) +{ + return sg_get_unaligned_le32(data + sizeof(data) - 4); +} + +static inline void jmb_put_crc(uint8_t (& data)[512], uint32_t crc) +{ + sg_put_unaligned_le32(crc, data + sizeof(data) - 4); +} + +static inline bool jmb_check_crc(const uint8_t (& data)[512]) +{ + return (jmb_get_crc(data) == jmb_crc(data)); +} + +static inline void jmb_put_le32(uint8_t (& data)[512], unsigned index, uint32_t val) +{ + jmbassert(index + 4 <= sizeof(data)); + sg_put_unaligned_le32(val, data + index); +} + +static void jmb_set_wakeup_sector(uint8_t (& data)[512], int id) +{ + uint32_t code = 0, crc = 0; + switch (id) { + case 0: code = 0x3c75a80b; crc = 0x706d10d9; break; + case 1: code = 0x0388e337; crc = 0x6958511e; break; + case 2: code = 0x689705f3; crc = 0xfe234b07; break; + case 3: code = 0xe00c523a; crc = 0x5be57adb; break; + default: jmbassert(false); + } + jmb_put_le32(data, 0, 0x197b0325); // WAKEUP_CMD + jmb_put_le32(data, 4, code); + memset(data + 8, 0, 8); + for (unsigned i = 16; i < sizeof(data) - 8; i++) + data[i] = (uint8_t)i; + jmb_put_le32(data, sizeof(data) - 8, 0x10eca1db); + jmb_put_crc(data, crc); +} + +static void jmb_set_request_sector(uint8_t (& data)[512], uint8_t version, uint32_t cmd_id, + const uint8_t * cmd, unsigned cmdsize) +{ + jmbassert(4 <= cmdsize && cmdsize <= 24); + memset(data, 0, sizeof(data)); + + uint32_t scrambled_cmd_code; + switch (version) { + default: + case 0: scrambled_cmd_code = 0x197b0322; break; // JMB39x: various devices + case 1: scrambled_cmd_code = 0x197b0393; break; // JMB39x: QNAP TR-004 NAS + case 2: scrambled_cmd_code = 0x197b0562; break; // JMS562 + } + jmb_put_le32(data, 0, scrambled_cmd_code); + + jmb_put_le32(data, 4, cmd_id); + memcpy(data + 8, cmd, cmdsize); + jmb_put_crc(data, jmb_crc(data)); +} + +static int jmb_get_sector_type(const uint8_t (& data)[512]) +{ + if (jmb_check_crc(data)) + return 1; // Plain (wakeup) sector + uint8_t data2[512]; + memcpy(data2, data, sizeof(data2)); + jmb_xor(data2); + if (jmb_check_crc(data2)) + return 2; // Obfuscated (request/response) sector + return 0; +} + +static void jmb_check_funcs() +{ + uint8_t data[512]; + jmb_set_wakeup_sector(data, 0); + jmbassert(jmb_check_crc(data)); + jmbassert(jmb_get_sector_type(data) == 1); + jmb_set_wakeup_sector(data, 1); + jmbassert(jmb_check_crc(data)); + jmb_set_wakeup_sector(data, 2); + jmbassert(jmb_check_crc(data)); + jmb_xor(data); + jmbassert(jmb_crc(data) == 0x053ed64b); + jmb_xor(data); + jmbassert(jmb_check_crc(data)); + jmb_set_wakeup_sector(data, 3); + jmbassert(jmb_check_crc(data)); + uint8_t cmd[] = {1, 2, 3, 4, 5, 6, 7}; + jmb_set_request_sector(data, 0, 42, cmd, sizeof(cmd)); + jmbassert(jmb_get_crc(data) == 0xb1f765d7); + jmbassert(jmb_check_crc(data)); + jmb_set_request_sector(data, 1, 42, cmd, sizeof(cmd)); + jmbassert(jmb_get_crc(data) == 0x388b2759); + jmbassert(jmb_check_crc(data)); + jmb_set_request_sector(data, 2, 42, cmd, sizeof(cmd)); + jmbassert(jmb_get_crc(data) == 0xde10952b); + jmbassert(jmb_check_crc(data)); + jmb_xor(data); + jmbassert(jmb_get_sector_type(data) == 2); +} + +///////////////////////////////////////////////////////////////////////////// + +static bool ata_read_lba8(ata_device * atadev, uint8_t lba8, uint8_t (& data)[512]) +{ + ata_cmd_in in; + in.in_regs.command = 0x20; // READ SECTORS, 28-bit PIO + in.set_data_in(data, 1); + in.in_regs.lba_low = lba8; + in.in_regs.lba_mid = 0; + in.in_regs.lba_high = 0; + in.in_regs.device = 0x40; // LBA mode | LBA bits 24-27 + if (!atadev->ata_pass_through(in)) + return false; + return true; +} + +static bool ata_write_lba8(ata_device * atadev, uint8_t lba8, const uint8_t (& data)[512]) +{ + ata_cmd_in in; + in.in_regs.command = 0x30; // WRITE SECTORS, 28-bit PIO + in.set_data_out(data, 1); + in.in_regs.lba_low = lba8; + in.in_regs.lba_mid = 0; + in.in_regs.lba_high = 0; + in.in_regs.device = 0x40; // LBA mode | LBA bits 24-27 + if (!atadev->ata_pass_through(in)) + return false; + return true; +} + +static int scsi_get_lba_size(scsi_device * scsidev) +{ + scsi_readcap_resp srr; memset(&srr, 0, sizeof(srr)); + if (!scsiGetSize(scsidev, false /*avoid_rcap16*/, &srr)) + return -1; + return srr.lb_size; +} + +static bool scsi_read_lba8(scsi_device * scsidev, uint8_t lba8, uint8_t (& data)[512]) +{ + struct scsi_cmnd_io io_hdr = {}; + + io_hdr.dxfer_dir = DXFER_FROM_DEVICE; + io_hdr.dxfer_len = 512; + io_hdr.dxferp = data; + uint8_t cdb[] = {0x28 /* READ(10) */, 0x00, 0x00, 0x00, 0x00, lba8, 0x00, 0x00, 0x01, 0x00}; + STATIC_ASSERT(sizeof(cdb) == 10); + io_hdr.cmnd = cdb; + io_hdr.cmnd_len = sizeof(cdb); + io_hdr.timeout = SCSI_TIMEOUT_DEFAULT; + + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "scsi_read_lba")) + return false; + return true; +} + +static bool scsi_write_lba8(scsi_device * scsidev, uint8_t lba8, const uint8_t (& data)[512]) +{ + struct scsi_cmnd_io io_hdr = {}; + + io_hdr.dxfer_dir = DXFER_TO_DEVICE; + io_hdr.dxfer_len = 512; + io_hdr.dxferp = const_cast<uint8_t *>(data); + uint8_t cdb[] = {0x2a /* WRITE(10) */, 0x00, 0x00, 0x00, 0x00, lba8, 0x00, 0x00, 0x01, 0x00}; + STATIC_ASSERT(sizeof(cdb) == 10); + io_hdr.cmnd = cdb; + io_hdr.cmnd_len = sizeof(cdb); + io_hdr.timeout = SCSI_TIMEOUT_DEFAULT; + + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "scsi_write_lba")) + return false; + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +namespace jmb39x { + +class jmb39x_device +: public tunnelled_device< + /*implements*/ ata_device, + /*by tunnelling through a ATA or SCSI*/ smart_device +> +{ +public: + jmb39x_device(smart_interface * intf, smart_device * smartdev, const char * req_type, + uint8_t version, uint8_t port, uint8_t lba, bool force); + + virtual ~jmb39x_device(); + + virtual bool open() override; + + virtual bool close() override; + + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override; + +private: + uint8_t m_version; + uint8_t m_port; + uint8_t m_lba; + bool m_force; + + bool m_blocked; + bool m_orig_write_back; + uint32_t m_cmd_id; + uint8_t m_orig_data[512]; + + bool raw_read(uint8_t (& data)[512]); + bool raw_write(const uint8_t (& data)[512]); + bool run_jmb_command(const uint8_t * cmd, unsigned cmdsize, uint8_t (& response)[512]); + void report_orig_data_lost() const; + bool restore_orig_data(); +}; + +jmb39x_device::jmb39x_device(smart_interface * intf, smart_device * smartdev, const char * req_type, + uint8_t version, uint8_t port, uint8_t lba, bool force) +: smart_device(intf, smartdev->get_dev_name(), req_type, req_type), + tunnelled_device<ata_device, smart_device>(smartdev), + m_version(version), m_port(port), m_lba(lba), m_force(force), + m_blocked(false), m_orig_write_back(false), m_cmd_id(0) +{ + set_info().info_name = strprintf("%s [jmb39x_disk_%u]", smartdev->get_info_name(), port); + memset(m_orig_data, 0, sizeof(m_orig_data)); +} + +jmb39x_device::~jmb39x_device() +{ + if (m_orig_write_back) try { + jmb39x_device::restore_orig_data(); + } catch (...) { + // ignore + } +} + +bool jmb39x_device::raw_read(uint8_t (& data)[512]) +{ + memset(data, 0, sizeof(data)); + if (get_tunnel_dev()->is_scsi()) { + if (!scsi_read_lba8(get_tunnel_dev()->to_scsi(), m_lba, data)) + return set_err(EIO, "SCSI READ LBA %d failed: %s", m_lba, get_tunnel_dev()->get_errmsg()); + } + else if (get_tunnel_dev()->is_ata()) { + if (!ata_read_lba8(get_tunnel_dev()->to_ata(), m_lba, data)) + return set_err(EIO, "ATA READ LBA %d failed: %s", m_lba, get_tunnel_dev()->get_errmsg()); + } + else { + jmbassert(false); + } + return true; +} + +bool jmb39x_device::raw_write(const uint8_t (& data)[512]) +{ + if (get_tunnel_dev()->is_scsi()) { + if (!scsi_write_lba8(get_tunnel_dev()->to_scsi(), m_lba, data)) + return set_err(EIO, "SCSI WRITE LBA %d failed: %s", m_lba, get_tunnel_dev()->get_errmsg()); + } + else if (get_tunnel_dev()->is_ata()) { + if (!ata_write_lba8(get_tunnel_dev()->to_ata(), m_lba, data)) + return set_err(EIO, "ATA WRITE LBA %d failed: %s", m_lba, get_tunnel_dev()->get_errmsg()); + } + else { + jmbassert(false); + } + return true; +} + +bool jmb39x_device::run_jmb_command(const uint8_t * cmd, unsigned cmdsize, uint8_t (& response)[512]) +{ + // Set up request + uint8_t request[512]; + jmb_set_request_sector(request, m_version, m_cmd_id, cmd, cmdsize); + + if (ata_debugmode) { + pout("JMB39x: Write request sector #%d\n", m_cmd_id); + if (ata_debugmode > 1) + dStrHex(request, sizeof(request), 0); + } + + // Write obfuscated request + jmb_xor(request); + if (!raw_write(request)) { + m_blocked = true; + return false; + } + jmb_xor(request); + + // Read obfuscated response + memset(response, 0, sizeof(response)); + if (!raw_read(response)) { + m_blocked = true; + return false; + } + jmb_xor(response); + + if (ata_debugmode) { + pout("JMB39x: Read response sector #%d\n", m_cmd_id); + if (ata_debugmode > 1) + dStrHex(response, sizeof(response), 0); + } + + // Check result + if (!memcmp(request, response, sizeof(request))) { // regular I/O? + m_blocked = true; + return set_err(EIO, "No JMB39x response detected"); + } + if (!jmb_check_crc(response)) { + m_blocked = true; + jmb_xor(response); + return set_err(EIO, "%s", (!jmb_check_crc(response) + ? "CRC error in JMB39x response" + : "JMB39x response contains a wakeup sector")); + } + if (memcmp(request, response, 8)) { // code + id identical? + m_blocked = true; + return set_err(EIO, "Invalid header in JMB39x response"); + } + + m_cmd_id++; + return true; +} + +void jmb39x_device::report_orig_data_lost() const +{ + bool zf = !nonempty(m_orig_data, sizeof(m_orig_data)); + pout("JMB39x: WARNING: Data (%szero filled) at LBA %d lost\n", (zf ? "" : "not "), m_lba); + if (!zf) // Dump lost data + dStrHex(m_orig_data, sizeof(m_orig_data), 0); +} + +bool jmb39x_device::restore_orig_data() +{ + if (ata_debugmode) + pout("JMB39x: Restore original sector (%szero filled)\n", + (nonempty(m_orig_data, sizeof(m_orig_data)) ? "not " : "")); + if (!raw_write(m_orig_data)) { + report_orig_data_lost(); + m_blocked = true; + return false; + } + return true; +} + +bool jmb39x_device::open() +{ + m_orig_write_back = false; + if (m_blocked) + return set_err(EIO, "Device blocked due to previous errors"); + + if (!tunnelled_device<ata_device, smart_device>::open()) + return false; + + // Check SCSI LBA size (assume 512 if ATA) + if (get_tunnel_dev()->is_scsi()) { + int lba_size = scsi_get_lba_size(get_tunnel_dev()->to_scsi()); + if (lba_size < 0) { + error_info err = get_tunnel_dev()->get_err(); + tunnelled_device<ata_device, smart_device>::close(); + return set_err(err.no, "SCSI READ CAPACITY failed: %s", err.msg.c_str()); + } + if (lba_size != 512) { + tunnelled_device<ata_device, smart_device>::close(); + return set_err(EINVAL, "LBA size is %d but must be 512", lba_size); + } + } + + // Read original data + if (ata_debugmode) + pout("JMB39x: Read original data at LBA %d\n", m_lba); + if (!raw_read(m_orig_data)) { + error_info err = get_err(); + tunnelled_device<ata_device, smart_device>::close(); + return set_err(err); + } + + // Check original data + if (nonempty(m_orig_data, sizeof(m_orig_data))) { + if (ata_debugmode > 1) + dStrHex(m_orig_data, sizeof(m_orig_data), 0); + int st = jmb_get_sector_type(m_orig_data); + if (!m_force) { + tunnelled_device<ata_device, smart_device>::close(); + m_blocked = true; + return set_err(EINVAL, "Original sector at LBA %d %s", m_lba, + (st == 0 ? "is not zero filled" : + st == 1 ? "contains JMB39x wakeup data" + : "contains JMB39x protocol data")); + } + if (st) { + // Zero fill to reset protocol state + if (ata_debugmode) + pout("JMB39x: Zero filling original data\n"); + memset(m_orig_data, 0, sizeof(m_orig_data)); + } + } + + // TODO: Defer SIGINT,... until close() + + // Write 4 wakeup sectors + uint8_t dataout[512]; + for (int id = 0; id < 4; id++) { + jmb_set_wakeup_sector(dataout, id); + if (ata_debugmode) { + pout("JMB39x: Write wakeup sector #%d\n", id+1); + if (ata_debugmode > 1) + dStrHex(dataout, sizeof(dataout), 0); + } + if (!raw_write(dataout)) { + error_info err = get_err(); + if (id > 0) + report_orig_data_lost(); + tunnelled_device<ata_device, smart_device>::close(); + m_blocked = true; + return set_err(err.no, "Write of JMB39x wakeup sector #%d: %s", id + 1, err.msg.c_str()); + } + } + m_orig_write_back = true; + + // start command sequence + m_cmd_id = 1; + + // Run JMB identify disk command + uint8_t b = (m_version != 1 ? 0x02 : 0x01); + uint8_t cmd[24]= { + 0x00, + b, b, + 0xff, + m_port, + 0x00, 0x00, 0x00, + m_port, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + uint8_t (& response)[512] = dataout; + if (!run_jmb_command(cmd, sizeof(cmd), response)) { + error_info err = get_err(); + close(); + return set_err(err); + } + + // Check for device model string + if (response[16] < ' ') { + close(); + return set_err(ENOENT, "No device connected to JMB39x port %d", m_port); + } + return true; +} + +bool jmb39x_device::close() +{ + bool ok = true; + if (m_orig_write_back) { + ok = restore_orig_data(); + m_orig_write_back = false; + } + + if (!tunnelled_device<ata_device, smart_device>::close()) + return false; + return ok; +} + +// Return: 0=unsupported, 1=supported, 2=supported and has checksum +static int is_supported_by_jmb(const ata_in_regs & r) +{ + switch (r.command) { + case ATA_IDENTIFY_DEVICE: + return 1; // Checksum is optional + case ATA_SMART_CMD: + switch (r.features) { + case ATA_SMART_READ_VALUES: + case ATA_SMART_READ_THRESHOLDS: + return 2; + case ATA_SMART_READ_LOG_SECTOR: + switch (r.lba_low) { + case 0x00: return 1; // Log directory + case 0x01: return 2; // Summary Error log + case 0xe0: return 1; // SCT Command/Status + } + break; + } + break; + } + return 0; +} + +bool jmb39x_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & /* out */) +{ + jmbassert(is_open()); + if (m_blocked) + return set_err(EIO, "Device blocked due to previous errors"); + if (in.direction == ata_cmd_in::no_data) // TODO: add to ata_cmd_is_supported() ? + return set_err(ENOSYS, "NO DATA ATA commands not implemented [JMB39x]"); + if (!ata_cmd_is_supported(in, 0, "JMB39x")) + return false; + // Block all commands which require full sector data + int supported = is_supported_by_jmb(in.in_regs); + if (!supported) + return set_err(ENOSYS, "ATA command not implemented due to truncated response [JMB39x]"); + jmbassert(in.direction == ata_cmd_in::data_in); + + // Run ATA pass-through command + uint8_t cmd[24]= { + 0x00, 0x02, 0x03, 0xff, + m_port, + 0x02, 0x00, 0xe0, 0x00, 0x00, + // Registers + in.in_regs.features, + 0x00, + in.in_regs.sector_count, + 0x00, + in.in_regs.lba_low, + 0x00, + in.in_regs.lba_mid, + 0x00, + in.in_regs.lba_high, + 0x00, + 0xa0, // in.in_regs.device ? + 0x00, + in.in_regs.command, + 0x00 // status register returned here + }; + uint8_t response[512]; + if (!run_jmb_command(cmd, sizeof(cmd), response)) + return false; + + // Check status register + uint8_t status = response[31]; + if (status == 0x00) { + m_blocked = true; + return set_err(EIO, "No device connected to JMB39x port %d", m_port); + } + if ((status & 0xc1) != 0x40 /* !(!BSY && DRDY && !ERR) */) + return set_err(EIO, "ATA command failed (status=0x%02x)", status); + + // Copy data + jmbassert(in.size == sizeof(response)); + memset(in.buffer, 0, in.size); + memcpy(in.buffer, response + 32, in.size - 32 - 16); + + // Prevent checksum warning + if (supported > 1) + ((uint8_t *)in.buffer)[512-1] -= checksum(in.buffer); + + return true; +} + +} // namespace jmb39x + +ata_device * smart_interface::get_jmb39x_device(const char * type, smart_device * smartdev) +{ + jmbassert(smartdev != 0); + // Take temporary ownership of 'smartdev' to delete it on error + smart_device_auto_ptr smartdev_holder(smartdev); + jmb_check_funcs(); + + // Base device must be ATA or SCSI + if (!(smartdev->is_ata() || smartdev->is_scsi())) { + set_err(EINVAL, "Type '%s+...': Device type '%s' is not ATA or SCSI", type, smartdev->get_req_type()); + return 0; + } + + int n1 = -1; + char prefix[15+1] = ""; + sscanf(type, "%15[^,],%n", prefix, &n1); + uint8_t version; + if (!strcmp(prefix, "jmb39x")) + version = 0; + else if (!strcmp(prefix, "jmb39x-q")) + version = 1; + else if (!strcmp(prefix, "jms56x")) + version = 2; + else + n1 = -1; + if (n1 < 0) { + set_err(EINVAL, "Unknown JMicron type '%s'", type); + return 0; + } + + // Use default LBA 33, same as JMraidcon. + // MBR disk: Zero filled if there is no boot code in boot area. + // GPT disk: Zero filled if GPT entries 125-128 are empty. + unsigned lba = 33; + + unsigned port = ~0; + bool force = false; + const char * args = type + n1; + n1 = -1; + sscanf(args, "%u%n", &port, &n1); + int n2 = -1, len = strlen(args); + if (0 < n1 && n1 < len && sscanf(args + n1, ",s%u%n", &lba, &n2) == 1 && n2 > 0) + n1 += n2; + n2 = -1; + if (0 < n1 && n1 < len && (sscanf(args + n1, ",force%n", &n2), n2) > 0) { + force = true; + n1 += n2; + } + if (!(n1 == len && port <= 4 && 1 <= lba && lba <= 255)) { + set_err(EINVAL, "Option -d %s,N[,sLBA][,force] must have 0 <= N <= 4 [, 1 <= LBA <= 255]", prefix); + return 0; + } + + ata_device * jmbdev = new jmb39x::jmb39x_device(this, smartdev, type, version, port, lba, force); + // 'smartdev' is now owned by 'jmbdev' + smartdev_holder.release(); + return jmbdev; +} |