STM32 з нуля без HAL: Місяць 4, Тиждень 3: пишемо /dev/mydev — character device driver. Частина 13

Ну що ж, 13 тижнів позаду, за цей час отримав багато повідомлень. Що радує — це те, що ця серія статей і інші мої опуси для когось стали тригером для написання своїх серій і статей, хтось робить це заради піару, хтось розвиває україномовний контент, а хтось ділиться безцінним досвідом. Хай там як, я щасливий, бо мене питали, навіщо це все, ти графоман чи блажений, навіщо стільки часу витрачати на це все. Ну ладно, якби ти шукав роботу і хотів пропіаритись, чи володів безцінним досвідом, який вартий уваги. А ти просто робиш, описуєш це і публікуєш. Так, просто описую, бо це пригода, бо це мій стиль життя.

Якщо ти читаєш мої опуси і знаходиш їх корисними, чи вони надихнули тебе на щось подібне, напиши мені особисто чи в коментарі, це не займе багато часу, але це маленьке «Дякую» покаже іншим, що такий контент потрібен українською, хоча б тому, що на цих текстах можливо будуть навчати україномовні моделі, і ти будеш зашитий в її тензори.

◆ ASIC, FPGA, Jetson — де ми в цьому всьому

Після виходу попередньої статті прийшов цікавий коментар від Evgen Ryabko — людини з досвідом в avionics і medical embedded (laryngoscope на Qualcomm SOM + STM32 + GStreamer, IEC-62304). Він звернув увагу, що в серії мова постійно йде про ASIC, і це правда. Варто один раз пояснити, де ми в загальному embedded ландшафті.

ASIC (Application-Specific Integrated Circuit) — це те, що у нас:

  • BCM2837 на Raspberry Pi — фіксований silicon, ARM Cortex-A53, виготовлений один раз;
  • RV1106 на Luckfox — теж ASIC, але з NPU всередині;
  • STM32F103 — ASIC, Cortex-M3.

FPGA (Field-Programmable Gate Array) — програмована логіка. Купляєш чіп, завантажуєш bitstream і він стає тим що тобі треба — власний процесор, кастомний DSP, паралельний відеопроцесор. Мова — VHDL або Verilog, не C. Toolchain — Vivado або Quartus, не gcc. Це зовсім інший світ, і він не в нашій серії.

Jetson (NVIDIA) — теж ASIC, але з потужним GPU. Технічно ближче до нас (embedded Linux + AI), але це протилежне нашій тезі «AI на слабкому залізі». Jetson Nano коштує від $100, споживає 5-10W, і призначений для людей яким потрібен GPU. Нам він не потрібен.

Наша серія свідомо залишається в зоні ASIC без GPU: MCU + Linux SBC + Buildroot + kernel modules. Це те залізо, що є у більшості читачів, те, що реально використовується в IoT і embedded продуктах, де немає бюджету на GPU і немає сенсу в FPGA.

До речі, проєкт Evgena — Qualcomm SOM + STM32 MCU + GStreamer для медичного пристрою — це буквально та сама архітектура, що я намагаюсь зробити тут, тільки в IEC-62304 продакшні. Різниця в масштабі і сертифікації, а не в принципах. Якщо у вас є досвід з подібними проєктами — пишіть в коментарях, цікаво порівнювати.

◆ Як Linux бачить пристрої

Перш, ніж перейдемо до коду — коротко про те, що таке character device. У Linux є три типи пристроїв:

Character device — це потік байтів без внутрішньої структури. Ядро присвоює йому major і minor числа:

4 — номер драйвера (UART driver). 64 — конкретний пристрій у межах цього драйвера. Ядро по major числу знаходить драйвер, по minor — конкретний device instance.

Наш mydev.ko буде:

  • Запитувати major number динамічно (alloc_chrdev_region).
  • Реєструватись як character device (cdev_add).
  • Автоматично створювати /dev/mydev через udev (device_create).
  • Реалізовувати openreadwriterelease.

◆ Готуємо середовище

◆ Для самих маленьких: звідки береться ssh pi+

По серії ти часто побачиш команди типу ssh pi чи ssh pi+ — це не окрема утиліта, а SSH host alias. Замість ssh alex@192.168.1.123 щоразу, один раз описуєш плату в ~/.ssh/config:

І далі просто ssh pi+. Коли плат у тебе не одна-дві, а ціла флотилія (у мене на цьому етапі 6 Raspberry Pi плюс Luckfox і Orange Pi), це рятує від постійного набивання IP руками. Туди ж можна додати ключі, порти, прапорці — наприклад, для Luckfox, який не любить pubkey-авторизацію:

Між іншим, є ще один зручний спосіб — ProxyJump. Якщо якась плата сидить у підмережі, куди немає прямого доступу, і достукатись до неї можна лише через проміжний хост, це пишеться в один рядок:

Тепер ssh orange-hidden спершу зайде на pi+, а звідти прокине з’єднання далі — жодних ручних тунелів. Для розподілених штук на кшталт майбутньої TDOA-флотилії, де ноди розкидані по сегментах мережі, це стане в нагоді.

Дрібниця, але коли ти щодня стрибаєш між п’ятьма платами — це різниця між «набрав ssh pi+» і «згадав який там був IP у 3B+».

Зверни увагу,що рядок ProxyJump pi+ посилається на вже описаний вище alias — показує що хости в конфігу можна складати ланцюжком.

Перед тим як писати свій драйвер, хочу глянути, що вже є в системі — щоб розуміти, куди ми вписуємось, і не наступити комусь на major. Дивимось усі character devices і список зареєстрованих драйверів:

⚠ Будь уважний: це дві окремі команди, не одна. Якщо набрати їх в один рядок через кому (grep "^c", cat /proc/devices) — shell не зрозуміє коми як розділювача (це не Python), і grep сприйме cat за ім’я файлу: grep: cat: No such file or directory. Розділювач команд у shell — ; або новий рядок, а не кома.

І ось що цікаво у виводі /proc/devices. Статичні драйвери сидять на маленьких номерах — 1 mem4 tty5 console10 misc. А все, що ядро роздає динамічно, лізе з верхнього краю вниз: 242 rpmb241 hidraw240 binder239 gpiomem238 media237 cec. Тому коли наш alloc_chrdev_region попросить вільний major, ми опинимось десь поряд — у мене вийшло 236. Ось чому хардкодити major руками — погана ідея: система й сама знає, що вільно, треба тільки попросити.

До речі, тут же у виводі вже присутні майбутні герої — gpiochip0gpiochip1gpiomem. До них ми ще дійдемо.

◆ Код: mydev.c

Makefile:

◆ Збірка

Чисто, без warning’ів. Зверни увагу на class_create(DRIVER_NAME) в коді — один аргумент. На ядрі 6.18 (наш 3B+) старий двоаргументний виклик class_create(THIS_MODULE, name) просто не скомпілюється.

◆ Ґуля № 1 — Permission denied

device_create створив вузол з правами 0600 за замовчуванням — читати й писати може тільки root. Так поводиться драйвер без явного налаштування прав. Швидкий обхід — sudo

Працює. Але правильне рішення виставити права через udev rule:

⚠ Тут легко наступити на ще одні граблі: вміст udev-правила треба покласти у файл через tee, а не вставляти як команду в термінал — KERNEL=="mydev", MODE="0666", набраний прямо в shell, bash спробує виконати як команду й видасть помилку command not found.

Тепер без sudo:

◆ Ґуля № 2 — чому cat не зависає (два виклики read)

Найцікавіша частина — дивимось у dmesg що насправді відбувається:

Розбираємо по рядках:

  • write() called, count=13 — echo "hello kernel" дає 13 байтів, а не 12. echo сам додає \n у кінці — звідси +1.
  • open/release з різними pid (1483 і 1487) — echo > file і cat file це два окремих процеси, кожен зі своїм відкриттям і закриттям файлового дескриптора.
  • read() called, count=262144 — це розмір внутрішнього буфера cat, не нашого. cat завжди просить «скільки є», а драйвер віддає скільки реально має.
  • Перший read() повертає 13 байтів, ppos зсувається на 13.
  • Другий read() викликається з ppos=13. У коді є перевірка if (*ppos >= kbuf_len) return 0; — це і є EOF. cat бачить 0 байтів і зупиняється.

Якби цієї перевірки не було (або вона завжди повертала б to_copy навіть коли даних більше нема), cat подумав би, що дані ще є, викликав read() знову — і отримав би нескінченний потік однакових байтів. Класична «зависло» character device, з якою стикається майже кожен, хто пише перший драйвер.

◆ Ґуля № 3 — copytouser / copyfromuser, а не memcpy

Прямий доступ до користувацького вказівника з kernel space заборонений — userspace і kernel живуть у різних адресних просторах, і ядро не довіряє адресі, яку йому передав userspace (вона може бути невалідна або взагалі належати чужому процесу).

⚠ Зверни увагу: copy_to_user/copy_from_user повертають кількість байтів, яку НЕ вдалось скопіювати (0 = повний успіх). Це протилежно memcpy, який повертає вказівник на призначення. Переплутати легко, і код мовчки «працюватиме» неправильно, поки не натрапиш на реальну помилку копіювання.

◆ Tainted kernel і контекст живлення

5120 — ядро позначене tainted одразу кількома прапорцями, серед них той самий, що бачили в dmesg при insmod:

Будь-який модуль, зібраний поза офіційним деревом ядра, одразу «забруднює» ядро цим прапором — це не помилка, просто маркер для діагностики (якщо щось впаде, maintainer’и ядра одразу бачать, що в системі був сторонній код).

throttled=0x50000 — у логах під час сесії кілька разів проскакувало:

0x50000 означає що недостатнє живлення траплялось в цьому сеансі раніше, але зараз все в нормі (молодші біти, що відповідають за активну проблему прямо зараз, не виставлені). Важливо це записати собі окремо: якщо колись з’явиться дивна поведінка модуля чи SD-картки, перше, що варто перевірити — не сам код, а vcgencmd get_throttled. Просадки живлення на Pi 3B+ під навантаженням — не рідкість, і легко прийняти їх симптом за баг драйвера.

◆ Підсумок

Що ми пройшли за цей тиждень. Почали з того, як Linux взагалі бачить пристрої — три типи, major і minor числа, character device як потік байтів. Далі написали mydev.c з нуля: попросили в ядра вільний major через alloc_chrdev_region, зареєстрували cdev з cdev_add, віддали udev створення /dev/mydev через class_create + device_create, і реалізували чотири операції file_operations — openreadwriterelease. Між kernel і userspace ходили тільки через copy_to_user / copy_from_user, бо прямий memcpy по користувацькому вказівнику — це шлях до oops.

І по дорозі набили нормальні такі гулі, кожну на реальному залізі:

  • device_create дає вузол з правами 0600 — echo від звичайного користувача впирається в Permission denied, поки не додаси udev rule з MODE="0666".
  • cat викликає read() двічі: перший раз бере дані, другий отримує 0 (EOF) — і саме ця перевірка *ppos >= kbuf_len рятує від нескінченного циклу.
  • copy_to_user/copy_from_user повертають кількість байтів, яку не скопіювали (0 = успіх) — протилежно інтуїції від memcpy.
  • class_create на ядрі 6.4+ приймає один аргумент замість двох — старі приклади з інтернету просто не компілюються.

Це той самий мінімум, що відрізняє «вмію вантажити чужі модулі» від «можу написати свій драйвер». Далі його можна нарощувати — ioctl для команд, poll для асинхронності, кільцевий буфер замість одного слоту — але каркас уже стоїть.

Наступного тижня — HC-SR04. Беремо ультразвуковий далекомір і робимо з нього character device, але цього разу пасивним читанням буфера не обійтись: треба смикнути trigger-пін, дочекатись echo, і виміряти час між фронтами сигналу. А час між фронтами в Linux означає одне — переривання. Зайдемо в interrupt handling з ядра: request_irq, обробка по фронту, вимірювання тривалості імпульсу. І cat /dev/hcsr04 покаже відстань у сантиметрах. Там на нас чекає нова порція ґуль — interrupt-driven вимірювання на Linux, який і близько не реального часу, це окрема пригода.

◆ Дивись також

◆ Словник embedded термінів

Мінімум, який нам мою думку має знати junior embedded-розробник, і про який можуть спитати на співбесіді. Не факт, але це покриває bare-metal STM32 і Linux kernel modules з нашої серії. Колонка «💬 на співбесіді» — типовий ракурс питання.

◆ Загальні поняття

Термін Що це 💬 Питання
Bare-metal Код, що працює прямо на залізі без ОС. Сам керуєш регістрами, перериваннями, пам’яттю «Чим bare-metal відрізняється від програмування під Linux?»
Firmware Прошивка — програма, зашита в пам’ять пристрою
RTOS Операційна система реального часу (FreeRTOS, Zephyr). Гарантує час реакції на подію «Навіщо RTOS, якщо є super-loop у while(1)
HAL Hardware Abstraction Layer — шар, що ховає роботу з регістрами за зручними функціями (gpio_write) «Що дає свій HAL і чим він гірший за прямі регістри?»
Toolchain Набір інструментів збірки: компілятор, лінкер, асемблер (arm-none-eabi-gcc)
Cross-compilation «Чому не можна просто gcc на ПК для STM32?»
SoC System on Chip — процесор + периферія на одному кристалі (RV1106, BCM2837)

◆ Пам’ять і C

Термін Що це 💬 Питання
volatile Каже компілятору «не оптимізуй це, значення може змінитись ззовні» Класика. «Навіщо volatile для регістрів і змінних з ISR?»
MMIO Memory-Mapped I/O — регістри периферії доступні як звичайні адреси пам’яті «Як CPU спілкується з GPIO?»
.text Секція з кодом програми (read-only, у Flash) «Де живе код, а де змінні?»
.data Ініціалізовані глобальні змінні (копіюються з Flash у RAM при старті)
.bss Неініціалізовані глобальні (обнуляються в Reset_Handler) «Що відбувається з .bss до main()
Stack Стек — локальні змінні, адреси повернення. Росте вниз «Stack vs heap, де що?»
Heap Купа — malloc. У bare-metal зазвичай уникають «Чому malloc погано в embedded?»
Linker script .ld-файл: де у пам’яті лежать секції, де стек, де вектори «Навіщо лінкер-скрипт?»
Endianness Порядок байтів: little-endian (ARM) vs big-endian «У якому порядку зберігається uint32_t

Чому volatile критичний (готова відповідь): Компілятор бачить while (!(SR & RXNE)); і думає: «SR я вже прочитав, він не міняється — зациклю на старому значенні». Без volatile цикл зависне назавжди. volatile змушує читати з пам’яті щоразу. Те саме з прапором, який ставить ISR: без volatile основний код його «не помітить».

◆ Периферія

Термін Що це 💬 Питання
GPIO General Purpose I/O — універсальний цифровий пін (вхід/вихід)
UART Послідовний інтерфейс, 2 дроти (TX/RX), без тактового сигналу «Скільки дротів у UART? Що таке baud rate?»
I²C Двопровідна шина (SDA/SCL), багато пристроїв на адресах «Чим I²C відрізняється від SPI?»
SPI Швидка шина (MOSI/MISO/SCK/CS), full-duplex «Коли SPI, коли I²C?»
PWM Широтно-імпульсна модуляція — це метод керування потужністю, що базується на зміні тривалості електричних імпульсів, а не їхньої амплітуди. Замість постійного живлення пристрій отримує серію швидких ввімкнень та вимкнень, що дозволяє ефективно регулювати напругу без виділення зайвого тепла

◆ Переривання і час

Термін Що це 💬 Питання
Interrupt / IRQ Сигнал, що змушує CPU кинути все й виконати обробник Класика. «Interrupt vs polling?»
ISR Interrupt Service Routine — функція-обробник переривання «Що НЕ можна робити в ISR?»
Polling Активне опитування в циклі замість переривання
Vector table Таблиця адрес обробників на початку Flash (у нас — startup.c) «Що лежить за адресою 0×08000000?»
NVIC Контролер переривань у Cortex-M (пріоритети, вмикання)
SysTick Системний таймер ядра Cortex-M (у нас — delay_ms) «Як зробити затримку без бібліотек?»
DMA Прямий доступ до пам’яті без участі CPU «Навіщо DMA при роботі з UART/ADC?»
Watchdog Сторожовий таймер: перезавантажує систему, якщо її «повісило» «Як захиститись від зависання?»
Race condition Гонитва: результат залежить від порядку доступу до спільних даних

Що не можна в ISR (готова відповідь): ISR має бути коротким. Не можна: довгі затримки, printf/malloc, блокуючі операції. Спільні з основним кодом дані — тільки через volatile (а в Linux — через блокування). Ідея: ISR ставить прапор / кладе байт у буфер, а важку роботу робить основний цикл.

◆ Завантаження

Термін Що це 💬 Питання
Bootloader Перша програма після ввімкнення; готує систему й запускає основну «Що відбувається від подачі живлення до main()
Reset_Handler Перша функція bare-metal: копіює .data, чистить .bss, кличе main() «Хто викликає main() на STM32?»
U-Boot Універсальний bootloader для embedded Linux «Роль U-Boot у boot-ланцюгу?»
SPL Secondary Program Loader — мінімальний перший етап (вліз у малий SRAM)
Boot sequence ROM → SPL → U-Boot → kernel → init → userspace «Намалюй ланцюг завантаження Linux»
RCC / clock gating Тактування периферії: вимкнений блок не працює, поки не ввімкнеш такт «Чому GPIO мовчить, хоч код правильний?»

◆ Linux kernel

Термін Що це 💬 Питання
udev User-space демон, що автоматично створює/видаляє /dev/* вузли, коли ядро повідомляє про новий пристрій «Звідки береться /dev/sdb1, коли встромляєш флешку?»
mknod Ручне створення device-вузла (mknod /dev/x c major minor). Потрібен тільки якщо драйвер НЕ використовує class_create/device_create «Чим відрізняється ручний mknod від автоматичного udev?»
class_create / device_create API ядра: драйвер сам реєструє клас і просить udev створити /dev/... без ручного mknod «Як /dev/mydev з’являється сам після insmod
Device permissions (udev rules) За замовчуванням device_create дає 0600 (тільки root). Права налаштовуються через /etc/udev/rules.d/*.rules, наприклад KERNEL=="mydev", MODE="0666" «Чому echo > /dev/mydev дає Permission denied навіть коли драйвер запущений?»
User space / kernel space Розділення: програми (обмежені права) vs ядро (повний доступ) Класика. «Чим відрізняються?»
System call Місток із user space у kernel (readwriteopen) «Як userspace просить ядро щось зробити?»
Kernel module Код, що додається в ядро на льоту (insmod/rmmod) «Навіщо модуль, а не вкомпілювати в ядро?»
Character device Пристрій, з яким працюють побайтово як з потоком (/dev/ttyS0) «char vs block device?»
Block device Пристрій блоками з довільним доступом (диск, SD)
major / minor Старший номер = драйвер, молодший = конкретний пристрій «Що значать цифри в ls -l /dev/...
file_operations Структура з вказівниками на open/read/write/release драйвера «Як cat читає твій /dev/mydev?»
sysfs / procfs Віртуальні ФС, що показують стан ядра (/sys/proc)
Device Tree Опис заліза для ядра у вигляді дерева (.dts → .dtb) «Як ядро дізнається, яка периферія є на платі?»
printk printf ядра; вивід читаємо через dmesg
Platform driver / probe Драйвер, що «знаходить» своє залізо через DT; probe() викликається при збігу «Що таке probe і коли він спрацьовує?»
copy_to_user / copy_from_user Безпечне копіювання між kernel і user space «Чому не можна просто memcpy з userspace-вказівника?»
Kernel oops / panic oops — локальна помилка (модуль), panic — фатальна (система стає) «Різниця між oops і panic?»
Tainted kernel «Забруднене» ядро (сторонній модуль, oops) — впливає на підтримку

user vs kernel space (готова відповідь): User space — де живуть програми, з обмеженими правами й власною віртуальною пам’яттю; впала програма — впала тільки вона. Kernel space — ядро з повним доступом до заліза; помилка тут валить усю систему. Перехід — лише через системні виклики. Тому copy_from_user: вказівник з userspace не можна довіряти напряму (може бути невалідним або чужим).

◆ Конкурентність

Термін Що це 💬 Питання
Mutex Блокування, що може спати; для довгих секцій, тільки в контексті процесу «mutex vs spinlock — коли що?»
Spinlock Блокування, що крутиться в циклі (busy-wait); для коротких секцій, можна в ISR
Semaphore Лічильник доступів; дозволяє N одночасних власників
Atomic Операція, яку не можна перервати посередині (atomic_inc) «Як безпечно інкрементувати лічильник?»

mutex vs spinlock (готова відповідь): Mutex може заснути, поки чекає — тому не можна в ISR (там спати не можна). Spinlock крутиться в циклі й не спить — підходить для ISR, але марнує CPU, тож тільки для дуже коротких секцій. Правило: довга критична секція в контексті процесу — mutex; коротка або в перериванні — spinlock.

◆ «Підступні» питання

Питання Коротка правильна відповідь
Навіщо volatile? Заборонити оптимізацію змінних, що міняються ззовні (регістри, прапори з ISR)
Чому не malloc в ISR? Може блокувати/спати, фрагментує купу, недетермінований час — ISR має бути швидким
sudo echo 1 > /sys/... не працює, чому? > виконує шел від користувача, не від root. Треба echo 1 | sudo tee
echo > /dev/mydev дає Permission denied, хоч модуль завантажений? device_create за замовчуванням ставить 0600 (тільки root). Треба sudo або udev rule з MODE="0666"
Чому GPIO мовчить, хоч код вірний? Не ввімкнене тактування порту (RCC / clock gating)
Stack чи heap росте вгору? Stack зазвичай вниз, heap вгору — назустріч одне одному
Що буде при колізії stack і heap? Stack overflow / пошкодження даних — у bare-metal без MMU тихо і боляче
Чому модуль під інше ядро не вставляється? Version magic: .ko прив’язаний до версії й конфігу ядра
open-drain навіщо? Кілька пристроїв на одній лінії (I²C), wired-AND, рівні через pull-up

◆ Що читати далі

  • RM0008 — Reference Manual STM32F103 (головний довідник по регістрах)
  • Linux Device Drivers 3rd ed. — lwn.net/Kernel/LDD3/ (безкоштовно)
  • The Linux Programming Interface — Michael Kerrisk (по системних викликах)
  • The Linux Kernel Module Programming Guide — tldp.org

📌 Команди до цих термінів.

◆ Додаток: довідник команд

Усе, що ми використовували в статтях 11–12 (kernel modules на Raspberry Pi), зібране в одному місці. Плюс кілька команд, які знадобляться далі. Тримай під рукою — у kernel-розробці пів роботи це dmesg в одному вікні й make в іншому.

◆ Керування модулями ядра

Команда Що робить Коли треба
insmod вставляє конкретний .ko під час розробки, локальний файл
modprobe шукає в /lib/modules, тягне залежності коли модуль вже встановлений
rmmod вивантажує після тесту
modinfo метадані модуля перевірити module_param, ліцензію

⚠ Якщо rmmod каже Module is in use — хтось тримає /dev/... відкритим або лічильник посилань (refcount) не нуль. Дивись lsmod (третя колонка — used by). rmmod -f тільки якщо ядро зібране з CONFIG_MODULE_FORCE_UNLOAD — інакше шлях один: reboot.

◆ Логи ядра — dmesg і printk

Рівні printk у коді модуля:

⚠ pr_debug мовчить, поки модуль не зібраний з -DDEBUG або не ввімкнений через dynamic debug (/sys/kernel/debug/dynamic_debug/control).

◆ Збірка kernel-модуля

Мінімальний Makefile для out-of-tree модуля:

⚠ Модуль зібраний під одну версію ядра не вставиться в іншу — insmod дасть version magic ... should be .... Перевір uname -r ДО збірки. На нашому 3B+ це 6.18.33+rpt-rpi-v8.

◆ sysfs / procfs — підглядаємо в ядро

⚠ module_param з правами 0644 можна міняти через sysfs після завантаження — але це НЕ викликає probe() повторно. Класична «ґуля»: думаєш, що змінив параметр і драйвер перечитав, а він прочитав значення один раз під час insmod.

◆ Character device — /dev/mydev

⚠ Якщо в драйвері використовуєш class_create + device_create, вузол /dev/mydev створює udev автоматично — mknod руками тоді не потрібен. mknod лишається для випадку «голого» register_chrdev без класу.

Права на автостворений вузол — device_create за замовчуванням дає 0600 (тільки root). Без правила echo/cat від звичайного користувача впадуть з Permission denied:

⚠ Вміст udev-правила (KERNEL=="mydev", MODE="0666") пиши через tee у файл, а НЕ вставляй у термінал як команду — bash спробує її виконати і впаде з command not found.

◆ GPIO з командного рядка — libgpiod 2.x

⚠ libgpiod 2.x ламає сумісність з 1.x. У 1.x чіп передавався позиційно: gpioget gpiochip0 17. У 2.x — через -c/--chip, а флаг -t/--toggle з’явився саме у 2.x. На свіжому Pi OS у тебе майже напевно 2.x — стара шпаргалка з інтернету не спрацює.

Raspberry-специфічне (Bookworm і новіше):

Старий sysfs-інтерфейс /sys/class/gpio/export — deprecated ще з ядра 4.8 і прибраний у нових. Цікавий, тільки як історичний контраст (це окрема тема — userspace GPIO проти kernel module).

◆ Device Tree та overlay

Підключення в /boot/firmware/config.txt:

⚠ На Bookworm конфіг переїхав у /boot/firmware/config.txt (раніше /boot/config.txt). Помилки DT під час boot шукай у dmesg | grep -i overlay.

◆ Інформація про систему

◆ Дебаг і аналіз бінарів

Читаємо kernel oops: у дампі шукай рядок PC is at <функція>+0x.../0x... та Call trace: — це стек викликів у момент падіння. +0x.. це зсув від початку функції; через addr2line або objdump мапиться на конкретний рядок коду. NULL-дереференс зазвичай виглядає як звернення за адресою близько 0000000000000000.

⚠ Після oops модуль часто лишається «напівживий»: lsmod показує його, а rmmod не дає. Ядро при цьому позначається tainted (cat /proc/sys/kernel/tainted ≠ 0). Чистий стан — тільки після reboot.

◆ Дрібниці, які економлять час

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *