Вторая жизнь китайского чудо-шнурка 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, что полностью ломает весь процесс передачи.

0f4015fd4e25633e0220948f26463f91.png

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

3cf111619e1c9d310530d3fb663baea9.png

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

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

574866f45abe42a14481a52953e0d11e.png

Функция с реализацией отправки данных по 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 перенесена.

586e60d9ca1a1fe972ac000c83e0a3ca.png

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

266da23f51fe0c4686c3b75e0cba020f.png

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

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

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

© Habrahabr.ru