Вторая жизнь китайского чудо-шнурка J2534
Давным-давно, когда деревья были большими и вариации комплектаций одной модели автомобиля можно было перечислить пальцами одной руки, был куплен диагностический адаптер, о котором сегодня пойдет речь. Творение неизвестного китайца получило название Mini-VCI J2534. Откуда он взялся доподлинно неизвестно, но позиционируется как интерфейс для работы с различными Тойотами, а так же как J2534 совместимый адаптер (спойлер — нет). В момент покупки его было достаточно для диагностики и ковыряния в мозгах автомобилей тех лет, но прогресс не стоит на месте и в нынешних реалиях он если так можно выразиться — «не вывозит». О том, можно ли с этим что-то сделать и пойдет речь ниже.
Итак, знакомьтесь — наш пациент снаружи и внутри:
Обратите внимание на маркировку микроконтроллера 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 байт выглядит так:
Источник отправляет First Frame (FF) с данными об общей длине передаваемых данных и первыми 6 байтами payload’а.
Приемник отвечает ему Flow Control фреймом, в котором говорит о минимальном допустимом времени между посылками CF (о них ниже) и количестве CF, после которого источник снова должен дождаться Flow Control фрейм.
Источник после приема 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