디바이스 드라이버

심화 실습이 한바탕 휩쓸고 지나간 11주차는 디바이스 드라이버에 대해 학습을 했습니다. 중요한 내용처럼 느껴졌지만 이해를 한 것은 ..zero!! 그러니 블로그를 작성하면서 복습을 한 번 해봅시다.!! 이 글만 읽어도 디바이스 드라이버 완전 정복 ? 이라는 말이 나오도록 한번 가보즈악 .🛫

우선 현재 사용하는 리눅스 커널의 드라이버 소스코드를 확인해봅시다.

https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/

해당 링크에서 라즈베리 파이 및 우분투에 설치되어 있는 커널 버전을 다운로드 받을 수 있습니다.

다운로드 이후에는 폴더 내에서 드라이버의 소스코드를 확인할 수 있습니다 .

커널 소스 트리

디렉토리설명주요 역할 및 특징
arch/ArchitectureCPU 아키텍처별 종속적인 코드 (x86, ARM, RISC-V 등). 부팅 시 하드웨어 초기화 코드가 포함됨.
block/Block LayerHDD, SSD와 같은 블록 장치 입출력 스케줄링 및 제어 로직.
certs/Certificates커널 모듈 서명 및 보안 인증 관련 인증서와 키 처리.
crypto/Cryptography암호화 알고리즘 (AES, SHA 등) API 제공.
drivers/Device Drivers각종 하드웨어용 드라이버 (GPU, USB, Network 등). 커널 소스의 약 60% 이상을 차지함.
fs/File Systems파일 시스템 구현체 (EXT4, VFS, NTFS, NFS 등).
include/Header Files커널 전반에서 사용되는 공통 헤더 파일 (.h).
init/Initialization커널 부팅 초기화 코드 (main.c 포함). start_kernel() 함수가 여기서 실행됨.
ipc/Inter-Process Comm.프로세스 간 통신 (Semaphore, Message Queue, Shared Memory 등).
kernel/Core Kernel커널의 핵심 로직 (스케줄러, 프로세스 관리, 시그널 처리 등).
lib/Library routines커널에서 공통으로 사용하는 유틸리티 함수 (문자열 처리, 압축 등).
mm/Memory Mgmt.메모리 관리 코드 (가상 메모리, 페이징, 할당/해제 알고리즘).
net/Networking네트워크 프로토콜 스택 (TCP/IP, Bluetooth, IPv6 등).
scripts/Scripts커널 빌드(Compile) 및 관리에 필요한 스크립트 도구들.
tools/Tools커널 개발 및 테스트에 필요한 사용자 공간(User space) 도구.
Documentation/Documentation커널 기능 및 개발 가이드 문서.
  • /init 폴더 내에는 부팅 시 실행되는 main func를 확인 할 수 있습니다.
  1. Bootloader (U-Boot 등): 하드웨어를 최소한으로 깨우고 커널 이미지를 메모리에 로드

  2. Architecture Setup (arch/ 폴더 내 코드):

    • 어셈블리어로 작성된 코드들이 먼저 실행
    • CPU의 모드를 설정하고, 페이지 테이블을 만들고, MMU(메모리 관리 유닛) ON
    • 아키텍처에 따라 ( x86, ARM) 완전히 다르다
  3. start_kernel()의 역할

    위의 하드웨어 종속적인 준비가 끝나면, 드디어 우리가 찾은 init/main.c에 있는 start_kernel()로 점프


이처럼 리눅스 커널의 코드를 확인했다면 본격적으로 리눅스 드라이버로 들어가봅시다 .

드라이버 작성시 고려사항

드라이버를 작성하기 위해서는 고려해야할 몇가지 사항들이 있습니다 .

  1. 드라이버의 실행 환경은 kernel space이다 .

    -> 사용자 프로그램과 커널은 메모리 주소 체계가 완전히 분리되어 있다.
    -> 전용 커널 함수를 사용한다 .

커널에서의 오류는 시스템에 치명적인 영향을 미칠 수 있으니 주의해서 사용 해야 합니다 .

  1. 효율적인 하드웨어 제어

    하드웨어는 CPU보다 처리 속도가 느립니다. 따라서 드라이버가 디바이스의 응답을 어떻게 기다리느냐에 따라 시스템 전체의 성능이 결정됩니다.

  • 인터럽트 : 하드웨어 작업 완료시 신호 전송
  • bottom half : 중요한 일은 즉시 처리, 시간이 걸리는 task는 나중으로 지연
  • non blocking I/O : 하드웨어가 준비되지 않았을 때 프로세스를 wait시킬지 return할지를 고려 이처럼 다양한 처리 방식을 드라이버에서 하드웨어와 CPU의 동작에 따라 나누어 구현해야 합니다 .
  1. 병행성 및 동기화 : 커널은 여러 프로세스가 동시에 실행되는 멀티 태스킹 환경
    1. Race Condition
    2. 동기화
      1. spinlock : 대기 시간이 짧고 잠시 동안만 자원 점유 ( cpu 점유 & 대기)
      2. Mutex , Semaphore : 대기 시간이 길어서 프로세스 sleep
  2. 콜백 함수를 제공해야 한다.
    1. ISR (interrupt service routine)
    2. file operations interfaces
  3. 커널 기능을 이용
    1. 메모리 할당
    2. 인터럽트
    3. 동기화

디바이스 드라이버의 형태

  1. 커널 드라이버/모듈 넣는 두 가지 주요 방식
구분In-tree (커널 트리 내부)Out-of-tree (외부/독립)
소스 위치커널 소스 트리 내부 (drivers/ 아래에 폴더 생성)커널 소스와 완전히 별도의 디렉토리
대표 경로~/linux-6.8/drivers/char/my_driver/~/my_driver_project/
빌드 방식커널 전체 빌드 시 같이 컴파일make -C /lib/modules/$(uname -r)/build M=$PWD
장점
  • 의존성 관리 쉬움
  • 커널과 완벽 통합
  • 커널 소스 안 건드림
  • 빠른 수정/빌드/테스트
단점
  • 커널 전체 다시 빌드해야 함
  • 패치 관리 복잡
  • 커널 API 변화에 취약
  • 심볼 충돌 가능성
주 사용 시기upstream 제출 예정, 안정화 단계초기 개발, 프로토타이핑, 회사 내부 드라이버

커널 모듈 .ko의 장점😯

장점
├─ 동적 로드/언로드 가능 (insmod / rmmod)
├─ 커널 재컴파일 & 재부팅 없이 교체 가능
└─ 메모리 효율적 (필요할 때만 메모리 사용)

단점
├─ 커널 API/심볼을 엄격히 따라야 함
├─ C언어로만 작성 가능 (Rust는 아직 제한적)
└─ 커널 오염(taint) 발생 (out-of-tree 모듈은 기본적으로 taint됨)

커널 모듈의 빌드 및 설치 Flow

# 1. 빌드
make

# 2. 생성된 파일 확인
ls
# hello.ko  hello.mod.c  hello.o  Module.symvers  modules.order

# 3. 모듈 로드
sudo insmod hello.ko

# 4. 메시지 확인 (여러 방법)
dmesg | tail -n5
# 또는
sudo journalctl -k --since "5 minutes ago" | grep hello

# 5. 모듈 목록에서 확인
lsmod | grep hello

# 6. 모듈 제거
sudo rmmod hello

# 7. 제거 후 메시지 다시 확인
dmesg | tail

디바이스의 종류

  • 문자 디바이스 : char
    • 바이트 단위의 입출력
    • /dev의 node를 통해 접근
      • /dev/console , /dev/ttyS0
    • 주로 순차 참조
    • keyboard , Mouse , Serial , Modem , Video
  • 블록 디바이스 : block
    • 블록 단위 입출력 : 버퍼 사용
    • /dev/sda (저장 장치)0
    • 순차 및 랜덤 참조
    • Hard disk , CD_ROM , USB disk

장치 번호

  • major : 장치의 종류를 구분
    • 문자 디바이스 테이블의 인덱스로 사용됨
    • 드라이버를 찾아오게 할 수 있는 번호
  • minor : 동종 장치 중 개별 물리적 장치를 구분하기 위한 번호

-> 이 때 문자 장치와 블록장치는 독립적으로 사용된다.

  • 장치 등록
    struct dev 사용
#include <linux/cdev.h>

struct cdev {
    struct kobject kobj;          // 커널 객체 (내부 관리용)
    struct module *owner;         // 보통 THIS_MODULE 대입
    const struct file_operations *ops; // 장치 호출 시 실행될 함수 모음 (read/write 등)
    struct list_head list;        // 커널 내 문자 장치 리스트 관리용
    dev_t dev;                    // 메이저/마이너 번호 (dev_t 타입)
    unsigned int count;           // 할당받은 마이너 번호의 개수
};
  1. 장치 번호 할당 (dev_t)

    메이저/마이너 번호를 먼저 예약합니다.

    • alloc_chrdev_region(&dev, 0, 1, "my_device"); (동적 할당)
  2. cdev 초기화

    cdev 구조체와 file_operations(함수 포인터 모음)를 연결합니다.

    • cdev_init(&my_cdev, &my_fops);
    • my_cdev.owner = THIS_MODULE;
  3. 커널에 등록

    준비된 장치를 커널 시스템에 활성화합니다.

    • cdev_add(&my_cdev, dev, 1);

Configuration

#make menuconfig : 메뉴 파일 kconfig를 불러들여 메뉴를 표시하는데 사용된다.

해당 이미지처럼 설정을 정할 수 있는 CMD 창으로 접근할 수 있습니다.

3가지 조건 
1. <*> :  built-in  :  vmlinux
2. <M> :  module = > /lib/modules/.../drivers/iio/humidity/dht11.ko
3. < > :  excluded

전체 프로세스

make menuconfig
	<> or <*> or <M> --> .config 
	~~
	ie) CONFIG_DHT11=m 
Makefile 
    # cat drivers/iio/humidity/Makefile | grep dht11
		obj-$(CONFIG_DHT11) += dht11.o
		obj-m  += xxx.o  // ---> xxx.ko   //<M>					
        obj-y  += yyy.o  // ---> vmlinux -> zImage   //<*>							
       	obj-   +=zzz.o   // no compile 사실 만들어 지지 않음  //<>

===== > 각 옵션에 따른 커널 모듈 진행 과정

이처럼 Kconfig 파일은 커널 소스트리 전반에 퍼져 있으며 특정 기능을 커널에 포함할지 말지를 결정하는 옵션들의 구조를 정의한 설계도 입니다.

커널 모듈의 기본 형태

  #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init my_module_init(void)
{
    pr_info("Module loaded! Hello kernel world\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    pr_info("Module unloaded. Goodbye~\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");           // ← 절대 빠지면 안 되는 1순위
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Basic kernel module example");

이처럼 커널 메세지에는 printk를 사용합니다(이때 실수 연산은 불가능 )

printk에서의 log level

Console에서 지정한 log level 보다 높게 설정해 원하는 메세지만 출력할 수 있습니다.

레벨 (숫자)매크로 이름 (String)의미사용 예시
0KERN_EMERGEmergency: 시스템을 사용할 수 없는 상태시스템 충돌(Crash) 직전
1KERN_ALERTAlert: 즉각적인 조치가 필요한 상태하드웨어 데이터 손상 위험
2KERN_CRITCritical: 위험한 상태심각한 하드웨어 에러
3KERN_ERRError: 에러 발생드라이버 로드 실패, 장치 인식 불능
4KERN_WARNINGWarning: 주의 요망비정상적이지만 동작은 가능한 경우
5KERN_NOTICENotice: 정상적이지만 중요한 상태보안 관련 알림, 설정 변경
6KERN_INFOInformational: 일반적인 정보[기본값] 모듈 로드 성공 메시지
7KERN_DEBUGDebugging: 디버깅용 정보개발 중 변수 값 확인용
  • 콘솔 로그 지정

    → 현재 콘솔 로그 레벨 확인

    cat /proc/sys/kernel/printk
    3	4	1	3

    맨 앞의 숫자 3보다 높아야 콘솔에 표시된다. (높다는 게 숫자가 아니라 level , 숫자로는 더 낮음)

    • 지정 하기
      echo 3 >> /proc/sys/kernel/printk
  • 실시간 확인 방법

    dmesg -n 7 # 콘솔에 출력될 로그 레벨 범위를 확장 (7까지 출력)
    dmesg -w   # 실시간 모니터링

모듈 기초

  • modinfo
    # modinfo 02_dev.ko
    filename:       /root/exercise_A05.251102/03/02_dev.ko
    license:        GPL
    srcversion:     F10301C449E9A0AC5976BC5
    depends:        
    name:           02_dev
    vermagic:       6.1.21-v8+ SMP preempt mod_unload modversions aarch64
  • 모듈 매크로
    매크로 이름의미상세 설명필수 여부
    MODULE_LICENSE()라이선스모듈의 배포 라이선스를 지정합니다. (예: "GPL", "GPL v2", "Proprietary")필수
    MODULE_AUTHOR()작성자모듈을 만든 사람의 이름과 이메일 주소를 기입합니다.권장
    MODULE_DESCRIPTION()모듈 설명이 모듈이 어떤 기능을 수행하는지 짧게 요약합니다.권장
    MODULE_VERSION()버전모듈의 버전 번호를 관리합니다. (예: "1.0", "v1.2-alpha")선택
    MODULE_ALIAS()별칭모듈의 별명을 지정하여 modprobe 시 다른 이름으로도 호출 가능하게 합니다.선택

매개 변수 전달

드라이버 로딩 시 상황에 맞는 매개변수를 지정할 수 있다.

모듈 매개변수를 사용하여 가변 값을 지정할 수 있다.

# insmod dev.ko one=0x34 two="Hello!"

전달하기

  • linux/moduleprarm.h 필요
  • module_param() 매크로 사용
  • module_param(name, type, permission)
  • module_param_array() 는 별도 지정

변수 타입

  • int
  • char
  • array 등등

⇒ 배열은 ,를 사용하여 구분

매개 변수 전달 example

// 04_dev.c
#include <linux/moduleparam.h>
#include <linux/stat.h>
 .
 .
 /*TODO:
	module parameter myintArray as int array.

myintArray, int
*/

module_param_array( myintArray, int, &arr_argc, 0000);
MODULE_PARM_DESC(myintArray, "An array of integers");
.
.
  • Result
    # insmod 04_dev.ko myint=4
    root@rpi:~/exercise_A05.251102/03# dmesg
    [609931.438013] myint    is an integer: 4
    [609931.438045] mystring is a  string : blah
    [609931.438058] myintArray[0] = 0
    [609931.438069] myintArray[1] = 0
    [609931.438080] got 0 arguments for myintArray.
    [609931.438090] (mod)init

장치 파일 생성

3가지 방법
1. shell에서 노드 생성

  • 장치 파일은 user process와 device driver를 연결해주는 매개체 역할

    sudo mknod [파일명] [타입] [Major] [Minor]
  1. mknod() 시스템 콜 (User 레벨 호출) -> 반드시 root 권한이 필요
  2. device_create()함수 ( kernel level 호출)
    • 위치: linux/device.h (커널 헤더)
  • 용도: 드라이버가 로드(insmod)되자마자 커널이 알아서 /dev에 파일을 짠! 하고 나타나게 할 때 사용합니다.
  • 함수 형태
    struct device *device_create(struct class *class, struct device *parent,
                                 dev_t devt, void *drvdata, const char *fmt, ...);
    1. 드라이버가 device_create를 호출함
    2. 커널이 유저 공간의 udev(또는 mdev) 데몬에게 "새 장치 생겼다!"라고 신호를 보냄(uevent)
    3. udev 데몬이 설정된 규칙에 따라 /dev 아래에 파일을 자동으로 생성함.

시스템 콜백

응용 프로그램에서 드라이버로 전달되어지는 메커니즘

→ 드라이버의 서비스를 받기 위한 방법

To driver context
어떤 경로로 찾아왔나?
	1. Top(application) --> driver
		process context
		=> system call 에 의해 
	2. Bottom( hardware) --> driver 
		interrupt context
		=> interrupt 에 의해

file operation 구조체 의 구성

// 1. 드라이버 내부 함수 구현
static int my_open(struct inode *inode, struct file *file) {
    pr_info("장치가 열렸습니다!\n");
    return 0;
}

static ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off) {
    pr_info("장치로부터 데이터를 읽습니다.\n");
    return 0; // 읽은 바이트 수 반환
}

static int my_release(struct inode *inode, struct file *file) {
    pr_info("장치가 닫혔습니다!\n");
    return 0;
}

// 2. 구조체 변수 선언 및 함수 매핑 (중요!)
static struct file_operations my_fops = {
    .owner = THIS_MODULE,  // 모듈 참조 카운트 관리용
    .open = my_open,       // 앱의 open() -> my_open() 실행
    .read = my_read,       // 앱의 read() -> my_read() 실행
    .release = my_release, // 앱의 close() -> my_release() 실행
};

.open = my_open 처럼 쓰는 방식 ⇒ Designated Initializer

💡구조체 안의 멤버 순서가 바뀌어도 상관 없고 필요한 것만 골라서 초기화 가능

// To file operations 
====================

int main(){
	int fd = open("/dev/rpihat",O_WONLY);
	int c =  read(fd , buf,10);
	return 0;
}

/*
open(const char* pathname , int flags) 
path name : inode
flags -> flip 
*/

Read / Write

커널 메모리의 할당

  • 커널의 동적 메모리 관리
    • 각 영역에서의 메모리 할당
      • 유저 영역 glibc 의 메모리 루틴에 의해 할당
      • 커널 영역 slab 할당자 (buddy 할당) 에 의한 할당
  • 할당 주체
    • 버디 할당자 : page < 경우에 할당
      • 2n2^n 단위로 할당

        외부 단편화 💡: 총 여유 공간은 충분하지만 연속된 덩어리가 없어서 할당하지 못하는 상태

        → 크기가 다른 메모리를 반복적으로 할당하고 해제하다 보면 하나하나의 크기가 작아 프로세스가 들어갈 수 없게 된다.

        이를 막기 위해 버디 할당자가 재구성

        HOW ❔ : 병합 (Coalescing): 메모리를 해제할 때, 옆에 붙어 있는 같은 크기의 빈 블록(버디)이 있다면 하나로 합쳐서 더 큰 블록으로 만듭니다. 이를 통해 잘게 쪼개진 파편들을 다시 큰 덩어리로 복원

    • 슬랩 할당자 [소매 ] PAGE > 경우의 할당
      • → 더 단위가 작음

      • slab : 하나 이상의 연속된 페이지 프레임 더 작은 단위로 쪼갠 공간 → 이를 할당함

      • kmem-cache에 정의한 각 캐쉬 단위로 할당

        # cat /proc/slabinfo | grep task_
        task_struct          227    248   7872    4    8 : tunables    0    0    0 : slabdata     62     62      0
        task_group           100    100    640   25    4 : tunables    0    0    0 : slabdata      4      4      0

        이렇게 사이즈가 정해진 가판대를 만들어 놓고 장사

        장점

      1. 내부 단편화 방지: 객체 크기에 딱 맞는 공간을 제공
        1. 할당받은 메모리 공간 안에 남는 공간이 생겨 낭비되는 상태
      2. 캐시 성능: 매번 메모리를 새로 할당/해제하지 않고, 미리 만들어진 빈 공간(Object)을 재사용하여 매우 빠름
    • 할당 위험
      • 유저 영역은 물리적 연속 메모리의 할당이 아니므로 실패 위험이 상대적 낮음
      • 커널 영역은 물리적 연속 메모리의 할당이 맞으므로 실패 위험성이 상대적으로 큼

커널에서 동적 메모리 할당

  • kmalloc() : 물리적으로 연속된 공간의 메모리 할당

    kfree()

kernel 버전 마다 upper limit가 다르나 slab.h에서 확인 가능

kmalloc option ⭐

kmalloc(size , flag) 
	- GFP_KERNEL : process context
			app -> driver -> kmalloc --> out of memory? [blocked 됨 ]
	- GFP_ATOMiC : interrupt context
			device --> interrupt -> driver -> kmalloc  --> out of memory? => wait(x) 
																										Hardware는 기다려주지 않음 -> fail 
																										
	둘 중 하나를 자주 쓴다
	
	- GFP_KERNEL :  할당이 여의치 않으면 잠들 수 있다 
							그냥 할당만 , 초기화는 x 
	- GFP_ATOMIC :  메모리 할당이 여의치 않으면 빈 손으로 돌아감 
	
	- GFP_ZERO : 깨끗한 동적 메모리 할당 
  • devm_kzalloc : 메모리 할당 후 자동 해제⭐ ⭐⭐⭐

  • vmalloc() , vfree() : 커다란 동적 메모리 할당

    → 물리적으로 비연속적인 공간의 메모리 할당

  • get_free_pages() , free_pages()

    • 페이지 단위 할당 및 해제 함수 .

인터럽트 처리

인터럽트란? 하드웨어(NVIC)에 연결된 신호

irq에서 controller가 우선 순위가 높은 것들을 먼저 처리한다 .

  1. 프로세스에서 인터럽트 발생 -> 인터럽트 벡터 테이블에서 벡터값 확인

  2. 인터럽트 서비스 루틴 호출

  3. 인터럽트 금지

  4. 프로세스상태 저장

  5. 인터럽트 처리

  6. 프로세서 상태 복구

  7. 인터럽트 허용

  8. 다시 프로세서로 복귀

인터럽트 예제

GPIO Descriptor + Device Tree + devm_ API 를 활용한 스위치 인터럽트 예제

GPIO 인터럽트 등록 플로우

  1. Device Tree에 키 정의 (dts/dtoverlay)
  2. compatible으로 드라이버 매칭
  3. probe()에서 devm_gpiod_get()
  4. gpiod_to_irq()로 IRQ 번호 얻음
  5. devm_request_irq()로 핸들러 등록
// ... (생략)
struct key_gpio_dev { // 구조체 선언
    struct gpio_desc *key_gpio;    // GPIO 디스크립터 (현대적인 GPIO 핸들)
    int irq;                       // 해당 GPIO에 매핑된 IRQ 번호
};

static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    struct key_gpio_dev *key_dev = dev_id;
    int value = gpiod_get_value(key_dev->key_gpio);   // ← 버튼 상태 읽기 (0 or 1)

    pr_info("GPIO KEY: State changed, value = %d\n", value);
    // 여기서 입력 이벤트 발생 시키거나, LED 켜기/끄기 등 추가 작업 가능

    return IRQ_HANDLED;
}

static int key_gpio_probe(struct platform_device *pdev)
{
    // 1. 메모리 할당   ===> 자동 해제 
    key_dev = devm_kzalloc(...);

    // 2. Device Tree에서 GPIO 정보 가져오기 (가장 중요한 부분)
    key_dev->key_gpio = devm_gpiod_get(&pdev->dev, NULL, GPIOD_IN);
    // 내부적으로 Device Tree 파싱:
		// 1. "gpios" 프로퍼티 읽기 → [phandle=7, line=22, flags=1]
		// 2. phandle=7 → &gpio 컨트롤러 획득
		// 3. line=22 → GPIO22 요청
		// 4. flags=1 → ACTIVE_LOW 설정 (gpiod_get_value()=0이 눌린 상태)

    // 3. GPIO → IRQ 번호 변환
    key_dev->irq = gpiod_to_irq(key_dev->key_gpio);

    // 4. 인터럽트 등록 (Falling edge = 버튼 누를 때)
    devm_request_irq(&pdev->dev, key_dev->irq, key_irq_handler,
                     IRQF_TRIGGER_FALLING, "gpio_key_irq", key_dev);
}
static const struct of_device_id key_gpio_of_match[] = {
    { .compatible = "rpi, key_K1 ", },
    {}, // 테이블 종료를 나타내는 sentinel
}; // 디바이스 트리 노드를 나열하는 매칭 테이블 
static struct platform_driver key_gpio_driver = {
    .driver = {
        .name = "gpio_key_driver", //드라이버 이름(for debugging)
        .of_match_table  = key_gpio_of_match, // 위에서 만든 매칭 테이블 
    },
    .probe    = key_gpio_probe, // 장치 발견 -> 호출 되는 함수 
    .remove = key_gpio_remove, // 장치 제거시 호출 
		//.shutdown , .suspend , .resume 등은 생략 가능 
};

Device Tree Overlay code

/dts-v1/;
/plugin/;
    compatible = "brcm,bcm2835";

    fragment@0 {
        target-path = "/";
        __overlay__ {

            key_K1: key_K1 {
                compatible = "rpi,key_K1";
                gpios = <&gpio 22 GPIO_ACTIVE_LOW>;   // ← GPIO22, 눌렀을 때 Low
                //physical 15 -> gpio22 
            };

            key_K2: key_K2 {
                compatible = "rpi,key_K2";
                gpios = <&gpio 23 GPIO_ACTIVE_LOW>;
            };
        };
    };
};
  • compatible = "rpi,key_K1"; → 이 문자열이 커널 드라이버의 of_match_table과 매칭gpio-keys.c (커널 기본 드라이버)
  • gpios = <&gpio 22 GPIO_ACTIVE_LOW>;
    → GPIO22번 핀 사용
    GPIO_ACTIVE_LOW : 버튼 누르면 0V (Low) → 일반적인 풀업+버튼 구성

    RESULT

      #insmod 01_dev.ko
      # dtoverlay 01_dev.dtbo
      # dmesg
      [24591.222865] (dev)GPIO KEY: State changed, value = 0
      [24591.352453] (dev)GPIO KEY: State changed, value = 1
      =========> 스위치가 눌러졌을 때 값이 바뀌는 것을 확인할 수 있다
      # rmmod 01_dev
      root@rpi:~/exercise_A05.251102/22# demesg -c
      bash: demesg: command not found
      root@rpi:~/exercise_A05.251102/22# dmesg -c
      [27090.580446] gpio_key_driver key_K1: GPIO KEY driver removed
      root@rpi:~/exercise_A05.251102/22# dtoverlay -l
      Overlays (in load order):
      0:  01_dev
      ======> 드라이버를 없애도 dtoverlay는 남아있음 
      # dtoverlay -r 01_dev
      root@rpi:~/exercise_A05.251102/22# dtoverlay -l
      No overlays loaded
       ===> 오버레이 삭제 확인 

11주차 자투리 time

일찍 오니 get 한 아몬드 모찌 단팥빵 아 슈웃~~~
쫀득한 팥빵 처음 느껴보는 교육장에서의 이 식감 한번 더 나와다오 ..
옆에는 800여 페이지의 라면 받침대 .. 내용은 알차 보이지만 아직 한번도 펼쳐본적 없는 무자비한 녀석 ( 언제 공부 하냐. . )

2026 내 손으로 한 살 더 먹기 .. 조금 푸짐하게 한살 먹기 .

얘는 2026에도 여전하구나 나의 애착인형 귭해쿤 ...... 제발 프로젝트만은 피하게다오 ... ^^ JOKE

profile
세상의 어려운 문제를 해결하자

0개의 댓글