[Перевод] Создание гигабайтного коммутатора на Linux

Making a Linux-managed network switch

Сетевые коммутаторы — простые устройства: принимаешь пакет, отправляешь пакет. К счастью, люди придумали, как усложнить их, и изобрели управляемые коммутаторы.

Обычно они реализуются добавлением веб-интерфейса, конфигурирующего настройки и контролирующего такие параметры, как состояние портов. В более дорогих коммутаторах есть доступ к альтернативным интерфейсам, например, Telnet и последовательным консольным портам.

Однако есть и вторая категория управляемых коммутаторов, о которых вспоминают не сразу, это коммутаторы, находящиеся внутри маршрутизаторов потребительского уровня. Эти маршрутизаторы — небольшие устройства на Linux, имеющие внутри чип коммутатора, один или несколько портов с внутренним подключением к CPU, а остальные выведены наружу как физические порты.

Блок-схема Mikrotik RB2011 с mikrotik.com

Блок-схема Mikrotik RB2011 с mikrotik.com

Вот пример устройства, в котором это задокументировано. Я всегда думал, что конфигурация этих подключённых к коммутатору портов — это удобная абстракция веб-интерфейса, но с удивлением узнал, что благодаря DSA и подсистеме switchdev в Linux эти порты оказываются полнофункциональными «локальными» сетевыми портами. К сожалению, из-за того, что это практически единственные порты, доступные внутри интегрированных маршрутизаторов, с ними довольно сложно экспериментировать.

То, что на схеме показано как единая линия — это, на самом деле, соединение SoC маршрутизатора и коммутатора по шине SGMII (или, возможно, RGMII в этом случае) и шины управления, SMI или MDIO. У сетевых коммутаторов есть множество подобных забавных аббревиатур, которые даже при полной расшифровке кажутся непонятными, если вы не знаете, что там к чему.

Управлять стандартным коммерческим коммутатором при помощи этой системы попросту невозможно, потому что требуемые подключения чипа коммутатора не раскрыты для этого. Остаётся лишь один вариант…

Создаём собственный гигабитный сетевой коммутатор

Наверно, в создании собственного сетевого коммуникатора не может быть ничего особо сложного? Эти устройства продаются по цене чашки кофе, и учитывая стоимость, должны иметь высокую степень интеграции. Я не вижу в Интернете особо много самодельных коммутаторов, так что можно предположить. что чипы для них найти достаточно сложно…

5d46c0977c33d1d6efeb2e911e76ef4f.png

Ан нет, достать их очень легко. Для них даже имеется даташит. Поэтому я создал новый проект KiCad и приступил к работе.

Я рад, что для этого чипа есть хоть какой-то даташит, потому что для устройств Realtek их обычно не найти, но он всё равно довольно скромный. Я решил ограничиться поиском устройств, у которых есть доступные схемы для схожих чипов Realtek, чтобы понять, как интегрировать их, и изучил множество документации по тому, как в целом реализовать Ethernet в устройстве.

Реализация чипа изначально казалась очень сложной: она требует примерно семь отдельных цепей питания и у неё множество очень плохо задокументированных коммуникационных интерфейсов. Изучив другие реализации, я пришёл к выводу, что проще всего для её питания подключить все сети с близкими интервалами напряжений вместе и нам понадобится всего лишь регулятор на 3,3 В и 1,1 В.

Похоже, дополнительные коммуникационные шины требуются для дополнительных портов, которые мне вроде бы не нужны. В качестве чипа коммутатора я выбрал RTL8367S. Это очень широко используемый пятипортовый гигабитный чип, хотя на самом деле он не пятипортовый. Это семипортовый чип коммутатора, где пять портов имеют интегрированный PHY, а два предназначены для соединений с CPU.

Блок-схема соединений CPU из даташита RTL8367S

Блок-схема соединений CPU из даташита RTL8367S

Однако мой план был другим: хотя все эти порты CPU доступны, на самом деле в подсистеме Linux switchdev ничто не требует, чтобы подключение CPU осуществлялось с этими портами. Вместо этого я буду подключаться к порту 0 коммутатора сетевым кабелем, и насколько известно драйверу switchdev, в промежутке нет никакого Ethernet PHY.

Следующей проблемой стала конфигурация чипа коммутатора: существует множество систем конфигурации, а в даташите не говорится о том, какая система минимально требуется для того, чтобы устройство работало как обычный «тупой» коммутатор. Вкратце опишу опции конфигурации чипа:

  • На чипе есть восемь контактов, считываемых при его запуске. Эти контакты сделаны общими с контактами светодиодов портов, из-за чего проектирование становится довольно неудобным процессом. Кроме того, переключение с повышения на понижение требует подключения светодиода в другой ориентации.

  • Есть шина I2C, которую можно подключить к чипу EEPROM. Контакты для неё сделаны общими с шиной SMI, которая мне требуется для того, чтобы чип мог общаться с Linux. Существует конфигурация контактов, позволяющая выбирать один из двух интервалов размеров EEPROM, но нигде не указано, что же меняет эта настройка.

  • Есть шина SPI, поддерживающая подключение флэш-чипа NOR. Он может хранить или регистры конфигурации, или прошивку для встроенного ядра 8051; это зависит от конфигурации считываемых при запуске контактов. Контакты шины SPI тоже сделаны общими с одним из сетевых портов CPU.

  • Есть ещё доступный последовательный порт, но судя по изученному мной, он, вероятно, ничего не делает, если только в 8051 не загружена прошивка.

Чтобы разобраться, я просто заказал плату и по-разному припаивал соединения, пока всё не заработало. Я добавил площадку для флэш-чипа, которая в итоге оказалась не нужна, а для всех контактов конфигурации реализовал перемычки. Я отказался от всех светодиодов, потому что сделать их конфигурируемыми было бы довольно сложно.

Дальше я начал разбираться, как правильно реализовать Ethernet. Об этом написано много документации, из которой складывается ощущение, что для гигабитного Ethernet требуется прецизионное проектирование, платы с управлением импедансом и благословение самих богов Ethernet. Это не очень согласуется с реальностью, ведь эти коммутаторы очень дёшевы в производстве и вроде вполне неплохо работают. Поэтому я решил вскрыть коммутатор, чтобы проверить, действительно ли все эти разделительные конденсаторы и платы согласования импеданса используются в реальных устройствах. Как оказалось, всё это не особо важно.

126443052df66407fcbb8b083a9b3ff1.png

Вот схема, на которой я остановился, но она отличается от моей тестовой печатной платы. Впрочем, я с первого раза сделал всё почти правильно.

eb482e46c636ce6959e7d1cf03ba156e.png

Похоже, важно здесь согласовать асимметрию пар, но согласовывать длину четырёх сетевых пар совершенно бесполезно; в основном это вызвано тем, что сетевые кабели не имеют одинаковой частоты скручивания для четырёх пар, поэтому их длина сильно различается внутри кабеля.

Пары между трансформатором и разъёмом RJ45 имеют собственную заземляющую плоскость, соединённую с основным заземлением через конденсатор. Пары после трансформатора просто находятся на заземлении основной платы.

На первой версии платы я ошибся в том, что забыл конденсатор, соединяющий ответвления от центральной точки трансформатора на боковой части коммутатора с землёй. Из-за этого Ethernet практически не работал, поэтому мне пришлось вручную перерезать на плате крошечные дорожки, чтобы отсоединиться от заземления. В моей тестовой системе конденсатора вообще нет, а все центральне точки изолированы от заземления. Похоже, в такой схеме всё работает, но в окончательной версии этот конденсатор добавлен.

Отрезанные дорожки заземления на трансформаторе Ethernet

Отрезанные дорожки заземления на трансформаторе Ethernet

В результате получился довольно странный гигабитный коммутатор. Четыре его порта выведены в одном направлении, один смотрит в противоположном, а запитано устройство через штифтовой разъём размером 2,54 мм. Кроме того, я добавил площадку для разъёма USB Type-C, чтобы удобно подавать питание без необходимости использования кабелей DuPont.

ac3d8a4c54fa53f98bcec7fe449c2831.jpeg

Подключаем систему к Linux

Для своей тестовой системы я выбрал плату PINE64 A64-lts, потому что её разъёмы находятся примерно в нужных мне местах. Также важно, что это не x86, ведь для конфигурирования требуется изменение дерева устройств, а его невозможно выполнить на платформе, где нет деревьев устройств.

Первым делом нужно было пересобрать ядро под плату, потому что в большинстве ядер эти модули ядра просто отключены. Для этого я включил следующие опции:

  • CONFIG_NET_DSA для системы Distributed Switch Architecture

  • CONFIG_NET_DSA_TAG_RTL8_4 для тегирования портов этого чипа коммутатора Realtek

  • CONFIG_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, но для этого нужно изучать документацию…

Habrahabr.ru прочитано 9735 раз