Как из неуправляемого коммутатора сделали управляемый или как я купил очередную хрень и сделал из неё другую
Однажды мне попался на озоне медиаконвертер с 4 портами RJ-45(8P8C) и одним портом для оптики. Стоил он недорого(их и сейчас можно найти за 2 тысячи) и мне было интересно на чем он устроен и что можно из него сделать(а ещё если он будет вовсе глупый то использовать по прямому назначению). Так прошел год после покупки и у меня недоходили руки до него. Недавно время всё таки нашлось. Итак я сделал из медиаконвертера управляемый свитч.
Начнем с того что обнаружилось на плате медиаконвертера.
RTL8367S-CG — управляемый коммутатор второго уровня, пять встроенных
приёмопередатчиков на гигабит плюс два расширенных порта. На нашей плате
наружу выведены четыре разъёма и один под оптику.
Рядом с ним на плате стоит FT24C32A — микросхема памяти на 32 килобита
(4 КБ) с двухбайтовой адресацией, откликается на адрес 0x50.
В качестве управляющей платы я взял WeAct STM32G431 Core Board. На плате стоит STM32G431CB, а в нем: Cortex-M4F на 170 МГц, 128 КБ флеш-памяти,
32 КБ оперативной. Определение платы уже есть в дереве Zephyr под именемweact_stm32g431_core, и в нём всё, что нужно. Ровно 48 МГц для USB — обязательное требование, и делитель Q подобран
именно под него. Взят он потому-что был просто под рукой. И да для данной задачи это over.
Выводы коммутатора и выбор режима
Самая ценная выкладка из документации на RTL8367S-CG была в том:
Отдельных выводов MDC/MDIO у микросхемы нет. Это те же две линии что и I2C. Какую из двух ролей они играют, задаёт состояние вывода SMI_SEL
(совмещён с GPIO52, LAN0LED0/LED_CK) в момент сброса.
Второй страппинг: EEPROM_MOD/LAN4LED0 задаёт размер памяти —
низкий уровень означает до 16 кбит (24C02…24C16), высокий — больше
(24C32…24C256). У нас стоит 24C32, значит вывод должен быть в высоком
состоянии.
Служебный протокол Realtek SMI
Протокол внешне напоминает I²C, но им не является: у него свои условия
начала и конца посылки. Поэтому аппаратный контроллер I²C использовать
нельзя — обмен формируется программно.
Код команды
Первый байт собирается из трёх частей:
1011 + 100 + К = 1011 100К 4 бита 3 бита 1 бит
где К — направление: 1 означает чтение, 0 — запись.
Последовательность чтения регистра
START 0xB9 → подтверждение младший байт адреса → подтверждение старший байт адреса → подтверждение младший байт данных ← выдаём подтверждение (0) старший байт данных ← выдаём отказ (1) STOP
Запись отличается тем, что после старшего байта адреса передаются младший
и старший байты данных, каждый с подтверждением.
Порядок байт: младший вперёд и для адреса, и для данных.
Условия начала и конца
Начало посылки отличается от I²C тем, что спад линии данных приходится
на второй такт, а не на первый:
SCK: __/‾‾\__/‾‾‾‾‾‾\__
SDA: ‾‾‾‾‾‾‾‾‾‾‾‾\_____/‾‾
такт 1 такт 2Конец посылки содержит несколько добавочных тактов - коммутатору нужно
время завершить внутреннюю операцию.
Самое важное: способ управления линиями
Линии необходимо вести активно в оба уровня. На открытом стоке, когда
единицу отдаёт подтягивающий резистор, коммутатор не отвечает вовсе -
ни на одной скорости, ни при каком назначении выводов.
Тайминги
Задержка между сменами уровней у Realtek - 1 микросекунда. У нас по
умолчанию 2 мкс, и обмен устойчиво работает в диапазоне 1…10 мкс. Скорость
на работоспособность не влияла: перебор от нулевой задержки до 100 мкс при
открытом стоке не дал ни одного ответа.
5. Открытие доступа к регистрам
Регистр номера чипа 0x1300 упорно читался нулями, хотя соседние регистры
отдавали осмысленные значения. Разгадка нашлась в исходниках изготовителя:
при запуске он записывает особое значение в отдельный регистр.
записать 0x0249 в регистр 0x13C2
0x6367 - это и есть опознавательный номер RTL8367. Заодно опыт доказал,
что запись в регистры работает: записанное значение изменило поведение
микросхемы.
Прошивка делает это сама при запуске, выждав перед этим три секунды —
столько коммутатору нужно на собственную инициализацию после включения. Поймал при ребуте коммутатора по питанию.
6. Карта регистров
Официальный заголовок Realtek rtl8367c_reg.h содержит около двадцати трёх
тысяч строк определений. Переписывать это руками бессмысленно, поэтому
карта построил разбором заголовка.
Привязка полей к регистрам делается по самому длинному совпадающему
префиксу имени — иначе поля похоже названных регистров достаются не тем
владельцам.
7. Состояние портов
Один регистр на порт, начиная с 0x1352:
адрес = 0x1352 + номер порта
Расшифровка проверена вживую. При подключении кабеля регистр изменился
так:
было 0x00e0 = 0000 0000 1110 0000
стало 0x00f5 = 0000 0000 1111 0101
↑ ↑ ↑ ↑
│ │ │ └─ бит 0: скорость 100 Мбит/с
│ │ └─── бит 2: полный дуплекс
│ └───── бит 4: появилась связь
└──────── биты 5-7 не изменились
Поднялись ровно три ожидаемых разряда, остальное осталось на месте.
Соответствие разъёмов портам: физический разъём 1 — это порт 0
внутри микросхемы.
8. Счётчики
Коммутатор ведёт 59 счётчиков на каждый порт. Они лежат не в обычных
регистрах: нужно задать номер счётчика и забрать значение из окна.
0x1004 -номер нужного счётчика
0x1005 -состояние, разряд 0 - занятость, разряд 1 - идёт сброс
0x1000…0x1003 -окно из четырёх регистров со значением
Вычисление номера
смещение = 0x7C × номер_порта если номер_порта > 7: смещение += 68 для каждого предыдущего счётчика: смещение += его_длина
Длина счётчика — 4 регистра (64 разряда) для объёмов в байтах и
2 регистра (32 разряда) для остальных. Шаг между наборами разных портов —0x7C, то есть 124 регистра.
Порядок обращения
- Прочитать
0x1004. Если там уже лежит нужный номер, записать другой —
иначе коммутатор не станет обновлять окно. - Записать
смещение >> 2в0x1004. - Дождаться сброса разряда занятости в
0x1005. - Прочитать окно. Для счётчика длиной 4 регистра чтение начинается
с0x1003, для длиной 2 — с0x1000 + ((смещение + 1) mod 4).
Дальше адрес убывает, а значение собирается сдвигом влево на 16.
Отдельно стоит счётчик отброшенных изученных записей: он не привязан
к порту и лежит по фиксированному смещению 0x420.
9. Виртуальные сети
Записи членства: 32 штуки по 4 регистра
адрес = 0x0728 + номер_записи × 4
0 - маска портов-членов, 11 разрядов
1 - группа фильтрации, 4 разряда
2 - разряд 0 - приоритет включён, 1–3 - приоритет, 4 - учёт включён, 5–10 - номер счётчика
Привязка порта к записи
адрес = 0x0700 + номер_порта / 2
По два порта на регистр: порт с чётным номером в младшей половине, со
следующим - в старшей. В каждой половине разряды 0–4 содержат номер
записи членства, разряды 5–7 - приоритет.
Обратите внимание: порт ссылается не на номер сети напрямую, а на
номер записи членства.
Таблица на 4096 сетей: 3 слова на запись
0 - разряды 0–7 - маска членов, 8–15 - маска портов без метки
1 - 0-3 группа фильтрации, 4 приоритет включён, 5–7 приоритет, 8 учёт включён, 9–13 номер счётчика, 14 раздельное изучение адресов
2 - 0-2 старшие разряды маски членов, 3–5 старшие разряды маски без метки, 6 старший разряд номера счётчика
Маски портов разрезаны между словами 0 и 2, потому что портов
одиннадцать, а в байт помещается восемь.
10. Доступ к внутренним таблицам
Таблицы сетей, изученных адресов и правил разбора доступны через общее
окно:
0x0500 - вид таблицы и направление обращения
Слово управления собирается так:
команда = (направление << 3) | вид_таблицы
Направление: 0 — чтение, 1 — запись.
11. Прочие подсистемы
Зеркалирование трафика
Дополнительно 0x12FB содержит маску портов-источников (11 разрядов).
Изоляция портов
адрес = 0x08A2 + номер_порта
Одиннадцатиразрядная маска: каким портам этот порт вправе пересылать
кадры. Позволяет развести абонентов, не прибегая к виртуальным сетям.
Ограничение скорости
Приём -0x000F + номер_порта × 2, два регистра
Передача -PORT<n>_EGRESSBW_CTRL0/1
Первый регистр хранит младшие 16 разрядов, второй — разряды 16–18. Счёт
идёт в единицах по 8 килобит в секунду, поэтому заданное значение
округляется вниз до кратного.
12. Что лежало в памяти на плате
Содержимое микросхемы вычитано целиком — 4096 байт. Ожидалась таблица
вида «адрес регистра — значение», которую коммутатор загружает при
включении. Оказалось иное.
0000: b7 0d 02 0a 71 e4 f5 a8 d2 af 22 00 00 02 0c d3
0x00 - b7 0d - заголовок: 0x0DB7 = 3511, длина
0x02 -02 0a 71 - LJMP 0x0A71 — вектор сброса
0x05 - e4 f5 a8 d2 af 22 - CLR A / MOV IE,A / SETB EA / RET
0x0D - 02 0c d3 - LJMP 0x0CD3 - вектор прерывания таймера
Это исполняемый код архитектуры 8051. Если считать, что адресу0x0000 соответствует смещение 2, таблица векторов прерываний сходится
идеально.
Характерные команды по всему объёму: e4 (CLR A), 90 xx xx
(MOV DPTR), 12 xx xx (LCALL), 02 (LJMP), 22 (RET),7f/7e/7d (загрузка регистров).
В конце образа: по смещению 0x0FE0 — байты d1 a1 cd a4 ca a1, которые
в кодировке GB2312 складываются в иероглифы; по смещению 0x0FF6 —20 16 10 25, очень похоже на дату 25 октября 2016 года.
Проверка догадки
В открытой библиотеке для этого семейства нашёлся готовый образ памяти,
причём именно для FT24C32A в корпусе SOIC8 — той же микросхемы:
наш образ: b7 0d 02 0a 71 e4 f5 a8 d2 af 22 00 00 02 0c d3
эталонный: fc 09 02 07 37 e4 f5 a8 d2 af 22 00 00 02 09 44
└──┬──┘ └───┬──┘ └────────┬───────┘ └─┬─┘ └──┬──┘
заголовок LJMP одинаковый код нули LJMP
Совпадает структура: заголовок с длиной, переход на начало программы,
побайтово одинаковый стартовый код, нули, вектор прерывания.
Содержимое разное (другая версия прошивки, у эталона длина 0x09FC =
2556), но формат тот же.
ядро 8051 встроено в сам RTL8367S, и в памяти лежит его
прошивка. Коммутатор при включении сам вычитывает её по тем же линиям,
выступая на шине ведущим.
13. Архитектура решения
Почему логика на компьютере
Карта регистров содержит 4079 позиций. Реализовывать их обслуживание в
прошивке значило бы перепрошивать плату ради каждой новой возможности.
Поэтому разделение такое:
Прошивка на Zephyr v4.4.2 на STM32 - прозрачный мост к регистрам
Программа на Python - карта регистров, подсистемы, веб-интерфейс
Машинный протокол
У оболочки на плате два набора команд. Один для кожаных - с подписями и
пояснениями. Второй для программы - со строгим ответом:
m r <адрес> [кол-во] - прочитать подряд идущие регистры
m w <адрес> <знач>… - записать подряд идущие регистры
m m <адрес> <маска> <знач> - изменить только отмеченные разряды
m i - номер чипа, версия, режим
m u - открыть доступ к регистрам
m s [мкс] - показать или задать полутакт шины
Ответ всегда одной строкой: + <значения> при успехе, - <код> при
отказе, все числа шестнадцатеричные. Пример:
m r 1352 8 + 00e0 00e0 00e0 00f5 0086 0000 0000 0000
Команда изменения разрядов существует отдельно неспроста: читать и писать
по очереди небезопасно, потому что между двумя обращениями регистр может
изменить сам коммутатор. Прошивка делает это неделимо.
Связь с платой
Плата отдаётся системе как виртуальный последовательный порт с
опознавательными признаками 2FE3:0001 и названием RTL8367S Manager.
Программа ищет её по этим признакам, поэтому смена имени устройства
(/dev/ttyACM0 против /dev/ttyACM1) ни на что не влияет — а меняется
оно охотно, поскольку рядом подключён отладчик.
Источники
- RTL8367S-CG, техническое описание
- Realtek Unmanaged Switch Arduino Library — эталонная реализация обмена, карта регистров, образ памяти
- Realtek_switch_hacking — первоисточник файлов изготовителя
- Драйверы realtek в ядре Linux — два независимых интерфейса управления
- Описание привязки realtek-smi в Linux