
Интернет пестрит информацией о том, как запускают DOOM на различных устройствах. Захотелось и мне запустить Doom на своём стареньком роутере TP-Link WR-841N. Как видно из заголовка статьи, у меня этого не получилось, но результатом стало понимание основ написания прошивок роутеров и запущенный на роутере тетрис.
Описанные в статье операции — вы делаете это на свой страх и риск. Если выполняете какую-то операцию, вы должны полностью понимать риски, которые она несёт, и возможные последствия. Мне не удалось угробить роутер, но это вовсе не значит, что у вас не получится.
Много информации, изложенной в статье, применима и для других роутеров. В любом случае после прочтения статьи вы должны по-другому смотреть на прошивки роутеров, если раньше не углублялись в эту тему.
Для TP-Link WR-841N существует 14 версий аппаратной реализации этого роутера, а я собирал прошивку с тетрисом именно для версии V8.
Разработку и проверку работы прошивки невозможно выполнить без доступа к системной консоли. Системная консоль — это то, куда выводятся системные сообщения при загрузке прошивки, и где можно зайти в оболочку и вводить команды Linux. Поэтому первым делом разберёмся, как получить доступ системной консоли.
Системная консоль
Алгоритм получения доступа к системной консоли следующий:
Разбираем роутер. Мне пришлось открутить два шурупа и аккуратно разделить две половинки корпуса, не сломав защёлки. На плате, как правило, можно увидеть 4 отверстия для подключения по UART-интерфейсу. Но я уже успел туда припаять гребёнку.

Вооружившись паяльником, припаиваем гребёнку. Разбираемся с распиновкой контактов. Для моего роутера она следующая:

Нужно использовать только три пина: TX, RX, GND. VCC нам не нужен.
Подсоединяем USB-UART адаптер к компьютеру и роутеру. Убедитесь в том, что UART-адаптер для TTL-уровней использует верный вольтаж, для моего это 3,3 В.
Набираем команду в Linux:
sudo picocom -b 115200 /dev/ttyUSB0
115200 — скорость передачи данных по UART-интерфейсу,
/dev/ttyUSB0 — символьное устройство, появившееся в Linux для нашего адаптера.
Включаем роутер и наблюдаем загрузку в системной консоли:
U-Boot 1.1.4 (Jun 20 2013 - 17:34:51) U-boot AP123 DRAM: 32 MB id read 0x100000ff Flash: 4 MB Using default environment In: serial Out: serial Err: serial Net: ag934x_enet_initialize... wasp reset mask:c03300 WASP ----> S27 PHY file: ag934x.c,line: 179==: set LAN&WAN SWAP. --debug by HouXB GMAC: cfg1 0x5 cfg2 0x7114 eth0: ba:be:fa:ce:08:41 s27 reg init athrs27_phy_setup ATHR_PHY_CONTROL 4: 0x1000 athrs27_phy_setup ATHR_PHY_SPEC_STAUS 4: 0x10 eth0 up WASP ----> S27 PHY file: ag934x.c,line: 179==: set LAN&WAN SWAP. --debug by HouXB GMAC: cfg1 0xf cfg2 0x7214 eth1: ba:be:fa:ce:08:41 s27 reg init lan ATHRS27: resetting s27 ATHRS27: s27 reset done athrs27_phy_setup ATHR_PHY_CONTROL 0: 0x1000 athrs27_phy_setup ATHR_PHY_SPEC_STAUS 0: 0x10 athrs27_phy_setup ATHR_PHY_CONTROL 1: 0x1000 athrs27_phy_setup ATHR_PHY_SPEC_STAUS 1: 0x10 athrs27_phy_setup ATHR_PHY_CONTROL 2: 0x1000 athrs27_phy_setup ATHR_PHY_SPEC_STAUS 2: 0x10 athrs27_phy_setup ATHR_PHY_CONTROL 3: 0x1000 athrs27_phy_setup ATHR_PHY_SPEC_STAUS 3: 0x10 eth1 up eth0, eth1 Autobooting in 1 seconds ## Booting image at 9f020000 ... Uncompressing Kernel Image ... OK ........При загрузке можем увидеть ценную информацию, например, распределение памяти на флеш-чипе:
5 cmdlinepart partitions found on MTD device ath-nor0 Creating 5 MTD partitions on "ath-nor0": 0x000000000000-0x000000020000 : "u-boot" 0x000000020000-0x000000120000 : "kernel" 0x000000120000-0x0000003e0000 : "rootfs" 0x0000003e0000-0x0000003f0000 : "config" 0x0000003f0000-0x000000400000 : "art"Нажимаем
Enterи наблюдаем приглашения входа в систему:TL-WR841N mips #11 Thu Jul 24 17:24:48 CST 2014 (none) TL-WR841N login:Вводим пользователя: root, пароль: sohoadmin. С современными роутерами узнать пароль, наверное, так просто не получится.
Теперь стоит вопрос, как запустить на роутере какую-нибудь свою программу, например, тетрис. Так просто, как на обычном компьютере, не получится.
Стоковые прошивки обычно очень ограниченные, а вот прошивки от OpenWrt обладают гораздо большим функционалом, там даже есть пакетный менедежер. Но на прошивках для TPLink WR841N для него просто не хватает места.
Режимы работы загрузчика
Умение работать с прошивками требует понимания, как работает загрузчик. Вы уже увидели, как работает загрузчик в штатном режиме. Но существуют ещё режим восстановления и интерактивный режим.
Режим восстановления переводит роутер в режим считывания прошивки с последующей её записью на флеш-чип.
Интерактивный режим позволяет вводить команды загрузчику с помощью клавиатуры.
Разберёмся, как запустить загрузчик в интерактивном режиме.
Работа с загрузчиком U-Boot в интерактивном режиме
Для большинства роутеров TP-Link, стоит модифицированный загрузчик U-Boot. Чтобы попасть в его интерактивный режим, нужно во время отображения сообщения Autobooting in 1 seconds быстро набрать на клавиатуре tpl.
Далее набираем help и смотрим список доступных команд. На моём роутере команд оказалось немного, но они позволяют выполнять основные операции, необходимые для загрузки и манипуляций с флеш-чипом.
Параметры роутера
При покупке роутера, на коробке будет указана скорость, различные поддерживаемые технологии, но не будут указаны главные параметры роутера, которые нужно знать при разработке прошивки:
используемый SoC,
система команд процессора (ISA),
используемые чипы для Ethernet и WiFi, если они не интегрированы в SoC,
размер оперативной памяти,
размер и тип используемой флеш-памяти,
схема разделов,
какой используется стоковый загрузчик.
Для многих роутеров характеристики можно подсмотреть на сайте OpenWRT.
Для роутера TP-Link WR-841N v8 имеем следующие характеристики:
Параметр | Значение |
|---|---|
SoC | Atheros AR9341 |
Ethernet | Интегрирован в SoC |
WiFi | Интегрирован в SoC |
ISA | MIPS32 |
CPU | MIPS 24Kc 560 MHz |
RAM | 32 MB |
Flash | 4 MB SPI NOR |
Знание этих параметров даёт очень многое для понимания процесса разработки или модификации прошивки.
Например, знание ISA и CPU даёт понимание, какой использовать toolchain, как процессор обрабатывает данные (Big Endian или Little Endian), а знание размера RAM и flash позволит понять насколько жёсткие требования к оптимизации.
Карты памяти
Чтобы ничего не испортить при работе с прошивками, нужно понимать, как распределена оперативная память, и как распределена память на флеш-чипе. Мне повезло, и после недолгого поиска в сети я получил информацию о картах памяти в моём роутере.
Распределение памяти флеш-чипа
Название раздела | Смещение (Offset) | Размер | Содержимое и назначение |
|---|---|---|---|
u-boot | 0x000000 | 128 КБ | Загрузчик. Инициализирует «железо», содержит консоль восстановления |
firmware | 0x020000 | 3904 КБ | Прошивка. Объединённая область для ядра и файловой системы |
config | 0x3e0000 | 64 КБ | Конфигурация. Хранит текущие настройки пользователя |
art | 0x3f0000 | 64 КБ | Калибровки Wi-Fi и MAC-адрес. Всегда в конце чипа |
Карта оперативной памяти
Область памяти | Начальный адрес |
|---|---|
Куда распаковывается ядро | 0x80000000 |
Куда можно загрузить файл по TFTP | 0x81000000 |
Куда мапится память флеш-чипа | 0x9f000000 |
Карту памяти я привёл в упрощённом виде, так как для детализации нужно будет ещё рассказывать об архитектуре Atheros AR9341 и MIPS 24Kc. Скажу только, что запись во флеш, это запись по адресам оперативной памяти, но так как флеш у нас SPI NOR, то записывать нужно блоками, предварительно очистив эти блоки.
Обратите внимание на разделы boot и art — если вы затрёте информацию в этих разделах, восстановление полноценной работы роутера у вас займёт много времени и сил.
Для восстановления boot-раздела вам придётся выпаивать флеш из платы роутера и записывать загрузчик при помощи программатора. Возможен вариант без выпаивания с использованием «прищепки», но этот вариант не всегда надёжен, и я его не проверял.
Раздел art содержит калибровочные данные для работы драйвера беспроводной сети. Содержимое раздела уникальное для каждого конкретного роутера, а использование art-раздела от другого экземпляра роутера может ухудшить работу WiFi.
Как загружается роутер
Наверное, большинство роутеров работает под операционной системой Linux, по крайней мере, других я не встречал. Мой роутер тоже не является исключением.
Загрузчик распаковывает ядро Linux и флеш-памяти в оперативную память по адресу, указанному в заголовке, и передаёт управление по адресу, также указанному в заголовке.
При интерактивном режиме работы загрузчика вы можете управлять процессом загрузки.
Чтобы выполнить загрузку, нужно ввести в командной строке:
bootm 0x9f020000
Загрузчик поддерживает загрузку по сети с использованием протокола TFTP, но так как команда bootm требует адрес в памяти, нужно сначала образ с Linux скачать в оперативную память при помощи команды tftpboot, а потом выполнить команду bootm.
tftpboot 0x81000000 firmware.bin bootm 0x81000000
Чтобы можно было загрузить по TFTP, нужно настроить TFTP-сервер. Какой адрес сервера ожидает загрузчик, я подсмотрел в выводе команды printenv. Им оказался 192.168.1.100.
wasp> printenv bootargs=console=ttyS0,115200 root=31:02 rootfstype=squashfs init=/sbin/init mtdparts=ath-nor0:128k(u-boot),1024k(kernel),2816k(rootfs),64k(config),64k(art) bootcmd=bootm 0x9f020000 bootdelay=1 baudrate=115200 ethaddr=0xba:0xbe:0xfa:0xce:0x08:0x41 ipaddr=192.168.1.111 serverip=192.168.1.100 dir= bc=ap123 lu=tftp 0x81000000 ${dir}u-boot.bin&&erase 0x9f000000 +$filesize;cp.b $fileaddr 0x9f000000 $filesize lf=tftp 0x81000000 ${dir}${bc}-squashfs&&erase 0x9f120000 +$filesize;cp.b $fileaddr 0x9f120000 $filesize stdin=serial stdout=serial stderr=serial ethact=eth0
Настройка TFTP сервера
Под Windows удобно использовать Tftpd64, но я всё выполнял на Linux и использовал TFTP-сервер в Docker-контейнере.
Создать такой контейнер просто.
Создаём директорию:
mkdir -p tftp/boot cd tftpВ директорию
bootмы будем помещать прошивки.Создаём в директории
simple-tftpDockerfile:FROM busybox:latest RUN echo "69 dgram udp wait root tftpd tftpd -c /tftpboot" > /etc/inetd.conf EXPOSE 69/udp CMD ["inetd", "-f", "-e", "/etc/inetd.conf"]Собираем:
sudo docker build -t simple-tftp .Настраиваем сетевой интерфейс, на котором у нас будет работать TFTP-сервер. Я обычно использую внешнюю сетевую карту своего ноутбука.

Настройки сетевого интерфейса Запускаем:
sudo docker run --rm -p 192.168.1.100:69:69/udp -v $(pwd)/boot:/tftpboot --name simple-tftp -it simple-tftp
Что содержит прошивка роутера
Прошивки удобно исследовать при помощи утилиты binwalk, которая позволяет эвристически по сигнатурам исследовать структуру бинарных файлов.
Прошивки там бывают трёх типов:
initramfs,
factory,
sysupgrade.
Откуда взять прошивки для экспериментов
Для своего роутера вы можете поискать прошивки на сайте OpenWRT.
Нам интересны первые две. Но безопаснее всего использовать первую — initramfs. Initfamfs-прошивок нет в готовом виде, но позже я покажу, как её собрать. Если factory прошивка использует такую же разбивку, как и на вашем флеш-чипе, эту прошивку можно тоже использовать для эксперимента по загрузке прошивки.
Сборка своей прошивки для роутера
Мне очень хотелось собрать прошивку с нуля на основе свежего mainlinе-ядра, но, к сожалению, пока эта задача не увенчалась успехом. Поэтому воспользуемся системой сборки, предоставляемой OpenWRT. Мой роутер, из-за того, что у него малый объём оперативной памяти и флеш-чипа, уже не подцеживается OpenWRT. Сборка и запуск современных прошивок приводили к тому, что загрузка падала из-за нехватки памяти.
Удобнее всего собирать в докере.
Создаём Docker-образ Ubuntu со всеми необходимыми пакетами:
cat <<EOF > Dockerfile FROM ubuntu:18.04 RUN apt-get update && apt-get install -y --no-install-recommends \\ build-essential \\ subversion \\ git-core \\ libncurses5-dev \\ zlib1g-dev \\ gawk \\ flex \\ quilt \\ libssl-dev \\ xsltproc \\ libxml-parser-perl \\ mercurial \\ unzip \\ wget \\ file \\ && rm -rf /var/lib/apt/lists/* ENV FORCE_UNSAFE_CONFIGURE=1 EOFsudo docker build -t firmware-builder .Запускаем Docker-контейнер:
sudo docker run -it -v $(pwd):/out firmware-builderСкачиваем OpenWRT:
cd root wget wget https://github.com/openwrt/openwrt/archive/refs/tags/v19.07.8.tar.gz tar xvf v19.07.8.tar.gz cd openwrt-19.07.8/Конфигурируем нашу прошивку:
./scripts/feeds update -a ./scripts/feeds install -a make menuconfigВыбираем
Target System -> Atheros AR7xxx/AR9xxx
ВыбираемSubtarget -> Devices with small flash
ВыбираемTarget Profile -> TP-LINK TL-WR841N/ND v8
ОтключаемTarget Images -> squashfs
ВключаемTarget Images -> ramdiskЗапускаем сборку:
make -j$(nproc) V=sСобранная прошивка находится здесь:
bin/targets/ar71xx/tiny/openwrt-ar71xx-tiny-tl-wr841-v8-initramfs-kernel.binКопируем файл в примонтированную директорию, чтобы файл можно было использовать на хосте:
cp bin/targets/ar71xx/tiny/openwrt-ar71xx-tiny-tl-wr841-v8-initramfs-kernel.bin /out/tftp/boot/bare.bin
Полученный файл прошивки можно попробовать загрузить на роутере.
Но мы хотели запустить какую-то свою программу на роутере, как это сделать?
Создание своего пакета для прошивки OpenWRT
Роутер работает под операционной системой Linux с запущенными пользовательскими приложениями. Нам нужно создать своё приложение и интегрировать его в прошивку роутера. В OpenWRT это решается просто.
Создаём папку с исходным кодом пакета:
mkdir -p ~/packages/helloworld/srcСоздаём
Makefileв директории~/packages/helloworld. Не забываем про табуляции вMakefile:include $(TOPDIR)/rules.mk PKG_NAME:=helloworld PKG_VERSION:=1.0 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) include $(INCLUDE_DIR)/package.mk define Package/helloworld SECTION:=examples CATEGORY:=Examples TITLE:=Simple Hello World program URL:=https://example.com endef define Package/helloworld/description A very basic "Hello, World!" example packaged for OpenWrt. endef define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ endef define Build/Compile $(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_CPPFLAGS) \ -o $(PKG_BUILD_DIR)/helloworld \ $(PKG_BUILD_DIR)/helloworld.c endef define Package/helloworld/install $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/usr/bin/ endef $(eval $(call BuildPackage,helloworld))Пишем исходный код приложения:
#include <stdio.h> int main() { printf("Hello world! \n"); return 0; }Прописываем папку с пакетами в конфигурации:
echo "src-link myfeed /root/packages" >> feeds.conf.defaultОбновляем фиды:
./scripts/feeds update -aУстанавливаем пакет в сборку, чтобы он стал доступен в меню конфигурации:
./scripts/feeds install -p myfeed helloworldКонфигурируем сборку:
make menuconfigОтмечаем наше приложение как встроенное (*):
Examples->helloworldСохраняем конфигурацию.
Выходим из меню конфигурации и запускаем сборку:
make -j$(nproc) V=s
Теперь прошивка будет содержать наш пакет.
Создание прошивки с тетрисом
Можно, конечно, написать свою версию тетриса, но я воспользовался следующим.
Добавление этого тетриса в прошивку похоже на то, как мы добавляли helloworld. Но некоторые шаги выполнять не нужно.
Создаём папку с исходным кодом пакета:
mkdir -p ~/packages/tetrisСоздаём
Makefileв директории~/packages/tetris. Не забываем про табуляции вMakefile:include $(TOPDIR)/rules.mk PKG_NAME:=tetris PKG_VERSION:=1.5.0 PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/troglobit/tetris.git PKG_SOURCE_VERSION:=1.5.0 PKG_MIRROR_HASH:=skip PKG_MAINTAINER:=Your Name <you@example.com> PKG_LICENSE:=GPL include $(INCLUDE_DIR)/package.mk define Package/tetris SECTION:=examples CATEGORY:=Examples TITLE:=Micro Tetris - very small terminal Tetris URL:=https://github.com/troglobit/tetris DEPENDS:=+libc endef define Package/tetris/description One of the smallest Tetris implementations using only ANSI escapes. Perfect for embedded devices with terminal access. endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) \ CC="$(TARGET_CC)" \ CFLAGS="$(TARGET_CFLAGS)" \ LDFLAGS="$(TARGET_LDFLAGS)" endef define Package/tetris/install $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/tetris $(1)/usr/bin/ endef $(eval $(call BuildPackage,tetris))Исходный код тетриса писать не нужно, так как его возьмёт из Git-репозитория OpenWRT при сборке.
Обновляем фиды:
./scripts/feeds update -aУстанавливаем пакет в сборку, чтобы он стал доступен в меню конфигурации:
./scripts/feeds install -p myfeed tetrisКонфигурируем сборку:
make menuconfigОтмечаем наше приложение как встроенное (*): Examples->tetris
Сохраняем конфигурацию.
Выходим из меню конфигурации и запускаем сборку:
make -j$(nproc) V=sКопируем файл в примонтированную директорию, чтобы файл можно было использовать на хосте:
cp bin/targets/ar71xx/tiny/openwrt-ar71xx-tiny-tl-wr841-v8-initramfs-kernel.bin /out/tftp/boot/tetris-wrong.bin
Теперь прошивка будет содержать наш пакет. Но если вы запустите эту прошивку на роутере, вы увидите, что приложение некорректно определяет количество строк и столбцов в консоли и падает с ошибкой. Исправим этот недостаток с применением патча.
Создание патча к исходному коду пакета
Скачиваем исходный код тетриса:
git clone https://github.com/troglobit/tetris.git cd tetris git checkout 1.5.0 -b tempВыполняем изменения в исходном файле tetris.c. Я привожу
sed-команды, чтобы удобно это было сделать из командной строки:sed -i 's/if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) {/if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, \&ws) \&\& ws.ws_col >= MIN_COLS \&\& ws.ws_row >= MIN_ROWS) {/' tetris.csed -i '/getenv("LINES");/a \ \tttcols = cols ? atoi(cols) : 80;\ \tttrows = lins ? atoi(lins) : 24;' tetris.cПроверяем внесённые изменения:
git diffКоманда должна вывести следующий diff:
diff --git a/tetris.c b/tetris.c index da29f70..21e30ea 100644 --- a/tetris.c +++ b/tetris.c @@ -355,12 +355,14 @@ static int tty_size(void) cols = getenv("COLUMNS"); lins = getenv("LINES"); + ttcols = cols ? atoi(cols) : 80; + ttrows = lins ? atoi(lins) : 24; if (cols && lins) { ttcols = atoi(cols); ttrows = atoi(lins); } - if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) { + if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) && ws.ws_col >= MIN_COLS && ws.ws_row >= MIN_ROWS) { ttcols = ws.ws_col; ttrows = ws.ws_row; } else {Создаём коммит. Так как мы делаем его первый раз, и git не настроен, нужно проконфигурировать git:
git config --global user.email "mynamet@mail.com" git config --global user.name "myname" git add . git commit -m "Changed default columns and rows behaviour"Создаём и копируем патч в директорию
tetris/patches:git format-patch -1 mkdir -p /out/packages/tetris/patches cp 0001-Changed-default-columns-and-rows-behaviour.patch /out/packages/tetris/patchesСобираем прошивку:
cd ../openwrt-19.07.8 make -j$(nproc) V=sКопируем файл в примонтированную директорию, чтобы файл можно было использовать на хосте:
cp bin/targets/ar71xx/tiny/openwrt-ar71xx-tiny-tl-wr841-v8-initramfs-kernel.bin /out/tftp/boot/tetris.bin
Запуск прошивки
Подключаем роутер с тетрисом к компьютеру через UART-интерфейс.
Запускаем TFTP-сервер.
Включаем роутер и входим в интерактивный режим загрузчика (набираем
tpl).Загружаем файл прошивки по TFTP.
tftpboot 0x81000000 firmware.bin bootm 0x81000000Нажимаем клавишу
Enter.Вводим команду
tetris.tetrisИграем в тетрис.

Выводы
Модификация прошивки не такая сложная, как может показаться, если использовать OpenWRT. Главное — знать характеристики роутера, как распределена память.
Роутер TpLink WR-841N имеет очень маленький по современным меркам объём памяти, что усложняет создание полноценных прошивок.
Было бы интересно создать прошивку на основе Mainline ядра, пусть даже без поддержки всего железа. Но задача увлекательная с технической точки зрения.
Мы можем записать нашу прошивку во флеш, но я это намеренно не рассказываю, чтобы не дать вам больше шансов окирпичить роутер.
Старые роутеры кажутся бесполезными устройствами. Но на самом деле это полноценные Linux-компьютеры.
Немного паяльника, UART-адаптер — и вы погружаетесь embedded-разработку. В моём случае всё закончилось тетрисом на роутере.
Но, возможно, кто-то из читателей всё-таки сможет запустить на нём Doom.
© 2026 ООО «МТ ФИНАНС»

