[Перевод] Создание гигабайтного коммутатора на Linux
Сетевые коммутаторы — простые устройства: принимаешь пакет, отправляешь пакет. К счастью, люди придумали, как усложнить их, и изобрели управляемые коммутаторы.
Обычно они реализуются добавлением веб-интерфейса, конфигурирующего настройки и контролирующего такие параметры, как состояние портов. В более дорогих коммутаторах есть доступ к альтернативным интерфейсам, например, Telnet и последовательным консольным портам.
Однако есть и вторая категория управляемых коммутаторов, о которых вспоминают не сразу, это коммутаторы, находящиеся внутри маршрутизаторов потребительского уровня. Эти маршрутизаторы — небольшие устройства на Linux, имеющие внутри чип коммутатора, один или несколько портов с внутренним подключением к CPU, а остальные выведены наружу как физические порты.
Блок-схема Mikrotik RB2011 с mikrotik.com
Вот пример устройства, в котором это задокументировано. Я всегда думал, что конфигурация этих подключённых к коммутатору портов — это удобная абстракция веб-интерфейса, но с удивлением узнал, что благодаря DSA и подсистеме switchdev в Linux эти порты оказываются полнофункциональными «локальными» сетевыми портами. К сожалению, из-за того, что это практически единственные порты, доступные внутри интегрированных маршрутизаторов, с ними довольно сложно экспериментировать.
То, что на схеме показано как единая линия — это, на самом деле, соединение SoC маршрутизатора и коммутатора по шине SGMII (или, возможно, RGMII в этом случае) и шины управления, SMI или MDIO. У сетевых коммутаторов есть множество подобных забавных аббревиатур, которые даже при полной расшифровке кажутся непонятными, если вы не знаете, что там к чему.
Управлять стандартным коммерческим коммутатором при помощи этой системы попросту невозможно, потому что требуемые подключения чипа коммутатора не раскрыты для этого. Остаётся лишь один вариант…
Создаём собственный гигабитный сетевой коммутатор
Наверно, в создании собственного сетевого коммуникатора не может быть ничего особо сложного? Эти устройства продаются по цене чашки кофе, и учитывая стоимость, должны иметь высокую степень интеграции. Я не вижу в Интернете особо много самодельных коммутаторов, так что можно предположить. что чипы для них найти достаточно сложно…
Ан нет, достать их очень легко. Для них даже имеется даташит. Поэтому я создал новый проект KiCad и приступил к работе.
Я рад, что для этого чипа есть хоть какой-то даташит, потому что для устройств Realtek их обычно не найти, но он всё равно довольно скромный. Я решил ограничиться поиском устройств, у которых есть доступные схемы для схожих чипов Realtek, чтобы понять, как интегрировать их, и изучил множество документации по тому, как в целом реализовать Ethernet в устройстве.
Реализация чипа изначально казалась очень сложной: она требует примерно семь отдельных цепей питания и у неё множество очень плохо задокументированных коммуникационных интерфейсов. Изучив другие реализации, я пришёл к выводу, что проще всего для её питания подключить все сети с близкими интервалами напряжений вместе и нам понадобится всего лишь регулятор на 3,3 В и 1,1 В.
Похоже, дополнительные коммуникационные шины требуются для дополнительных портов, которые мне вроде бы не нужны. В качестве чипа коммутатора я выбрал RTL8367S. Это очень широко используемый пятипортовый гигабитный чип, хотя на самом деле он не пятипортовый. Это семипортовый чип коммутатора, где пять портов имеют интегрированный PHY, а два предназначены для соединений с CPU.
Блок-схема соединений CPU из даташита RTL8367S
Однако мой план был другим: хотя все эти порты CPU доступны, на самом деле в подсистеме Linux switchdev ничто не требует, чтобы подключение CPU осуществлялось с этими портами. Вместо этого я буду подключаться к порту 0 коммутатора сетевым кабелем, и насколько известно драйверу switchdev, в промежутке нет никакого Ethernet PHY.
Следующей проблемой стала конфигурация чипа коммутатора: существует множество систем конфигурации, а в даташите не говорится о том, какая система минимально требуется для того, чтобы устройство работало как обычный «тупой» коммутатор. Вкратце опишу опции конфигурации чипа:
На чипе есть восемь контактов, считываемых при его запуске. Эти контакты сделаны общими с контактами светодиодов портов, из-за чего проектирование становится довольно неудобным процессом. Кроме того, переключение с повышения на понижение требует подключения светодиода в другой ориентации.
Есть шина I2C, которую можно подключить к чипу EEPROM. Контакты для неё сделаны общими с шиной SMI, которая мне требуется для того, чтобы чип мог общаться с Linux. Существует конфигурация контактов, позволяющая выбирать один из двух интервалов размеров EEPROM, но нигде не указано, что же меняет эта настройка.
Есть шина SPI, поддерживающая подключение флэш-чипа NOR. Он может хранить или регистры конфигурации, или прошивку для встроенного ядра 8051; это зависит от конфигурации считываемых при запуске контактов. Контакты шины SPI тоже сделаны общими с одним из сетевых портов CPU.
Есть ещё доступный последовательный порт, но судя по изученному мной, он, вероятно, ничего не делает, если только в 8051 не загружена прошивка.
Чтобы разобраться, я просто заказал плату и по-разному припаивал соединения, пока всё не заработало. Я добавил площадку для флэш-чипа, которая в итоге оказалась не нужна, а для всех контактов конфигурации реализовал перемычки. Я отказался от всех светодиодов, потому что сделать их конфигурируемыми было бы довольно сложно.
Дальше я начал разбираться, как правильно реализовать Ethernet. Об этом написано много документации, из которой складывается ощущение, что для гигабитного Ethernet требуется прецизионное проектирование, платы с управлением импедансом и благословение самих богов Ethernet. Это не очень согласуется с реальностью, ведь эти коммутаторы очень дёшевы в производстве и вроде вполне неплохо работают. Поэтому я решил вскрыть коммутатор, чтобы проверить, действительно ли все эти разделительные конденсаторы и платы согласования импеданса используются в реальных устройствах. Как оказалось, всё это не особо важно.
Вот схема, на которой я остановился, но она отличается от моей тестовой печатной платы. Впрочем, я с первого раза сделал всё почти правильно.
Похоже, важно здесь согласовать асимметрию пар, но согласовывать длину четырёх сетевых пар совершенно бесполезно; в основном это вызвано тем, что сетевые кабели не имеют одинаковой частоты скручивания для четырёх пар, поэтому их длина сильно различается внутри кабеля.
Пары между трансформатором и разъёмом RJ45 имеют собственную заземляющую плоскость, соединённую с основным заземлением через конденсатор. Пары после трансформатора просто находятся на заземлении основной платы.
На первой версии платы я ошибся в том, что забыл конденсатор, соединяющий ответвления от центральной точки трансформатора на боковой части коммутатора с землёй. Из-за этого Ethernet практически не работал, поэтому мне пришлось вручную перерезать на плате крошечные дорожки, чтобы отсоединиться от заземления. В моей тестовой системе конденсатора вообще нет, а все центральне точки изолированы от заземления. Похоже, в такой схеме всё работает, но в окончательной версии этот конденсатор добавлен.
Отрезанные дорожки заземления на трансформаторе Ethernet
В результате получился довольно странный гигабитный коммутатор. Четыре его порта выведены в одном направлении, один смотрит в противоположном, а запитано устройство через штифтовой разъём размером 2,54 мм. Кроме того, я добавил площадку для разъёма USB Type-C, чтобы удобно подавать питание без необходимости использования кабелей DuPont.
Подключаем систему к Linux
Для своей тестовой системы я выбрал плату PINE64 A64-lts, потому что её разъёмы находятся примерно в нужных мне местах. Также важно, что это не x86, ведь для конфигурирования требуется изменение дерева устройств, а его невозможно выполнить на платформе, где нет деревьев устройств.
Первым делом нужно было пересобрать ядро под плату, потому что в большинстве ядер эти модули ядра просто отключены. Для этого я включил следующие опции:
CONFIG_NET_DSA
для системы Distributed Switch ArchitectureCONFIG_NET_DSA_TAG_RTL8_4
для тегирования портов этого чипа коммутатора RealtekCONFIG_NET_SWITCHDEV
— система драйверов для сетевых коммутаторовCONFIG_NET_DSA_REALTEK
,CONFIG_NET_DSA_REALTEK_SMI
,CONFIG_NET_DSA_REALTEK_RTL8365MB
для самого драйвера чипа коммутатора
Сложнее было разобраться, как сделать так, чтобы всё это загружалось. Теоретически, можно создать для этого оверлей дерева устройств, чтобы он загружался при помощи U-Boot. Я решил не делать этого и вместо этого пропатчить дерево устройств для платы A64-lts, потому что я всё равно пересобираю ядро. В результате я пришёл к такому изменению дерева устройств:
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts
index 596a25907..10c1a5187 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts
@@ -18,8 +18,78 @@ led {
gpios = <&r_pio 0 7 GPIO_ACTIVE_LOW>; /* PL7 */
};
};
+
+switch {
+ compatible = "realtek,rtl8365rb";
+ mdc-gpios = <&pio 2 5 GPIO_ACTIVE_HIGH>; // PC5
+ mdio-gpios = <&pio 2 7 GPIO_ACTIVE_HIGH>; // PC7
+ reset-gpios = <&pio 8 5 GPIO_ACTIVE_LOW>; // PH5
+ realtek,disable-leds;
+
+ mdio {
+ compatible = "realtek,smi-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethphy0: ethernet-phy@0 {
+ reg = <0>;
+ };
+
+ ethphy1: ethernet-phy@1 {
+ reg = <1>;
+ };
+
+ ethphy2: ethernet-phy@2 {
+ reg = <2>;
+ };
+
+ ethphy3: ethernet-phy@3 {
+ reg = <3>;
+ };
+
+ ethphy4: ethernet-phy@4 {
+ reg = <4>;
+ };
+ };
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ label = "cpu";
+ ethernet = <&emac>;
+ };
+
+ port@1 {
+ reg = <1>;
+ label = "lan1";
+ phy-handle = <ðphy1>;
+ };
+
+ port@2 {
+ reg = <2>;
+ label = "lan2";
+ phy-handle = <ðphy2>;
+ };
+
+ port@3 {
+ reg = <3>;
+ label = "lan3";
+ phy-handle = <ðphy3>;
+ };
+
+ port@4 {
+ reg = <4>;
+ label = "lan4";
+ phy-handle = <ðphy4>;
+ };
+ };
+};
};
Оно загружает драйвер для коммутатора с realtek,rtl8365rb
, этот драйвер поддерживает широкий спектр чипов коммутаторов Realtek, в том числе и RTL8367S, который я использовал в своём устройстве. Я удалил порты CPU из примера в документации и просто добавил определения пяти обычных портов коммутатора.
Важная часть находится в port@0
— это порт, находящийся сзади моего коммутатора и подключённый к A64-lts; я привязал его к &emac
, то есть к ссылке на Ethernet-порт компьютера. Остальные порты привязаны к соответствующим PHY в чипе коммутатора.
В начале кода также определено три GPIO, они привязаны к SDA/SCL и Reset на плате коммутатора, чтобы работали коммуникации. После запуска системы мы получаем следующее:
1: lo: mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1508 qdisc noop state DOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
3 lan1@eth0: mtu 1500 qdisc noop state DOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
4 lan2@eth0: mtu 1500 qdisc noop state DOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
5 lan3@eth0: mtu 1500 qdisc noop state DOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
6 lan4@eth0: mtu 1500 qdisc noop state DOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
Здесь есть обычное устройство eth0
и четыре интерфейса для портов коммутатора, определённых в дереве устройств. Чтобы они что-то делали, интерфейсы обычно сначала нужно включить:
$ ip link set eth0 up
$ ip link set lan1 up
$ ip link set lan2 up
$ ip link set lan3 up
$ ip link set lan4 up
$ ip link
1: lo: mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1508 qdisc mq state UP qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
3: lan1@eth0: mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
4: lan2@eth0: mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
5: lan3@eth0: mtu 1500 qdisc noqueue state UP qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
6: lan4@eth0: mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000
link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff
Теперь коммутатор включён и, как видите, к третьему порту подключён кабель. Эта система многое заимствует у сетевой системы Linux, поэтому «просто работает»™. Вот пара примеров:
Если добавить несколько LAN-портов в стандартный мост Linux, то система switchdev создаст совместный мост этих портов в чипе коммутатора, чтобы Linux не приходилось перенаправлять этот трафик.
Вещи наподобие
ethtool lan3
просто получают информацию о линке. Аethtool -S lan3
возвращает всю стандартную информацию о статусе, в том числе и пакеты, которые полностью обработаны коммутатором.
Ограничения
Есть некоторые аспекты, снижающие удобство работы с такой системой. Во-первых, это необходимость создания собственного сетевого коммутатора или вскрытия готового, чтобы найти подходящие соединения.
Эту систему нельзя использовать на обычных компьютерах/серверах, поскольку нужны деревья устройств для конфигурирования ядра, а у большинства компьютеров нет контролируемых ядром контактов GPIO для подключения коммутатора.
Насколько я понял, ещё нет никакой возможности использовать эту систему с сетевым портом на стороне компьютера, если он не фиксирован; сетевые USB-интерфейсы не имеют дескриптора узлов дерева устройств, который можно было бы использовать для задания соединительного порта.
Есть вероятность, что некоторые из этих ограничений можно обойти: возможно, существует какое-нибудь странное USB-устройство, открывающее контакты подсистемы GPIO; может быть, можно как-то загрузить switchdev не на устройстве с ARM, но для этого нужно изучать документацию…