Обновление прошивки Toshiba NAS HDD под Linux

в 13:01, , рубрики: NAS, ruvds_перевод, toshiba hdd, прошивка hdd под linux, реверс-инжиниринг

Обновление прошивки Toshiba NAS HDD под Linux - 1


Я произвёл реверс-инжиниринг модуля обновления прошивки своего HDD Toshiba, чтобы иметь возможность обновлять её под Linux. Приведённые ниже команды должны работать, но используйте их на свой страх и риск:

$ wget https://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/ISFw.dat
$ grep -C2 MODELNAME ISFw.dat
 # ^
 # |___ здесь определяем нужное имя файла
$ wget https://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/sk060202.ftd
# hdparm --fwdownload-mode3 sk060202.ftd /dev/sdX

Предыстория

Недавно я приобрёл для своего NAS диск Toshiba HDWG480 HDD. Вот вывод команды hdparm -I /dev/XXX:

ATA device, with non-removable media
        Model Number:       TOSHIBA HDWG480
        Serial Number:      3430A00RFR0H
        Firmware Revision:  0601
        Transport:          Serial, ATA8-AST, SATA 1.0a, SATA II Extensions, SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0
Standards:
        Used: unknown (minor revision code 0x006d)
        Supported: 10 9 8 7 6 5
        Likely used: 10
[...]

Как обычно, я хотел проверить, есть ли для него доступные обновления прошивки. На сайте Toshiba для моей модели приводится версия 0602.

К сожалению, и вполне ожидаемо, для пользователей Linux возможности обновления нет. Производитель предоставляет лишь «Internal Storage Firmware Update Utility» для Windows.

Сами файлы обновления также отсутствуют.

▍ Цели

Итак, мои задачи:

  • Понять, откуда модуль обновлений получает свежие файлы.
  • Произвести реверс-инжиниринг процесса прошивки, чтобы переписать его под Linux.

Реверс-инжиниринг модуля обновления для Windows

▍ Начало

После успешного выполнения установщика1 с помощью Wine в каталоге Program Files (x86) появляются следующие файлы:

  18312 ISFw.exe:        PE32 executable (native) Intel 80386, for MS Windows, 4 sections
2434952 TosISFw.exe:     PE32 executable (GUI) Intel 80386, for MS Windows, 5 sections
2172296 TosISFwSvc.exe:  PE32 executable (GUI) Intel 80386, for MS Windows, 5 sections
2362248 TosISFwTray.exe: PE32 executable (GUI) Intel 80386, for MS Windows, 5 sections

Бегло просмотрев имена файлов и импорты, можно предположить следующее назначение перечисленных программ:

  • ISFW.exe — это драйвер (экспорт DriverEntry), вероятно, отвечающий за фактический процесс прошивки.
  • TosISFw.exe — это GUI.
  • TosISFwSvc.exe — это пользовательский сервис (на что указывают связанные с сервисом импорты).
  • TosISFwTray.exe — по всей видимости, отвечает за иконку в трее.

▍ Поиск файлов обновлений

Очевидным первым шагом было грепнуть URL в установленных бинарниках. К сожалению, это ничего не дало, кроме URL, связанных с цифровыми подписями. А вот грепнув API HttpOpenRequest, часто используемый программами Windows для скачивания файлов, я получил два результата: TosISFw.exe и TosISFwSvc.exe.

Давайте заглянем в более мелкий из них — TosISFwSvc.exe — поищем URL, проверив перекрёстные ссылки.

Вызов HttpOpenRequest находится в функции по адресу 0x00401040 и выглядит так:

v15 = HttpOpenRequestW(v14, L"GET", &v36[(_DWORD)lpBuffer], 0, (LPCWSTR)szReferrer, 0, 0x84000000, 0);

Очевидно, что эта функция участвует в «скачивании», на что указывают все вызовы API. Переименуем её в dlfile. На dlfile есть всего две перекрёстных ссылки:

if ( !RegOpenKeyExW(
        HKEY_LOCAL_MACHINE,
        L"SYSTEM\CurrentControlSet\Services\TosISFwSvc",
        0,
        0x20019u,
        &phkResult)
  && readregstring((LPBYTE)&String, &phkResult, L"FwURL")
  && lstrlenW(&String) )
{
  sub_401000();
  LOBYTE(v47) = 2;
  if ( dlfile(&String, (int)v38) )


[...]

sub_4052E0(&lpValueName, L"%s%d", L"URL", phkResult);
v25 = 0;
if ( !RegOpenKeyExW(
        HKEY_LOCAL_MACHINE,
        L"SYSTEM\CurrentControlSet\Services\TosISFwSvc",
        0,
        0x20019u,
        &v25)
  && readregstring((LPBYTE)&String, &v25, lpValueName)
  && lstrlenW(&String)
  && dlfile(&String, (int)v36) )

Первая даёт нам искомый ответ: URL хранится в реестре. По факту он записывается установщиком InstallShield.

Обновление прошивки Toshiba NAS HDD под Linux - 2

Вот его значение: http://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/ISFw.dat

▍ Парсинг файла обновления

Это файл ini, который несложно прочесть и спарсить:

[VERS]
VERSION="20240513"
[Firmware]
0000=qa060378.ftd
0000model="TOSHIBA HDWG21E"
0000rev="0603"
0000rev0000="0601"
0000native=0
0000option=0
0001=qa060378.ftd
0001model="TOSHIBA HDWG21C"
0001rev="0603"
0001rev0000="0601"
0001native=0
0001option=0
[...]
0008=sk060202.ftd
0008model="TOSHIBA HDWG480 "
0008rev="0602"
0008rev0000="0601"
0008native=0
0008option=0
[...]
; 905CBD24

В моём случае номер диска 8. Реальный же интерес здесь вызывает контрольная сумма в конце. Это CRC32 файла минус последние 10 байтов, которые можно легко проверить инструментами slice и crc32 из моего универсального набора для хакинга rsbkb:

$ slice -- ISFw.dat 0 -10 | crc32
905cbd24

Теперь, естественно, попробуем скачать нужный файл:

$ wget https://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/sk060202.ftd
Resolving www.canvio.jp (www.canvio.jp)... 23.72.248.205, 23.72.248.202
Connecting to www.canvio.jp (www.canvio.jp)|23.72.248.205|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1171456 (1.1M)
[...]

Чисто из любопытства можно проверить, сможет ли cpu_rec_rs определить какой-либо код в этом бинарнике:

$ ~/tools/cpu_rec_rs/cpu_rec_rs sk060202.ftd
Loading corpus from "/home/trou/tools/cpu_rec_rs/cpu_rec_corpus/*.corpus"
-------------------------------------------------
    File     |   Range    | Detected Architecture
-------------------------------------------------
sk060202.ftd | Whole file | ARMhf
-------------------------------------------------

Похоже, что прошивка выполняется на SoC ARM (так и есть).

▍ Разбор процесса обновления

Теперь надо понять, как этот файл отправляется на диск для выполнения обновления. Напомню, что у нас есть четыре бинарника, и мы видели, что драйвером является ISFW.exe.

Функция DriverEntry до боли проста:

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
  int v2; // eax

  readregistry();
  v2 = flashfirmware();
  sub_1001812(v2 % 100 == 0, 1, v2);
  return NtTerminateProcess((HANDLE)0xFFFFFFFF, 0);
}

Я уже переименовал readregistry и flashfirmware, так как эти функции определить легко:

char readregistry()
{
  [...]
  RtlInitUnicodeString(&DestinationString, L"\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\TosISFwSvc");
  [...]
  if ( NtOpenKey(&KeyHandle, 0x20019u, &ObjectAttributes) >= 0 )
  {
    RtlInitUnicodeString(&ValueName, L"FW_Serial");
    if ( NtQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, KeyValueInformation, 0x800u, &ResultLength) >= 0 )
    {
      memcpy(&fwserial, &KeyValueInformation[3], KeyValueInformation[2]);
       [...]
       RtlInitUnicodeString(&ValueName, L"FW_CurRev");
       memcpy(&fw_cur, &KeyValueInformation[3], KeyValueInformation[2]);
        [...]
        RtlInitUnicodeString(&ValueName, L"FW_NewRev");
        memcpy(fw_new, &KeyValueInformation[3], KeyValueInformation[2]);
          [...]
          RtlInitUnicodeString(&ValueName, L"FW_Model");
          memcpy(fw_model, &KeyValueInformation[3], KeyValueInformation[2]);
            [...]
            RtlInitUnicodeString(&ValueName, L"FW_FWFile");
            wmemcpy(path, L"\??\", 4);
            memcpy(&path + 4, &KeyValueInformation[3], KeyValueInformation[2]);
  [...]
}

Значения параметров реестра (устанавливаемые TosISFwSvc.exe) считываются и копируются в глобальные переменные, которые я переименовал в соответствии с названиями этих параметров.

Вот начало flashfirmware:

int flashfirmware()
{
  [...]
  Handle = 0;
  fwdata = 0;
  fwsize = 0;
  memset(&drivedata, 0, sizeof(drivedata));
  printf(L"%s Firmware: %s -> %sn", fw_model, &fw_cur, fw_new);
  printf(L"DO NOT TURN OFF THE PC WHILE ANY FIRMWARE UPDATE IS RUNNING.n");
  printf(
    L"Your device may become unusable if you do this and Toshiba is not n"
     "responsible for any damage, including any necessary replacement of n"
     "the unit, caused by your doing so.n");
  HeapHandle = RtlCreateHeap(2u, 0, 0, 0, 0, 0);
  if ( HeapHandle )
  {
    status = readfile(&path, &fwdata, &fwsize);
    if ( !(status % 100) )
    {
      Handle = verifydisk(&fwserial, &fw_cur, fw_model, &drivedata);
[...]

При своей относительной простоте функция verifydisk очень важна (здесь уже всё переименовано):

HANDLE __stdcall verifydisk(PCWSTR serial, PCWSTR cur, WCHAR *model, IDENTIFY_DEVICE_DATA *devdata)
{
  HANDLE hdl; // edi
  UNICODE_STRING cur_; // [esp+10h] [ebp-104h] BYREF
  struct _UNICODE_STRING serial_; // [esp+18h] [ebp-FCh] BYREF
  UNICODE_STRING model_from_drive_u; // [esp+20h] [ebp-F4h] BYREF
  UNICODE_STRING serial_from_drive_u; // [esp+28h] [ebp-ECh] BYREF
  UNICODE_STRING model_; // [esp+30h] [ebp-E4h] BYREF
  UNICODE_STRING fwrev_from_drive_u; // [esp+38h] [ebp-DCh] BYREF
  DWORD *drivenumber; // [esp+40h] [ebp-D4h]
  HANDLE hdl_; // [esp+44h] [ebp-D0h]
  char v14; // [esp+4Bh] [ebp-C9h] BYREF
  WCHAR model_from_drive[50]; // [esp+4Ch] [ebp-C8h] BYREF
  WCHAR serial_from_drive[30]; // [esp+B0h] [ebp-64h] BYREF
  WCHAR fwrev_from_drive[18]; // [esp+ECh] [ebp-28h] BYREF

  [...]
  for ( drivenumber = 0; (unsigned int)drivenumber < 0x20; drivenumber = (DWORD *)((char *)drivenumber + 1) )
  {
    [...]
    hdl = opendrive((char)drivenumber);
    if ( !hdl )
      break;
    if ( !getdevprop(hdl, &bustype) || bustype == BusTypeUsb ) {
      NtClose(hdl);
    } else {
      if ( get_IDENTIFY_DEVICE_DATA(hdl_, devdata, 0x200u) ) {
        get_drive_serial(devdata, serial_from_drive, 30, 1);
        get_drive_fw_rev(devdata, fwrev_from_drive, 18, 1);
        get_drive_model(devdata, model_from_drive, 50, 1);
        RtlInitUnicodeString(&serial_from_drive_u, serial_from_drive);
        RtlInitUnicodeString(&fwrev_from_drive_u, fwrev_from_drive);
        RtlInitUnicodeString(&model_from_drive_u, model_from_drive);
        if ( RtlEqualUnicodeString(&serial_, &serial_from_drive_u, 0) )
        {
          if ( RtlEqualUnicodeString(&cur_, &fwrev_from_drive_u, 0)
            && RtlEqualUnicodeString(&model_, &model_from_drive_u, 0) )
          {
            return hdl_;
          }
        }
      }
      NtClose(hdl_);
    }
  }
  return 0;
}

▍ Взаимодействие с диском

▍ Проверка типа диска

Заглянем в функции opendrive и getdevprop:

HANDLE __stdcall opendrive(char Args)
{
  [...]
  HANDLE FileHandle; // [esp+30h] [ebp-88h] BYREF
  WCHAR SourceString[64]; // [esp+34h] [ebp-84h] BYREF

  DestinationString.Length = 0;
  *(_DWORD *)&DestinationString.MaximumLength = 0;
  HIWORD(DestinationString.Buffer) = 0;
  memset(SourceString, 0, sizeof(SourceString));
  FileHandle = 0;
  wsprintf(SourceString, 64, (wchar_t *)L"\??\PhysicalDrive%u", Args);
  RtlInitUnicodeString(&DestinationString, SourceString);
  [...]
  NtOpenFile(&FileHandle, 0x100003u, &ObjectAttributes, &IoStatusBlock, 3u, 0x20u);
  return FileHandle;
}

char __stdcall getdevprop(HANDLE hdl, char *bustype)
{
  char tmp; // al
  struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+Ch] [ebp-1018h] BYREF
  char *bustype_; // [esp+14h] [ebp-1010h]
  HANDLE FileHandle; // [esp+18h] [ebp-100Ch]
  char retvalue; // [esp+1Fh] [ebp-1005h]
  STORAGE_DEVICE_DESCRIPTOR InputBuffer; // [esp+20h] [ebp-1004h] BYREF

  FileHandle = hdl;
  bustype_ = bustype;
  IoStatusBlock.Status = 0;
  IoStatusBlock.Information = 0;
  retvalue = 0;
  memset(&InputBuffer, 0, 0x1000u);
  if ( NtDeviceIoControlFile( hdl, 0, 0, 0, &IoStatusBlock,
        IOCTL_STORAGE_QUERY_PROPERTY,
         &InputBuffer, 0x1000u, &InputBuffer, 0x1000u) < 0 ) {
    tmp = 0;
  } else {
    tmp = InputBuffer.BusType;
    retvalue = 1;
  }
  if ( bustype_ )
    *bustype_ = tmp;
  return retvalue;
}

opendrive возвращает для указанного PhysicalDrive дескриптор, который затем использует функция NtDeviceIoControlFile, являющаяся частью getdevprop. С помощью «standard enums» из IDA (Interactive Disassembler) я переотобразил значение 0x2D1400 в читабельное определение: IOCTL_STORAGE_QUERY_PROPERTY.

Так как InputBuffer перед вызовом устанавливается на 0, в качестве данных возвращается структура STORAGE_DEVICE_DESCRIPTOR, которую verifydisk использует для проверки, не подключён ли диск по USB (BusTypeUsb), и прекращает процесс, если это так.

▍ Проверка модели диска

После этого verifydisk вызывает get_IDENTIFY_DEVICE_DATA:

char __stdcall get_IDENTIFY_DEVICE_DATA(HANDLE hdl, void *buff, size_t Size)
{
  struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+Ch] [ebp-3Ch] BYREF
  HANDLE FileHandle; // [esp+14h] [ebp-34h]
  char v6; // [esp+1Bh] [ebp-2Dh]
  ATA_PASS_THROUGH_DIRECT InputBuffer; // [esp+1Ch] [ebp-2Ch] BYREF

  FileHandle = hdl;
  IoStatusBlock.Status = 0;
  v6 = 0;
  IoStatusBlock.Information = 0;
  memset(buff, 0, Size);
  memset(&InputBuffer, 0, sizeof(InputBuffer));
  InputBuffer.Length = 0x28;
  InputBuffer.AtaFlags = ATA_FLAGS_DRDY_REQUIRED|ATA_FLAGS_DATA_IN|ATA_FLAGS_NO_MULTIPLE;
  InputBuffer.DataTransferLength = Size;
  InputBuffer.TimeOutValue = 10;
  InputBuffer.DataBuffer = buff;
  InputBuffer.CurrentTaskFile[reg_Command] = 0xEC;
  if ( NtDeviceIoControlFile(hdl, 0, 0, 0, &IoStatusBlock,
         IOCTL_ATA_PASS_THROUGH_DIRECT,
         &InputBuffer, 0x28u, &InputBuffer, 0x28u) >= 0
    && (InputBuffer.CurrentTaskFile[reg_Status] & 9) == 0 )
  {
    return 1;
  }
  return v6;
}

Теперь NtDeviceIoControlFile используется со структурой IOCTL_ATA_PASS_THROUGH_DIRECT, которая, как и предполагает её имя, отправляет диску голую команду ATA. Разобраться здесь сложновато, так как ATA_PASS_THROUGH_DIRECT посредством поля CurrentTaskFile устанавливает оба буфера данных и «регистры».

CurrentTaskFile — это массив, используемый для индексирования 8 регистров, входных и выходных. Опираясь на документацию, можно создать два перечисления для использования в IDA:

enum ATA_INPUT_REGISTERS : __int32
{
  reg_Features = 0x0,
  reg_Sector_Count_in = 0x1,
  reg_Sector_Number_in = 0x2,
  reg_Cylinder_Low_in = 0x3,
  reg_Cylinder_High_in = 0x4,
  reg_Device_Head_in = 0x5,
  reg_Command = 0x6,
  reg_Reserved = 0x7,
};

enum ATA_OUTPUT_REGISTERS : __int32
{
  reg_Error = 0x0,
  reg_Sector_Count_out = 0x1,
  reg_Sector_Number_out = 0x2,
  reg_Cylinder_Low_out = 0x3,
  reg_Cylinder_High_out = 0x4,
  reg_Device_Head_out = 0x5,
  reg_Status = 0x6,
  reg_Reserved_out = 0x7,
};

Итак, командой здесь выступает 0xEC, почитать о которой можно в документации. В спецификации набора команд ATA/ATAPI описывается IDENTIFY DEVICE — ECh, PIO Data-In, которая возвращает много данных. К счастью, Microsoft предоставляет нам структуру IDENTIFY_DEVICE_DATA, где есть всё.

Далее приведённый ниже код проверяет, «правильный» ли у нас диск, сравнивая серию, номер и версию прошивки из возвращённых данных с сохранёнными в реестре.

int __stdcall get_drive_serial(IDENTIFY_DEVICE_DATA *drivedata, wchar_t *dest, int destlen, char stripflag)
{
  return (int)getdrive_data_string( drivedata, dest, destlen,
                offsetof(IDENTIFY_DEVICE_DATA, SerialNumber), 20,
                stripflag);
}

  [...]
  get_drive_serial(devdata, serial_from_drive, 30, 1);
  [...]
  RtlInitUnicodeString(&serial_from_drive_u, serial_from_drive);
  [...]
  if ( RtlEqualUnicodeString(&serial_, &serial_from_drive_u, 0) )
        {
          if ( RtlEqualUnicodeString(&cur_, &fwrev_from_drive_u, 0)
            && RtlEqualUnicodeString(&model_, &model_from_drive_u, 0) )

▍ Отправка файла прошивки

После того, как драйвер определил диск и убедился, что его можно прошить, он приступает к самому процессу:

[...]
      MaxBlocksPerDownloadMicrocodeMode03 = drivedata.MaxBlocksPerDownloadMicrocodeMode03;
      if ( !drivedata.MaxBlocksPerDownloadMicrocodeMode03 || drivedata.MaxBlocksPerDownloadMicrocodeMode03 == 0xFFFF ) {
        MaxBlocksPerDownloadMicrocodeMode03 = 128;
      } else if ( drivedata.MaxBlocksPerDownloadMicrocodeMode03 >= 0x80u ) {
        MaxBlocksPerDownloadMicrocodeMode03 = 128;
      }
      if ( MaxBlocksPerDownloadMicrocodeMode03 >= drivedata.MinBlocksPerDownloadMicrocodeMode03
        && MaxBlocksPerDownloadMicrocodeMode03 ) {
        fwblocks = fwsize >> 9;
        fwblocks2 = fwsize >> 9;
        v1 = 60;
        do {
          printprogress();
          wait((LARGE_INTEGER)500LL);
          --v1;
        } while ( v1 );
        for ( fwsize = 0; (int)fwsize < 30; ++fwsize ) {
          currentblock = 0;
          status = 6000;
          if ( fwblocks ) {
            fwdata1 = fwdata;
            MaxBytesPerDL = MaxBlocksPerDownloadMicrocodeMode03 << 9;
            while ( 1 ) {
              printprogress();
              blocks_to_flash = fwblocks2 - currentblock;
              if ( MaxBlocksPerDownloadMicrocodeMode03 < fwblocks2 - currentblock )
                blocks_to_flash = MaxBlocksPerDownloadMicrocodeMode03;
              if ( !ATA_CMD_DOWNLOAD_MICRO(Handle, currentblock, blocks_to_flash, fwdata1) )
                break;
              currentblock += MaxBlocksPerDownloadMicrocodeMode03;
              fwdata1 += MaxBytesPerDL;
              if ( currentblock >= fwblocks2 )
                goto LABEL_25;
            }
            status = 6009;
LABEL_25:
            fwblocks = fwblocks2;
          }
          if ( !(status % 100) )
            break;
          v5 = 2;
          do {
            printprogress();
            wait((LARGE_INTEGER)500LL);
            --v5;
          } while ( v5 );
        }
        if ( !(status % 100) )
        {
          if ( get_IDENTIFY_DEVICE_DATA(Handle, &drivedata, 0x200u) ) {
            get_drive_fw_rev(&drivedata, newfwrev, 18, 1);
            if ( wcsncmp(fw_new, newfwrev, wcslen(fw_new)) )
              status = 6011;
          } else {
            status = 6010;
          }
        }
      } else {
LABEL_35:
        status = 6006;
      }
  [...]
  if ( status % 100 )
    printf(L"Update Failed.      n");
  else
    printf(L"Update Succeeded.   n");

Здесь мы видим, что модуль обновления проверяет интересное поле из данных о диске: MaxBlocksPerDownloadMicrocodeMode03. Посмотрим, что оно означает.

▍ Отправка команд ATA для обновления прошивки

▍ Документация

Приведённая ниже выдержка из спецификации набора команд ATA отражает смысл этого поля:

A.11.5.3.4 Поле DM MAXIMUM TRANSFER SIZE
Если:

a) значение поля DM MAXIMUM TRANSFER SIZE (см. таблицу A.30) больше нуля;
b) значение поля DM MAXIMUM TRANSFER SIZE меньше FFFFh;
c) на один установлен бит DOWNLOAD MICROCODE SUPPORTED (см. A.11.5.2.20) или бит DOWNLOAD MICROCODE DMA SUPPORTED (см. A.11.5.2.6); и
d) на один установлен бит DM OFFSETS DEFERRED SUPPORTED (см. A.11.5.3.1) или бит DM OFFSETS IMMEDIATE SUPPORTED (см. A.11.5.3.3), тогда поле DM MAXIMUM TRANSFER SIZE указывает максимальное число блоков данных размером 512 байт, допускаемое командой DOWNLOAD MICROCODE (см. 7.7) или командой DOWNLOAD MICROCODE DMA (см. 7.8), которая устанавливает подкоманду:

a) Скачивания со смещениями и сохранения микрокода для использования непосредственно после загрузки или в будущем (то есть 03h); или
b) Скачивания со смещениями и сохранения микрокода для будущего использования (то есть 0Eh).

В противном случае максимум не указывается (то есть нет предела количества блоков данных размером 512 байт).

Данные IDENTIFY DEVICE содержат копию поля DM MAXIMUM TRANSFER SIZE (см. данные IDENTIFY DEVICE, слово 235 в таблице 45).

Естественно, я хочу проверить эту команду DOWNLOAD MICROCODE:

Команда DOWNLOAD MICROCODE позволяет хосту изменять микрокод устройства. Данные, передаваемые с помощью команды DOWNLOAD MICROCODE и команды DOWNLOAD MICROCODE DMA в зависимости от поставщика отличаются.
[...]
Скачивание и активация микрокода включает следующие шаги:

1) скачивание: хост передаёт обновлённый микрокод на устройство в одной ли более команд DOWNLOAD
MICROCODE или DOWNLOAD MICROCODE DMA;
2) сохранение: когда все обновлённые данные микрокода получены, устройство сохраняет эти обновлённые данные в энергонезависимом хранилище, если это установлено режимом скачивания микрокода;
3) активация: устройство начинает использовать сохранённые данные или откладывает их использование до события, установленного режимом скачивания микрокода, после чего они становятся активными.

Поле BLOCK COUNT указывает количество блоков данных размером 512 байт, которые будут переданы. Поле BLOCK COUNT устанавливается в полях COUNT и LBA (см. таблицу 37).

Подкоманды DOWNLOAD по факту определяют поведение обновления:

Обновление прошивки Toshiba NAS HDD под Linux - 3

▍ Фактический код

char ATA_CMD_DOWNLOAD_MICRO(HANDLE FileHandle, __int16 currentblock, int blocks_to_flash, void *fwdata)
{
  struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+Ch] [ebp-38h] BYREF
  char v6; // [esp+17h] [ebp-2Dh]
  ATA_PASS_THROUGH_DIRECT InputBuffer; // [esp+18h] [ebp-2Ch] BYREF

  IoStatusBlock.Status = 0;
  IoStatusBlock.Information = 0;
  memset(&InputBuffer, 0, sizeof(InputBuffer));
  InputBuffer.Length = 0x28;
  InputBuffer.AtaFlags = ATA_FLAGS_DRDY_REQUIRED|ATA_FLAGS_DATA_OUT|ATA_FLAGS_NO_MULTIPLE;
  *(_WORD *)&InputBuffer.CurrentTaskFile[reg_Sector_Count_in] = blocks_to_flash;// BLOCK COUNT
  *(_WORD *)&InputBuffer.CurrentTaskFile[reg_Cylinder_Low_in] = currentblock;// BUFFER OFFSET
  v6 = 0;
  InputBuffer.DataTransferLength = blocks_to_flash << 9;
  InputBuffer.TimeOutValue = 70;
  InputBuffer.DataBuffer = fwdata;
  InputBuffer.CurrentTaskFile[reg_Features] = 3;// mode 3
  InputBuffer.CurrentTaskFile[reg_Device_Head_in] = 0xE0;// OBSOLETE7|N/A|OBSOLETE5
  InputBuffer.CurrentTaskFile[reg_Command] = IDE_COMMAND_DOWNLOAD_MICROCODE;
  if ( NtDeviceIoControlFile(FileHandle, 0, 0, 0, &IoStatusBlock,
         IOCTL_ATA_PASS_THROUGH_DIRECT,
         &InputBuffer, 0x28u, &InputBuffer, 0x28u) >= 0
    && (InputBuffer.CurrentTaskFile[6] & 9) == 0 )// status
  {
    return 1;
  }
  return v6;
}

Как видите, структура ATA_CMD_DOWNLOAD_MICRO просто следует спецификации. Единственным странным моментом здесь является регистр Device, который, по сути, является устаревшим, но тут установлен на 0xE0. Для пущей уверенности я заглянул в исходный код hdparm, чтобы увидеть значение, установленное в этой команде. И действительно, там оно тоже установлено на 0xE0, значит это наверняка какой-то легаси-мусор:

enum {
	ATA_USING_LBA		= (1 << 6),
	ATA_STAT_DRQ		= (1 << 3),
	ATA_STAT_ERR		= (1 << 0),
};

    [...]
	r->lob.dev   = 0xa0 | ATA_USING_LBA;

Заключение

Итак, по сути, модуль обновления делает следующее:

  • скачивает список обновлений,
  • проверяет соответствие диска, устанавливает значения реестра,
  • передаёт эстафету драйверу, который:
    • проверяет, не подключён ли диск по USB,
    • с помощью команды IDENTIFY DEVICE убеждается, что это тот самый диск, который указан в реестре,
    • циклически отправляет обновление прошивки по 128 фрагментов размером 512 байт за раз, используя команду DOWNLOAD MICROCODE,
    • с помощью команды IDENTIFY DEVICE убеждается, что диск был обновлён.

▍ Наконец, обновление

Была не была — я решился обновить прошивку на своём основном диске NAS:

# hdparm -I /dev/sdb | grep Firmware
	Firmware Revision:  0601
# hdparm --fwdownload-mode3 sk060202.ftd --yes-i-know-what-i-am-doing --please-destroy-my-drive /dev/sdb
/dev/sdb:
fwdownload: xfer_mode=3 min=1 max=4224 size=512
...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
. Done.
# hdparm -I /dev/sdb | grep Firmware
	Firmware Revision:  0602

▍ Сноска

1. Версия 1.20.0410, MD5: 7cc7dc301f7b8a45cc56ee25e5707cc2, дата: 2023-12-27

Автор: Bright_Translate

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js