나의 sht20드라이버는 무엇이 부족했을까, 메인라인 코드와 비교 분석

2026. 1. 2. 18:29·Linux/start_analyse()

라즈베리파이 온습도 제어 시스템을 만들면서

sht20 온습도 센서를 사용했고, 이 과정에서 sht20센서의 디바이스 드라이버를 직접 제작했다.

 

디바이스 트리를 작성하고, 데이터 시트를 보고 명령어를 짜고 꽤나 공부가 되었던 토이 프로젝트 인데,

정작 리눅스 내에 sht21 드라이버가 있는 줄은 몰랐다.

https://github.com/Jminu/Yocto-rasp-BSP/blob/master/meta-mylayer/recipes-kernel/sensor-drivers/files/sht20_driver.c

 

Yocto-rasp-BSP/meta-mylayer/recipes-kernel/sensor-drivers/files/sht20_driver.c at master · Jminu/Yocto-rasp-BSP

build custom linux for RaspberryPi using Yocto. Contribute to Jminu/Yocto-rasp-BSP development by creating an account on GitHub.

github.com

이게 내가 작성한 sht20 드라이버 코드이고, 글에서는 편하게 JMW드라이버라고 부르겠다. (내 이름)

 

https://github.com/torvalds/linux/blob/master/drivers/hwmon/sht21.c

 

linux/drivers/hwmon/sht21.c at master · torvalds/linux

Linux kernel source tree. Contribute to torvalds/linux development by creating an account on GitHub.

github.com

이게 리눅스 메인라인에 들어있는 sht21 드라이버 코드인데, sht20하고 아마 호환이 될 것이다.


probe함수 코드비교

일단 probe 함수부터 비교해보기 전에

probe 함수가 뭔지 간단하게 알아보자.

드라이버 프로브(Driver Probe)는
리눅스 커널에서 특정 하드웨어 장치(Platform Device)를 찾아서 드라이버와 바인딩(연결)하고 초기화하는 핵심 함수
로, 장치가 감지되면 커널이 드라이버 구조체 내의
probe
함수를 호출해 장치 설정, 메모리 할당, 자원 확보 등을 수행하며 장치를 사용 가능하게 만드는 과정입니다.

 

이게 probe 함수의 정의이다.

긍까 드라이버를 처음 만들고, insmod를 하게되는데 그때 실행되는 함수.

 

jmw_sht20의 probe

139 static int sht20_probe(struct i2c_client *client, const struct i2c_device_id *id) {
140         struct sht20_device *sht20;
141         int ret;
142 
143         sht20 = devm_kzalloc(&client->dev, sizeof(struct sht20_device), GFP_KERNEL); // sht20을 위한 kernel공간 할당
144         if (sht20 == NULL) {
145                 printk(KERN_ERR "devm_kzalloc fail\n");
146                 return -1;
147         }
148 
149         sht20->client = client; // 실제 칩을 연결(client)
150 
151         /*
152          * @client: i2c_client구조체안에 dev가 존재, 그 dev안에 driver_data
153          * @sht20: driver_data안에 넣을  데이터
154          */
155         i2c_set_clientdata(client, sht20); // 종료되어도 sht20의 상태를 알 수 있음
156 
157         ret = sht20_soft_reset(client); // soft reset 명령 write
158         if (ret < 0)
159                 return ret;
160 
161         // create char dev, device, class
162         ret = alloc_chrdev_region(&(sht20->dev_num), 0, 1, DEVICE_NAME);
163         if (ret != 0) {
164                 printk(KERN_ERR "alloc chrdev region fail\n");
165                 return -1;
166         }
167 
168         cdev_init(&(sht20->sht20_cdev), &fops);
169         ret = cdev_add(&(sht20->sht20_cdev), sht20->dev_num, DEVICE_COUNT);
170         if (ret < 0) {
171                 printk(KERN_ERR "cdev add fail\n");
172                 return -1;
173         }
174 
175         sht20->class = class_create(THIS_MODULE, CLASS_NAME);
176         device_create(sht20->class, NULL, sht20->dev_num, NULL, DEVICE_NAME);
177 
178         return 0;
179 }
  • devm_kzalloc(&client->dev, sizeof(struct sht20_device), GFP_KERNEL): 디바이스 위한 커널 공간 할당.
  • i2c_set_clientdata(client, sht20): client->data에 sht20포인터를 넣는다.
    • https://elixir.bootlin.com/linux/v6.13.2/source/include/linux/device.h#L943 를 확인해보자. 정확히 어떤 의미인지 모르겠음.
  • ret = sht20_soft_reset(client): soft reset 명령을 내림.
    • #define SOFT_RESET 0xFE로 정의했음. This command (see Table 6) is used for rebooting the
      sensor system without switching the power off and on again. 데이터 시트에 soft reset의 정의.
  • alloc_chrdev_region(&(sht20->dev_num), 0, 1, DEVICE_NAME): 디바이스 넘버 할당받음 by Kernel
  • cdev_init(&(sht20->sht20_cdev), &fops): initialize cdev structure, 만들었던 fops등록
  • ret = cdev_add(&(sht20->sht20_cdev), &fops): 문자 디바이스를 시스템에 등록 -> 특정 디바이스 번호로 들어오는 신호는 방금 등록했던 fops를 통해서 처리한다
  • sht20->class = class_create(THIS_MODULE, CLASS_NAME): create a struct class structure
  • device_create(sht20->class, NULL, sht20->dev_num, NULL, DEVICE_NAME): sysfs에 등록, /dev 에 장치파일을 생성한다.

sht20_soft_reset

 45 static int sht20_soft_reset(struct i2c_client *client) {
 46         int ret = i2c_smbus_write_byte(client, SOFT_RESET); // write SOFT_RESET command to SHT20
 47         if (ret < 0) {
 48                 printk(KERN_ERR "i2c smbus write fail\n");
 49                 return -1;
 50         }
 51         msleep(100);
 52 
 53         return ret;
 54 }
  • i2c_smbus_write_byte(client, SOFT_RESET): https://elixir.bootlin.com/linux/v6.13.2/source/drivers/i2c/i2c-core-smbus.c#L114 를 보면 smbus write는 'send byte' 프로토콜임.

 

linux_sht21의 probe

248 ATTRIBUTE_GROUPS(sht21);
249 
250 static int sht21_probe(struct i2c_client *client)
251 {
252         struct device *dev = &client->dev;
253         struct device *hwmon_dev;
254         struct sht21 *sht21;
255 
256         if (!i2c_check_functionality(client->adapter,
257                                      I2C_FUNC_SMBUS_WORD_DATA)) {
258                 dev_err(&client->dev,
259                         "adapter does not support SMBus word transactions\n");
260                 return -ENODEV;
261         }
262 
263         sht21 = devm_kzalloc(dev, sizeof(*sht21), GFP_KERNEL);
264         if (!sht21)
265                 return -ENOMEM;
266 
267         sht21->client = client;
268 
269         mutex_init(&sht21->lock);
270 
271         hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
272                                                            sht21, sht21_groups);
273         return PTR_ERR_OR_ZERO(hwmon_dev);
274 }
  • i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA): 어댑터 사용가능한지 판단
    • https://elixir.bootlin.com/linux/v6.13.2/source/include/linux/i2c.h#L922
  • devm_kzalloc(dev, sizeof(*sht21), GFP_KERNEL): 디바이스 공간 할당
    • devm_kmalloc: 디바이스를 위한 메모리 할당. devm_kzalloc은 할당해준다음 0으로 초기화함
    • https://elixir.bootlin.com/linux/v6.13.2/source/drivers/base/devres.c#L822
  • sht21->client = client: 전달받은 i2c_client를 sht21->client에 바인딩
    • https://elixir.bootlin.com/linux/v6.13.2/source/include/linux/i2c.h#L235 
  • 그리고 리눅스 메인라인 코드에서는 mutex_init이랑 devm_hwmon_device_register_with_groups를 사용함 (jmw_sht20에서도 뮤텍스를 쓰는걸 고려하는게 좋을 듯)

온도/습도 읽어오는 방식

jmw_sht20

에서는 read_data -> sht20_read_data를 통해서 읽어온다.

89 static ssize_t sht20_read(struct file *file, char __user *buf, size_t len,     loff_t *pos) {
 90         struct sht20_device *sht20 = file->private_data;
 91         int temp_raw;
 92         int humid_raw;
 93         char kbuf[64];
 94 
 95         printk(KERN_INFO "sht20_driver.c: sht20 read\n");
 96 
 97         if (*pos > 0) {
 98                 printk(KERN_ERR "pos err\n");
 99                 return -1;
100         }
101 
102         int ret;
103 
104         ret = sht20_read_data(sht20->client, TEMP_MEASUREMENT, &temp_raw);     // 0x40 chip address를 대상으로 온도 측정 명령
105         if (ret < 0) {
106                 printk(KERN_ERR "Temp measurement fail\n");
107                 return -1;
108         }
109 
110         ret = sht20_read_data(sht20->client, HUMID_MEASUREMENT, &humid_raw)    ; // 0x40 chip address를 대상으로 습고 측정 명령
111         if (ret < 0) {
112                 printk(KERN_ERR "Humid measurement fail\n");
113                 return -1;
114         }
115 
116         len = snprintf(kbuf, sizeof(kbuf), "%d|%d", temp_raw, humid_raw);
117         // printk(KERN_INFO "%s\n", kbuf);
118 
119         copy_to_user(buf, kbuf, len);
120 
121         return len;
122 }
  • struct sht20_device *sht20 = file->private_data: private_data는 file system or driver specific data라고 한다.
  • ret = sht20_read_data(sht20->client, TEMP_MEASUREMENT, &temp_raw): 온도데이터 읽어옴. sht20_read_data는 좀있다 다루겠음
  • len = snprintf(kbuf, sizeof(buf), %d|%d", temp_raw, humid_raw): 문자열로 만들어서
  • copy_to_user(buf, kbuf, len): 유저 공간으로 문자열을 보냄

sht20_read의 초기부분에 struct sht20_device *sht20 = file->private_data를 해서 devm_kzalloc으로 커널이 할당해주었던 공간의 주소를 반환받는다.

file->private_data는 어디서?

124 static int sht20_open(struct inode *inode, struct file *file) {
125         struct sht20_device *sht20;
126         sht20 = container_of(inode->i_cdev, struct sht20_device, sht20_cdev);
127         file->private_data = sht20; // 센서 데이터에 접근가능 ex)temp
128 
129         return 0;
130 }
  • sht20 = container_of(inode->i_cdev, struct sht20_device, sht20_cdev): 커널이 할당해주었던 공간의 주소를 반환
  • file->private_data: 여기서 그 주소가 들어있음. 따라서 위의 sht20_read에서 이방식으로 구조체의 주소를 가져옴
  • container_of와 file->private_data에서의 데이터 전달 중요

실제로 온도/습도를 읽어오는 곳

sht20_read_data

 63 static int sht20_read_data(struct i2c_client *client, int command, int *val) {
 64         int ret;
 65         u8 buf[3]; // 데이터 받을 unsigned char 3byte
 66     
 67         ret = i2c_smbus_write_byte(client, command); // write command to sht20
 68         if (ret < 0) {
 69                 printk(KERN_ERR "i2c_smbus_write_byte Fail\n");
 70                 return -1;
 71         }
 72 
 73         msleep(100);
 74 
 75         ret = i2c_master_recv(client, buf, 3); // SHT20으로부터 word만큼 데이터 읽음(3byte)
 76         if (ret < 0) {
 77                 printk(KERN_ERR "i2c_master_recv Fail\n");
 78                 return -1;
 79         }
 80     
 81         *val = (buf[0] << 8) | (buf[1] & 0xFC); // buf[1]에서 하위 2비트는 stat비트이기 때문에 무시
 82 
 83         return 0;
 84 }
  • ret = i2c_smbus_write_byte(client, command): client즉 sht20에 command 여기서는 TEMP_MEASUREMENT 명령을 내린다. 명령내리고 msleep(100)으로 기다림.
    • 여기서 client는 sht20이 되겠고, i2c주소 + write = 1000 0000 이다. (LSB가 1이면 read, 0이면 write)
  • ret = i2c_master_recv(client, buf, 3): sht20으로부터 word만큼 데이터를 읽는다. (3byte) 왜 3바이트를 읽는가?
    • 왜냐면 no hold master mode에서 read를 했을 때, 3바이트가 필요함. 근데 여기서 Data (LSB)의 마지막 2비트는 상태비트니까 제외시키자.

3바이트

linux_sht21

리눅스에 있는 메인라인 코드에서는

sht21_temperature_show에서 sht21_update_measurement를 호출하면서 온도를 읽어온다.

 

sht21_temperature_show

125 static ssize_t sht21_temperature_show(struct device *dev,
126                                       struct device_attribute *attr,
127                                       char *buf)
128 {
129         struct sht21 *sht21 = dev_get_drvdata(dev);
130         int ret;
131 
132         ret = sht21_update_measurements(dev);
133         if (ret < 0)
134                 return ret;
135         return sprintf(buf, "%d\n", sht21->temperature);
136 }
  • struct sht21 *sht21 = dev_get_drvdata(dev): 아마 커널내에 디바이스를 위해 할당된 공간을 가리키는 포인터로 추정
  • ret = sht21_update_measurements(dev) 호출

sht21_update_measurements

 84 static int sht21_update_measurements(struct device *dev)
 85 {
 86         int ret = 0;
 87         struct sht21 *sht21 = dev_get_drvdata(dev);
 88         struct i2c_client *client = sht21->client;
 89 
 90         mutex_lock(&sht21->lock);
 91         /*
 92          * Data sheet 2.4:
 93          * SHT2x should not be active for more than 10% of the time - e.g.
 94          * maximum two measurements per second at 12bit accuracy shall be made.
 95          */ 
 96         if (time_after(jiffies, sht21->last_update + HZ / 2) || !sht21->valid) {
 97                 ret = i2c_smbus_read_word_swapped(client,
 98                                                   SHT21_TRIG_T_MEASUREMENT_HM);
 99                 if (ret < 0)
100                         goto out;
101                 sht21->temperature = sht21_temp_ticks_to_millicelsius(ret);
102                 ret = i2c_smbus_read_word_swapped(client,
103                                                   SHT21_TRIG_RH_MEASUREMENT_HM);
104                 if (ret < 0)
105                         goto out;
106                 sht21->humidity = sht21_rh_ticks_to_per_cent_mille(ret);
107                 sht21->last_update = jiffies;
108                 sht21->valid = true;
109         }
110 out:
111         mutex_unlock(&sht21->lock);
112 
113         return ret >= 0 ? 0 : ret;
114 }
  • mutex_lock(&sht21->lock): 데이터 읽기전에 lock 걸어놓음
  • sht21->temperature = sht21_temp_ticks_to_millicelsius(ret): 온도 변환해주는 공식으로 추측
  • ret = i2c_smbus_read_word_swapped(client, SHT21_TRIG_T_MEASUREMENT_HM)
    • 내부적으로 i2c_smbus_read_word_data 호출: 즉, word만큼 데이터 읽어옴.
  • sht21->humidity = sht21_rh_ticks_to_millicelsius(ret): 습도 변환해주는 공식으로 추측 
  • mutex_unlock(&sht21->lock): 다 읽었으니 뮤텍스 락 해제

전체적으로 다른 점

리눅스 메인라인에 있는 sht21드라이버는

  • 뮤텍스 사용
  • hwmon 프레임 워크사용
  • devm계열의 함수를 사용하여 좀 더 자동화되고 깔끔한 코드
  • jiffies사용 -> 왜 사용했을까?

'Linux > start_analyse()' 카테고리의 다른 글

System Call 분석  (0) 2026.04.13
/drivers/char/mem.c 드라이버 2차 분석  (0) 2026.01.01
'Linux/start_analyse()' 카테고리의 다른 글
  • System Call 분석
  • /drivers/char/mem.c 드라이버 2차 분석
Minu Jin
Minu Jin
정보의 바다
  • Minu Jin
    뇌 구조가 바이너리
    Minu Jin
  • 전체
    오늘
    어제
    • 분류 전체보기
      • C프로그래밍
        • 오류해결
        • 개인 공부
        • Programming Lab(학교수업)
        • MemoryTracker
      • C++
        • 개인 공부
      • 자료구조(Data Structure)
      • ARM arch
        • Cortex-M
        • FreeRTOS
      • 컴퓨터 공학(Computer Science)
        • OS
        • 컴퓨터 구조
      • Qualcomm 기업과제
      • Linux
        • start_contribute()
        • start_analyse()
      • Web
      • 똥글
      • 백준
      • Git 학습
        • 오류해결
        • 학습중
      • Python
        • 오류해결
        • 개인 공부
  • 블로그 메뉴

    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    commit
    커널
    rubikpi3
    스택
    c언어
    이진 트리
    순환
    시스템콜
    포인터
    파일 입출력
    INIT
    드라이버 분석
    Git
    arm
    동적메모리
    토발즈
    파이썬
    rubik pi
    자료구조
    버퍼
    커널 기여
    Branch
    백준
    Qualcomm
    C++
    yolo
    피보나치
    앤드류모튼
    리눅스
    소수
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Minu Jin
나의 sht20드라이버는 무엇이 부족했을까, 메인라인 코드와 비교 분석
상단으로

티스토리툴바