Вторая жизнь китайского чудо-шнурка J2534

от автора

Давным-давно, когда деревья были большими и вариации комплектаций одной модели автомобиля можно было перечислить пальцами одной руки, был куплен диагностический адаптер, о котором сегодня пойдет речь. Творение неизвестного китайца получило название Mini-VCI J2534. Откуда он взялся доподлинно неизвестно, но позиционируется как интерфейс для работы с различными Тойотами, а так же как J2534 совместимый адаптер (спойлер — нет). В момент покупки его было достаточно для диагностики и ковыряния в мозгах автомобилей тех лет, но прогресс не стоит на месте и в нынешних реалиях он если так можно выразиться — «не вывозит». О том, можно ли с этим что-то сделать и пойдет речь ниже.

Итак, знакомьтесь — наш пациент снаружи и внутри:

Обратите внимание на маркировку микроконтроллера LPC2119, мы к ней еще вернемся.
Обратите внимание на маркировку микроконтроллера LPC2119, мы к ней еще вернемся.

Внутри него живет 16/32 bit ARM7TDMI-S™ CPU, пара CAN-контроллеров, 2 UART’а и еще кучка полезной и не очень периферии.

Суть проблемы

Если закрыть глаза на мелочи в виде почти полного несоответствия стандарту J2534, есть у него проблемы гораздо хуже, а именно невозможность отправлять данные по протоколу ISO-TP длиннее ~48 байт. С последним мириться было нельзя и в голове засела мысль, а что если получится сделать этот мир чуточку лучше.

Если кратко, как происходит передача данных длинной больше 8 байт по CAN-шине (длина сообщения CAN ограничена восемью байтами). Существует такой стандарт ISO15765, он же ISO-TP (Transport Protocol), который покрывает 2 модели OSI (сетевой и транспортный). Передача данных длиной более 7 байт выглядит так:

  1. Источник отправляет First Frame (FF) с данными об общей длине передаваемых данных и первыми 6 байтами payload’а.

  2. Приемник отвечает ему Flow Control фреймом, в котором говорит о минимальном допустимом времени между посылками CF (о них ниже) и количестве CF, после которого источник снова должен дождаться Flow Control фрейм.

  3. Источник после приема Flow Control’а продолжает отправку данных фреймами Consecutive Frame (CF) с заданным интервалом о ожиданием следующего Flow Control (если об этом было сказано в пункте 2)

    https://en.wikipedia.org/wiki/ISO_15765-2

Что происходит на самом деле и почему ничего не работает нам поможет выяснить обычный анализатор CAN шины (Can Hacker/PEAK CAN и иже с ними). Итак, картина маслом — все смешалось, кони, люди. Приемник сказал жди от меня каждые 8 Consecutive Frame’ов Flow Control и шли мне каждый Consecutive Frame не менее чем через 10 мс, а шнурок мало того, что проигнорировал ожидание FC, так еще и на минимальную задержку между CF не обратил внимания.

Flow Control от приемника — 30 08 0A FFFFFFFFFF, где 08 — количество CF, после которого источник снова должен дождаться Flow Control фрейм, 0A — минимальное допустимое время между посылками CF.

Что мы имеем по факту — задержка около 1мс между CF, вместо желаемой 10мс и отсутствие ожидания Flow Control, что полностью ломает весь процесс передачи.

Ну и ладно, подумаешь, организуем свой ISO-TP с задержками и таймингами, благо шнур позволяет работать с сырыми данными CAN и посмотрим что получилось (гадость какая)

В шнурке используется преобразователь USB-UART FT232, который имеет некоторые проблемы при работе с USB 3.0. И проблемы эти — конские задержки, которые не настраиваются из драйвера, хотя на USB 2.0 все работает, но где вы сейчас найдете честный контроллер USB 2.0 в матери/ноутбуке. В общем, ручное форматирование тоже отпадает, задержки между CF не поддаются критике, работать это тоже не будет.

Остается крайняя мера — залезть внутрь и попробовать исправить кривой софт костылями, насколько это возможно. Не знаю как, но прямо по USB из контроллера можно вычитать и записать флеш память даже без разборки шнурка с помощью программы Flash Magic. После чтения загружаем прошивку в IDA, процессор ARM Little Endian архитектура ARMv4T. Немного помощи руками, создание недостающих регионов и прошивка готова к исследованию.

Функция с реализацией отправки данных по ISO-TP была найдена от обратного (CAN периферия — отправка — обертка — сама функция). Что же по исходникам — вот кусок кода с отправкой данных. То, о чем говорилось выше не предусмотрено вообще никак.

iso_tp_fc_received_ptr = &ctx->iso_tp_fc_received; while (sended_len < send_len) {     if (ff_flag)     {         if (cf_counter >= 0xF)             cf_counter = 0;         else             ++cf_counter;         v21 = 8;         tx_data.data[0] = cf_counter + 0x20; // Сборка Consecutive frames         v23 = v21 - 1;         if (send_len - sended_len < v21 - 1)             v23 = send_len - sended_len;         memcpy(&tx_data.data[1], &send_data_[sended_len], v23);         can_tx_1(ctx, &tx_data);         sended_len += v23;     }     else     {         tx_data.data[0] = 0x10;         // Сборка First frame         tx_data.data[1] = send_len; // Больше 255 байт не предусмотрено, хотя по стандарту должно быть 4 с копейками кб, хотя о чем это я         memcpy(&tx_data.data[2], send_data_, 6));         cf_counter = 0;         set0(iso_tp_fc_received_ptr);         can_tx_1(ctx, &tx_data);         if (!wait_fc(ctx, 700)) // Ждем flow control             return 0;         ff_flag = 1;         sended_len += 6;     } }

Как видно, Flow Control шнурок ждет всего один раз, а дальше даже не пытается соответствовать ISO-TP. Как только он получит FC, сразу же без задержек начинает слать остатки данных в Consecutive Frame’ах. Ладно, но может он хотя бы обращает внимание на данные из Flow Control? Ха-ха. Нет. Вот функция обработки приема данных по ISO-TP, нас интересует только прием Flow Control.

header = rx_byte_0 & 0xF0; if (can_rx_ctx->rx_can_data[0] & 0xF0) {     switch (header)     {             //Тут были обработчики других заголовков, но они нам не нужны     case 0x30: //Flow control         set_1(&iso_tp_ctx->iso_tp_fc_received);         result = 0;         break;         } }

Как видим, просто выставляется флажок, что был принят какой-то flow control, а что там в нем нам не важно (мысли китайца).

Что же делать?

Дешево и сердито — засунуть простую задержку между отправкой Consecutive Frame’ов, чтобы приемник успевал отправить свой Flow Control там, где нужно и получил следующий CF уже после. Все что нам нужно, это найти место, в цикле с отправкой, куда можно засунуть переход в функцию с задержкой, благо мест таких полно, а замененные инструкции можно выполнить в новой функции, так что мы ничего не потеряем. Берем IAR, в нем есть поддержка именно такого процессора, чистый проект на ассемблере и пишем элементарный цикл

_my_func         STMFD   SP!, {R10-R12,LR}         LDR R10, =39062          ; ~7800 на 1 мс         B compare sub:         SUB     R10, R10, #1 compare:                 CMP     R10, #0         BGT sub                  MOV     R0, R4          ; та самая замененная инструкция на переход         LDMFD   SP!, {R10-R12,PC}

Конечный результат выглядит так — слева то, что было, справа то, что стало. Инструкция MOV R0, R4 перенесена.

Прошиваем и наслаждаемся прекрасной работой без сбоев.

Конечно, можно было сделать все по фен шую, и правильную обработку Flow Control фрейма, и честные задержки по желанию приемника, и ожидание остальных Flow Control’ов. Но результат в любом случае достигнут и терять время больше чем один вечер на такое желания нет.

Еще интересный момент — контроллер судя по всему китайский перемарк, т.к. определился программой по внутреннему ID как LPC2114, в котором, на минуточку, вообще нет CAN контроллера, если верить даташиту. Видишь CAN? И я не вижу, а он есть. Вот так вот.

Кому интересны прошивка и база IDA то вот. Пароль habr.com https://cloud.kolyandex.su/index.php/s/uLKakKEqZRMkltG


ссылка на оригинал статьи https://habr.com/ru/post/678610/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *