[Перевод] Встроенный контроллер одного ноутбука и его наследство
В этом материале я хочу немного рассказать о том, что происходит во встроенном контроллере (Embedded Controller, EC) моего ноутбука. Речь идёт о мультиконтроллере IT8586E, основанном на Intel 8051. Он встроен в ноутбук Lenovo Ideapad 310–15IKB. Но, прежде чем переходить к деталям, полагаю, нелишним будет поговорить о том, что это вообще такое — встроенные контроллеры.
Краткая история встроенных контроллеров на платформах x86
Сначала был создан компьютер IBM PC. Многих это возмутило, данный шаг компании IBM был широко признан неразумным.
В материнскую плату этого компьютера было встроено множество периферийных устройств, вроде Intel 8259 (программируемый контроллер прерываний) или Intel 8253 (программируемый таймер). Обращаться к этим контроллерам можно было, пользуясь инструкциями in/out
ядра x86 (и, на самом деле, к контроллерам 8259 и 8253 всё ещё можно обращаться на современных x86-процессоров, но теперь они входят в состав кристалла CPU). Один из контроллеров IBM PC, Intel 8255 (программируемый контроллер интерфейса периферийных устройств), отвечал за взаимодействие с клавиатурой.
В компьютере IBM PC AT, который был выпущен в 1984 году, контроллер i8255 заменили на Intel 8042, который отвечал за взаимодействие с клавиатурой и назывался контроллером клавиатуры. В адресном пространстве ввода/вывода x86 у него было два адреса — 0x60
и 0x64
. Ядро, пользуясь этими портами, могло отправлять этому контроллеру команды и получать от него данные. У контроллера клавиатуры были неиспользуемые выводы, поэтому специалисты компании IBM решили, что блестящей мыслью будет использовать эти выводы для решения задач, не связанных с клавиатурой, вроде перезагрузки компьютера.
Где-то в конце 1980-х периферийные устройства всё чаще и чаще объединяли, включая в состав одной микросхемы несколько таких устройств. Например — это чипы северного моста и южного моста. Один из таких чипов, Super I/O-контроллер, отвечал за взаимодействие с низкоскоростными устройствами, вроде последовательных портов, параллельных портов и контроллеров дисководов гибких дисков. В состав этого чипа часто входил и контроллер клавиатуры.
Ноутбукам, кроме прочего, требовалась особая система управления питанием. Производители портативных компьютеров взглянули на контроллер клавиатуры, на то, что он способен перезагружать компьютер, и сказали: «На самом деле — это замечательно. Давайте сделаем то, что нам нужно, по похожей схеме». В результате к контроллеру клавиатуры были добавлены два порта с похожим интерфейсом, позволяющим отправлять на устройства команды и получать от них данные. И, фактически, во многих платформах эта возможность реализована в том же самом контроллере, который входит в состав Super I/O-чипа.
В середине 1990-х заметным явлением стал стандарт ACPI (Advanced Configuration and Power Interface, усовершенствованный интерфейс управления конфигурацией и питанием). Он определял стандартизированный интерфейс для EC. А именно, через его командный интерфейс можно было обращаться к 8-битному адресному пространству переменных, определяемых производителями оборудования. Эти переменные можно было описывать с помощью языка AML (ACPI Machine Language), средствами AML можно было описывать и код, позволяющий воздействовать на эти переменные.
В конце 2000-х годов микросхемы южного и северного мостов были объединены с CPU. Правда, надо отметить, что интеграция южного моста (PCH, Platform Controller Hub) в CPU, по всей видимости, имеет место лишь на мобильных платформах. Но в ноутбуках EC всё ещё представлен отдельным чипом, который, кроме того, реализует функционал Super I/O-контроллера и контроллера клавиатуры. Он, например, решает следующие задачи:
- Управление вентиляторами.
- Взаимодействие с подсистемой интеллектуальных аккумуляторов (Smart Battery).
- Чтение данных, поступающих с клавиатуры, и чтение состояния кнопок управления питанием.
- Взаимодействие с регуляторами напряжения.
Схема материнской платы моего ноутбука
Так как EC интенсивно взаимодействует с разными устройствами — неплохо было бы знать о том, что это за устройства. Мне в решении этой задачи повезло — кто-то выложил в интернет так называемые boardview-файлы к интересующей меня плате. В таких файлах содержатся схемы печатных плат, в частности, сведения о компонентах, расположенных на плате, и о том, как они связаны. Между компонентами материнских плат имеется так много соединений, что анализ схемы их связей может оказаться весьма сложной задачей. Я попытался представить связи между компонентами платы в упрощённой форме, результат моих трудов показан на следующей схеме (тут нет большинства линий, имеющих отношение к питанию).
Схема связей компонентов платы (оригинал)
CPU соединён с EC с использованием шины LPC (Low Pin Count), которая, по сути, представляет собой замену шины ISA, применявшейся в более старых компьютерах, в физической реализации которой используется меньше линий связи. Процессор и контроллер взаимодействуют, в основном, по LPC. Но между EC и CPU имеется множество соединений (показанных синими линиями без стрелок), используемых в особых целях. Например — это линия SCI, предназначенная для вызова прерывания от EC в CPU.
Тут, кроме того, имеется соединение, названное BEEP#
, предназначенное для включения на ноутбуке звукового сигнала.
Прошивка EC
Раздобыть прошивку контроллера несложно. Я уже извлекал образ BIOS раньше, для других нужд. После его обработки с помощью утилиты cpu_rec в моём распоряжении оказалось примерно 160 Кб 8051-кода из самого начала образа, расположенного до кода, имеющего отношения к UEFI. Контроллер 8051 имеет 16-битное адресное пространство, а значит 160 Кб кода ему не соответствуют. Получается, что речь идёт о так называемой banked-прошивке. То есть — одни части («блоки» или «банки») прошивки в адресном пространстве подвергаются динамической замене на другие части путём воздействия на некие регистры.
Размеры блоков прошивки обычно, в большинстве контроллеров 8051, составляют либо 32, либо 64 Кб. Выяснить их размер при работе с конкретным контроллером можно, поискав повторяющиеся фрагменты кода. Дело в том, что в разных блоках прошивки обычно имеются одинаковые фрагменты, представляющие код общего назначения (быстро найти такие фрагменты можно попробовать, прибегнув к автокорреляции). Оказалось, что в моём случае речь идёт о блоках кода размером 32 Кб.
Обработка 32-килобайтных блоков с помощью утилиты at51_base позволила узнать о том, что первый блок загружается по адресу 0x0
, а второй, третий и четвёртый — по адресу 0x8000
. Получается, что первые 32 Кб адресного пространства не меняются, а его верхняя область (0x8000
) динамически переключается между тремя имеющимися банками. Пятый банк кода не содержит. В нём имеются только некие данные.
После того, как я всё это выяснил, меня посетила блестящая идея, которая заключалась в том, чтобы посмотреть даташит исследуемого компонента. Даташит на мой контроллер найти не удалось, но мне попались документы на IT8502E, описывающие устройство достаточно близкое к тому, которое было у меня. В нём, что было очень кстати, подробно описывалось большинство I/O-механизмов и, похоже, описание функционирования прошивки, в основном, соответствовало той прошивке, что была у меня.
При просмотре даташита я наткнулся на упоминание отладочного интерфейса I2C, но он, правда, не был документирован. После некоторых изысканий я нашёл проект ECSpy, который представляет собой Rust-реализацию отладчика для EC, созданную силами компании System76, которая работает над собственной прошивкой для EC.
Отладчик даёт доступ на чтение и запись к оперативной памяти и регистрам ввода/вывода EC. Доступ к регистрам отличается дополнительным ограничением, которое заключается в том, что операция записи чётко определена лишь для триггерных регистров (в результате, например, операция, вызывающая изменение состояния конечного автомата, не сработает).
Ещё в даташите сказано, что возможность отладки нужно включать с помощью регистра, но она и так включена. Доступ к ней осуществляется посредством I/O-портов x86 через Super I/O-чип, поэтому ей можно пользоваться из пользовательского пространства, без необходимости писать драйвер.
Для реверс-инжиниринга тех частей прошивки, которые отвечают за взаимодействие с различными устройствами, могут пригодиться спецификации этих устройств. Я, работая над этим проектом, прочитал часть спецификаций ACPI, SMBus и Smart Battery. В процессе исследования кода прошивки используются следующие ресурсы и инструменты: boardview-файлы, даташиты, спецификации компонентов, отладчик Ghidra, обычный браузер, применяемый для поиска дополнительных сведений вроде скан-кодов, исходный код Linux-драйвера для EC, дизассемблированный машинный ACPI-код, отладчик для EC и его исходный код. В таких делах весьма кстати могут оказаться несколько мониторов, использование которых позволяет ускорить сопоставление информации, полученной из разных источников.
В прошивке имеется единственная главная функция, в которой и решается большая часть задач. Обычно прерывания устанавливают какие-то биты, связанные с событиями, а потом эти события обрабатываются за пределами контекста прерываний в главном цикле. В векторе прерываний контроллера имеются описания некоторого количества различных прерываний, но большинство прерываний идут по линии так называемого INTC-прерывания, по адресу 0x13
, после чего осуществляется просмотр большой таблицы переходов, содержащей адреса конкретных обработчиков прерываний.
Большой объём работы выполняется в коде главного цикла, ответственном за обработку событий таймера. Таймер в EC запрограммирован так, что он выдаёт прерывание раз в одну миллисекунду. Потом, на основе прерываний таймера, формируются события, которые происходят, например, каждые 10, 50, 100 мс. Обновление ACPI-переменных выполняется, в основном, в этом контексте.
POST-карты в современных ноутбуках
BIOS, когда производится загрузка x86-системы, регулярно пишет данные в порт ввода/вывода 0x80
. Эти данные сообщают о том, на каком этапе находится загрузка, а так же о происходящих в ходе этого процесса ошибках. Раньше соответствующие сведения отправлялись на шину ISA, куда можно было подключить POST-плату, выводящую текущее значение в шестнадцатеричном формате. Это позволяло диагностировать проблемы, происходящие в ходе загрузки систем. В современных ноутбуках шина LPC представляет собой замену шины ISA, а данные о процессе загрузки до сих пор обычно отправляют на шину LPC.
В прошивке EC, на самом деле, имеется код, ответственный за запись данных в порт 0x80
. В этом коде можно обнаружить команды, с помощью которых данные разделяют на полубайты, используемые потом в роли смещений в следующей таблице:
0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e
Затем, вместе с тактовыми импульсами, осуществляется последовательный вывод результирующего значения на GPIO-пины.
Вышеприведённая поисковая таблица может показаться вам знакомой. Дело в том, что она позволяет преобразовывать полубайты в шестнадцатеричные числа для их вывода на 7-сегментном дисплее. Если взглянуть на схему материнской платы, там можно увидеть две линии — EC_TX
и EC_RX
(эти названия, вероятно, не очень удачны, так как одна из них — это линия данных, а другая — тактовая линия, обе они используются лишь для отправки данных). Они ведут к Wi-Fi-чипу, который подключён к плате через разъём M.2. Но эти линии, на самом деле, ни к чему на Wi-Fi-карте не присоединены.
Это наводит на мысль о том, что существуют некие POST-платы, которые можно подключать к разъёму M.2 для решения проблем, возникающих в ходе загрузки ноутбуков. Так как я знаком с протоколом обмена данными, используемым в M.2-картах, я попытался сделать собственный вариант такой платы (она, в целом, представляет собой два последовательно соединённых сдвиговых регистра, подключённых к 7-сегментным дисплеям). Посмотрите — какая симпатичная у меня получилась штука.
Самодельная POST-плата для разъёма M.2
Но я, к сожалению, видимо что-то напутал, так как эта плата, в итоге, так и не заработала, а мне не очень-то хотелось тратить время на поиск и исправление ошибок, допущенных при её создании.
Клавиатурные механизмы
Ноутбук получает сведения о нажатых клавишах клавиатуры через i8042-порты EC (0x60
и 0x64
). У контроллера имеется прямой доступ к матрице контактов клавиатуры, а значит, ему нужно преобразовывать необработанные данные, поступающие с клавиатуры, в скан-коды PS/2. Ему ещё нужно наблюдать за состоянием клавиши Fn
, которая позволяет определять альтернативный функционал для некоторых клавиш, и сведения о нажатии которой не отправляются процессору.
Можно подумать, что вышеописанная задача решается очень легко, что заключается она в просмотре некоей поисковой таблицы и в отправке хосту результатов. Но, увы, скан-коды PS/2 — это настоящий бардак.
Существует три различных набора скан-кодов, между которыми нет ничего общего. EC использует набор №2. Одиночное нажатие и отпускание клавиши не всегда приводит к генерированию одного байта скан-кода.
Вот примеры:
- Нажатие на клавишу
x
приводит к выдаче скан-кода22
, а её отпускание генерирует скан-кодF0 22
. - Нажатие клавиши
Delete
генерирует скан-кодE0 71
, а её отпускание — кодE0 F0 71
.
Поведение большинства клавиш, как правило, соответствует вышеприведённой схеме, когда при отпускании клавиши генерируется тот же код, что при её нажатии, но с префиксом F0
. А E0
— это префикс для расширенного набора скан-кодов (обратите внимание на то, что при отпускании клавиши Delete
код F0
идёт после кода E0
). Но тут есть некоторые исключения. Например:
- Нажатие кнопки
Pause
(на моём ноутбуке это — сочетание клавишFn + b
) приводит к выдаче последовательности кодовE1 14 77 E1 F0 14 F0 77
. А при её отпускании не генерируется вообще ничего. - Но комбинация клавиш
Ctrl + Pause
выдаёт совершенно другую последовательность кодов, в частности —E0 7E E0 F0 7E
. При отпускании этих клавиш снова ничего не выдаётся. - Существуют клавиши, направленные на управление мультимедийными возможностями, вроде
Mute Microphone
. Своих PS/2-скан-кодов у них нет. Их нажатие приводит к тому, что EC генерирует SCI-прерывание. Затем соответствующий AML-код, связанный с таблицами ACPI, отправляет уведомление операционной системе. После этого ОС считывает значение переменных ACPI EC и генерирует соответствующее событие.
Позиции клавиш на клавиатуре представлены 7-битным целым числом, которое используется в поисковой таблице в роли индекса при преобразовании этого числа в промежуточное байтовое значение. Эта первая поисковая таблица может быть изменена с помощью команд i8042 для переключения на другую раскладку клавиатуры.
Если промежуточное значение меньше 0×80, это значит, что оно просто соответствует обычному однобайтовому PS/2 скан-коду. А если говорить о других значениях, то они, по уже рассмотренной нами схеме, используются в роли индексов в поисковой таблице. Результирующее значение определяет функцию, которая используется для обработки скан-кода. Полученные скан-коды затем помещают в 16-байтовый кольцевой буфер, байты, содержащиеся в котором, если это возможно, потом отправляют хосту.
Тут решаются и другие задачи, вроде борьбы с фантомными нажатиями клавиш (anti-ghosting), устранения ложных повторных нажатий клавиш (debouncing) и поддержки клавиши Fn
, о которой я уже вкратце рассказал.
Интересно то, что контроллер иногда помещает в буфер некоторые скан-коды, делая это в коде, не имеющем отношение к клавиатурным механизмам. Например, речь идёт о сочетании клавиш Win + Q
, или о прерывании обработки нажатия на клавишу. Я, правда, более глубоко в этом не разбирался.
Таинственный фрагмент кода
В ходе общего обзора файла с прошивкой можно заметить, что 4-й блок (0x18000-0x20000
) содержит не особенно много кода. Странно то, что этот код расположен где-то посередине 0xffs
с переходом на область 0x1e000-0x1e596
. Сначала я подумал, что это — код, имеющий какое-то отношение к программам начальной загрузки или прошивки устройства, но после того, как я посмотрел на код, оказалось, что эта мысль неверна.
А именно, при более близком рассмотрении этого кода можно заметить в нём константы, вроде 0x67452301
, 0xEFCDAB89
или 0xCA62C1D6
. Они используются в алгоритме SHA-1.
Зачем встроенному контроллеру SHA-1? Если посмотреть на то, что именно вызывает SHA1-код, то окажется, что он используется при взаимодействии с батареей по SMBus:
- EC случайным образом генерирует последовательность из 20 байтов для одноразового использования (nonce) и отправляет её в регистр батареи
0x27
. - EC вычисляет значение
sha1($secret . sha1($secret . nonce))
, где$secret
— это 16-байтовый секретный ключ, который хранится в прошивке. - EC читает 20 байтов из регистра батареи
0x28
и проверяет, чтобы они совпадали с тем значением, которое было вычислено на предыдущем шаге.
В результате возникает такое ощущение, что батареи, перед их использованием, проходят аутентификацию для предотвращения применения подделок или для каких-то подобных нужд.
Где хранится код?
Прошивка, в соответствии с даташитом, способна обращаться к содержимому флеш-памяти, применяя линейную адресацию, пользуясь 28-битным регистром адреса и 8-битным регистром данных. В коде прошивки можно обнаружить множество операций записи и чтения, направленных на адреса 0x0fff_fe00
и 0x0fff_fd00
. Но размер флеш-памяти далёк от 256 Мб, в результате такие операции, очевидно, не работают с подобными адресами флеш-памяти.
При более близком рассмотрении кода выяснилось, что там, в основном, осуществляется запись 0xff
в 0x0fff_fe00
, а после этого выполняются операции чтения/записи множества байтов — вроде 0x05
, 0x02
или 0xd7
. Оказывается, что это SPI-команды для работы с флеш-памятью, а то, что мы видели — это механизм, с помощью которого код прошивки осуществляет запись и стирание данных. Но если взглянуть на образ, хранящийся в BIOS, оказывается, что он ничем не отличается от оригинала. Может, изменения хранятся лишь в кеш-памяти (которая есть у EC)? А, может быть, соответствующие участки кода попросту никогда не выполняются?
Во многих прошивках для 8051 обычно имеется код, реализующий возможности отладки, и мой — не исключение. Перед функциями, имеющими отношение к контроллеру клавиатуры и к EC, имеется множество функций, которые явно реализуют отладочные возможности. Одна из них — это команда контроллера клавиатуры, предназначенная для чтения из EC данных с адресов флеш-памяти с применением регистра.
Если сделать оттуда дамп прошивки, то получится образ, который отличается от оригинала. Изначально я предполагал, что EC использует образ из флеш-памяти BIOS, так как он хранится в этой памяти, и EC к этой памяти подключён. Но оказалось, что это, на самом деле, не так. К этой памяти напрямую подключены и CPU, и EC. В результате, в том случае, если и тот и другой одновременно попытаются прочесть из неё данные, на шине неизбежно возникнет конфликт. Прошивка, на самом деле, хранится в самом EC. В IT8502 этой возможности не было (это запутало меня ещё сильнее, так как я пользовался даташитом именно для такого контроллера).
Запуск моего собственного кода в EC
Вероятно, стоит сказать о том, что у 8051 имеется некоторое количество различных адресных пространств:
- IRAM: 256 байтов внутренней оперативной памяти (быстрой).
- SFR: 128 байтов адресного пространства ввода/вывода в верхней половине IRAM (при непрямой адресации памяти производится возврат к IRAM).
- XDATA: 16-битная адресуемая внешняя RAM (она медленная, и на данном EC, кроме того, содержит адреса подсистемы ввода/вывода).
- CODE: 16-битное адресуемое пространство кода.
Тут обращает на себя внимание факт отсутствия инструкций для записи в адресное пространство CODE. Но, если опираться на то, о чём мы говорили в предыдущем разделе, внести изменение в это адресное пространство можно, воспользовавшись возможностью записи данных во флеш-память.
Я этого делать не собирался, так как подобные действия несут в себе риск «окирпичивания» устройства. EC можно перепрограммировать, используя входы/выходы клавиатурной матрицы в роли параллельного порта, но я не собирался заниматься и этим.
Ещё одна проблема, связанная с записью данных во флеш-память, заключается в том, что её содержимое проверяется с использованием контрольной суммы. В прошивке имеется сигнатура, которая указывает на то, где начинается контрольная сумма, и в ней имеются два байта, которые различаются в разных редакциях прошивки. Подобное характерно для контрольных сумм. Если дело обстоит именно так — то неясно, где именно начинается и заканчивается контрольная сумма, и то, как именно она вычисляется.
Применив delsum part (инструмент, который я создал ранее, рассчитанный именно на решение подобных задач), можно найти множество кандидатов на роль контрольной суммы: это — CRC с полиномом 0x8005
, а контрольная сумма заканчивается по адресу 0x1ffff
. Но начало контрольной суммы с уверенностью определить нельзя, так как во всех редакциях прошивки, которые я смог найти, соответствующие данные выглядели, до адреса 0x4000
, одинаково. Это значит, что для каждого начального смещения, соответствующего адресу до 0x4000
, имеется набор параметров, применение которого приводит к получению правильных контрольных сумм. При этом можно изменять байты в области памяти, для которой вычисляется контрольная сумма, поступая так при условии, что есть полная уверенность в том, что это делается в правильном месте, так как для вычисления контрольной суммы важна лишь конечная часть этой области памяти.
Правда, для выполнения собственного кода нет нужды выполнять запись данных во флеш-память. В EC для этого имеется другой механизм. А именно — адреса 0x0000-0x1000
в адресном пространстве XDATA — это обычная оперативная память, которую можно отобразить на произвольную область адресного пространства CODE, изменив содержимое кое-каких регистров.
Схема из даташита, на которой показано 5 областей, пронумерованных от 0 до 4, которые могут быть отображены на адресное пространство кода. А именно, речь идёт об областях 0×0000–0×0800, 0×0800–0×0c00, 0×0c00–0×0e00, 0×0e00–0×0f00 и 0×0f00–0×1000
В результате для выполнения собственного кода достаточно лишь выполнить отображение одного из этих фрагментов на память с кодом. Оперативная память в диапазоне 0x0000-0x0e00
используется самой прошивкой, в результате остаются блоки 3 и 4, размер каждого из которых составляет 256 байт.
Правда, чтобы осуществить отображение этой памяти на память с кодом, нужно выполнить запись в регистры и в оперативную память из пространства XDATA. Есть одна отладочная функция, позволяющая писать данные в XDATA, но она содержит ошибки и может писать данные только по адресам, где байт из верхней части адреса является таким же, как и байт из его нижней части. Тут имеется ещё и интерфейс I2C, который достаточно хорошо подходит для решения задачи записи данных в SRAM.
Я сомневался по поводу записи данных в порты ввода/вывода, так как было сказано, что это подходит лишь для триггерных регистров. Но в регистрах, используемых для отображения памяти, имеется ещё и бит для запуска DMA-транзакции (так как этот чип, конечно, поддерживает DMA). В любом случае, не было способа проверить то, что отображение памяти осуществляется именно так, как мне нужно, так как нет отладочной функции, позволяющей читать данные из адресного пространства CODE.
К счастью, был и другой путь: функционал отображения памяти используется для перезаписи флеш-памяти (так как никто не заинтересован в том, чтобы работа программы завершилась бы с ошибкой, когда будет достигнута область с кодом, выполняющим запись данных). Используя I2C можно отредактировать содержимое SRAM в адресном пространстве XDATA, поместив туда отладочный код, и подстроить механизм записи данных во флеш-память так, чтобы он обращался бы к этому отладочному коду.
При таком подходе я смог ещё и проверить, работает ли механизм отображения памяти так, как нужно. Как оказалось, работал он неправильно. Получилось, что, например, отображение адресов 0x0e00-0x0f00
из пространства XDATA на область, начинающуюся с адреса 0x7722
, приводит к отображению адреса 0x7722
на 0x0e22
(а не на 0x0e00
), адреса 0x7723
на 0x023
, адреса 0x7800
на 0x0e00
, и адреса 0x7821
на 0x0e21
. Это, возможно, результат ошибки в самом чипе.
В любом случае, эту проблему легко обойти, так как между адресами, всё равно, имеется взаимно однозначное соответствие, а значит — код можно просто записать в те места пространства XDATA, которые будут соответствовать нужным местам памяти. Оказалось, что I2C-запись в регистры, отвечающие за отображение памяти, тоже работает. В результате для выполнения отладочного кода с его последующей записью мне не нужно было бы прибегать к режиму перезаписи флеш-памяти.
Итоги
Я приступил к разработке программы, которая берёт ihex-файл и настраивает страницы 0xe00
и 0xf00
, в результате чего я могу просто указать местоположение патча, пользуясь ассемблерной инструкцией ORG
, а программа сама заполнит непропатченные области кодом прошивки из образа и правильно запишет код в SRAM. Это упрощает жизнь программиста и, пожалуй, ускоряет процесс разработки кода для EC.
На сегодня это всё, а в следующий раз я расскажу об исследовании прошивки ноутбучного Wi-Fi-модуля (RTL8821AE) и о разработке небольшого кейлоггера, основанного исключительно на возможностях Intel 8051.
Занимались ли вы разработкой прошивок для контроллеров, основанных на Intel 8051?