unkown symbol

insmod 실행 시 unknown symbol 에러

가끔 모듈을 컴파일 하여 insmod로 추가할 때 unknown symbol 에러로 추가가 안 될 경우가 있다. xpad.c를 따라할 때 해당 함수를 찾아 지우고 했지만, 이번 sensehat을 사용하려는 기본 함수는 지울 수 없어 이유를 찾아야 했다.

[ 8890.395611] sensehat_core: loading out-of-tree module taints kernel.
[ 8890.395805] sensehat_core: Unknown symbol __devm_regmap_init_i2c (err -2)
[ 8903.630685] sensehat_core: Unknown symbol __devm_regmap_init_i2c (err -2)
[ 9094.432329] sensehat_core: Unknown symbol __devm_regmap_init_i2c (err -2)
[ 9232.390045] sensehat_core: Unknown symbol __devm_regmap_init_i2c (err -2)

결과적으로 devm_regmap_init_i2c를 사용하려면 먼저 regmap_i2c를 로딩해야 하는데, 이 작업을 하지 않아 그렇다. 부팅한 버전과 일치하는regmap-i2c.ko를 찾아 insmod regmap-i2c.ko로 먼저 로딩해야 한다.

어떤 모듈이 미리 필요한지 알기 위해서는 해당 모듈을 lib/modules/”부팅버전”에 복사하고 depmod -a로 의존성 확인을 먼저 해야 한다. modprobe로 내가 원하는 모듈을 로딩하면 의존성을 확인하여 같이 로딩한다.

매번 수정되는 드라이버를 modprobe로 로딩하기 귀찮으니, 해당 파일이 부팅 때 자동으로 로딩 되도록 수정한다.

pi@raspberrypi:/etc/modules-load.d $ pwd
/etc/modules-load.d
pi@raspberrypi:/etc/modules-load.d $ cat modules.conf 
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.

i2c-dev
#sensehat_core를 사용하기 위한 모듈 선 로딩
regmap_i2c

이제 sensehat을 가지고 놀 준비가 되었다.

linux memory pools

memory pools

cache memory에 이어 memory pools를 실습했다. 역시 책만 보면 잘 모른다. nvme 드라이버- drivers/nvme/target/io-cmd-file.c – 중 mempool 관련 코드를 찾을 수 있다.

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

#define scullc_quantum 16
/* declare one cache pointer: use it for all devices */
/* kmem_cache_t 가 kmem_cache로 변경됨*/
struct kmem_cache *scullc_cache;
static const char test[scullc_quantum]="123456789abcde";
static char* data;
mempool_t *mem_pool1;
static int __init hello_world_init(void) /* Constructor */
{
	int i;
	printk(KERN_INFO "hello, pool memory\n");
	/* memory 할당*/
	scullc_cache = kmem_cache_create("scullc", scullc_quantum*sizeof(char),
			0, SLAB_HWCACHE_ALIGN, NULL); /* no ctor */

	if (!scullc_cache) {
		printk(KERN_ERR "no memory. out.\n");
		return -ENOMEM;

	}
	
	/* memory pool로 allocate*/
	mem_pool1 = mempool_create(16, mempool_alloc_slab, mempool_free_slab,
			scullc_cache);

	//data = kmem_cache_alloc(scullc_cache, GFP_KERNEL);

	data = mempool_alloc(mem_pool1, GFP_KERNEL);
	for (i=0;i<scullc_quantum;i++){
		data[i] = test[i];
	}

	printk(KERN_INFO "cache: pos: %p, size: %d\n",scullc_cache, sizeof(scullc_cache));
	printk(KERN_INFO "data: pos:%p, size: %d, %s\n",data, sizeof(data), data);
	return 0;
}

void __exit hello_world_exit(void)
{
	//kmem_cache_free(scullc_cache, data);
	mempool_free(data, mem_pool1);
	mempool_destroy(mem_pool1);
	kmem_cache_destroy(scullc_cache);
	printk(KERN_INFO "good bye, pool memory\n");

}
module_init(hello_world_init);
module_exit(hello_world_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("now0930");
MODULE_DESCRIPTION("cache memory test");
pi@raspberrypi:~/rasp/advancedMemory/mempool $ make
#make -C /lib/modules/5.10.63-v7l+/build V=1 M=/home/pi/rasp/advancedMemory/mempool modules
make -C /lib/modules/`uname -r`/build M=`pwd` modules
make[1]: 디렉터리 '/usr/src/linux-headers-5.10.63-v7l+' 들어감
make[1]: 디렉터리 '/usr/src/linux-headers-5.10.63-v7l+' 나감
pi@raspberrypi:~/rasp/advancedMemory/mempool $ sudo insmod memory_pool.ko
pi@raspberrypi:~/rasp/advancedMemory/mempool $ sudo rmmod memory_pool 
pi@raspberrypi:~/rasp/advancedMemory/mempool $ dmesg | tail -10
[52590.309504] hello, pool memory
[52590.309579] cache: pos: 0c17faff, size: 4
[52590.309598] data: pos:f97bc0b2, size: 4, 123456789abcde
[52601.784069] good bye, pool memory

linux cache memory

cache memroy 실습

linux device driver, 8장을 실습 했다. 컴파일 하여 실행하기 전에는 어떻게 돌아가는지 잘 알 수 없다. scullc을 보고 따라하려 했으나 잘 모르겠다. 메모리를 cache로 선언하고 일반 메모리를 쓰듯이 하면 된다.

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

#define scullc_quantum 16

/* declare one cache pointer: use it for all devices */
/* kmem_cache_t 가 kmem_cache로 변경됨*/
struct kmem_cache *scullc_cache;

static const char test[scullc_quantum]="123456789abcde";
static char* data;
static int __init hello_world_init(void) /* Constructor */
{
	int i;
	printk(KERN_INFO "hello, cache memory\n");
	/* memory 할당*/
	scullc_cache = kmem_cache_create("scullc", scullc_quantum*sizeof(char),
			0, SLAB_HWCACHE_ALIGN, NULL); /* no ctor */

	if (!scullc_cache) {
		printk(KERN_ERR "no memory. out.\n");
		return -ENOMEM;

	}
	/* allocate*/
	data = kmem_cache_alloc(scullc_cache, GFP_KERNEL);
	for (i=0;i<scullc_quantum;i++){
		data[i] = test[i];
	}
	printk(KERN_INFO "cache: pos: %p, size: %d\n",scullc_cache, sizeof(scullc_cache));
	printk(KERN_INFO "data: pos:%p, size: %d, %s\n",data, sizeof(data), data);
	return 0;
}
void __exit hello_world_exit(void)
{
	kmem_cache_free(scullc_cache, data);
	kmem_cache_destroy(scullc_cache);
	printk(KERN_INFO "good bye, cache memory\n");

}
module_init(hello_world_init);
module_exit(hello_world_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("now0930");
MODULE_DESCRIPTION("cache memory test");

cache 메모리를 얼마나 사용하고 있는지 알아보는 유틸리티가 있을 것 같은데 못 찾았다.

참조 사이트

linux usb driver xbox360(5)

드디어 마지막이다. xbox 패드를 마우스처럼 사용하고 싶어 이렇게 동작하도록 수정했다. 처음 연결하면 조이스틱으로 동작하고, 가운데 버튼을 누르면 마우스로 동작하도록 했다. 리눅스에서 조이스틱 쓸 일이 있나?? 초기값을 마우스로 바꿨어야 했다.

input_device에 name을 설정하지 않고 등록하면 xserver로 이벤트를 전달할 수 없다. input_device에 name을 설정하지 않으면 evtest에 이름이 표시되지 않는다.

xbox 스틱으로 들어오는 값을 그대로 사용하면 값이 너무 커서 커서가 화면 끝에서 끝까지 점프한다. 대략 2,000으로 나눴다. 바꿔 말하면 큰 값을 짧은 시간에 한번에 전달하기 위해 조이스틱을 사용한다.

버튼을 누르면 rising edge, falling edge 두 번 동작한다. 비트 시프트로 특정 값을 만족하면 모드가 바뀌도록 했다. 인터럽트 등록할 때 rising, falling edge 필터링 할 수 있는데, urb로 등록하면 그런 게 없는 듯 하다. 짱구를 많이 굴렸다.

xpad.c 드라이버를 따라 한 수준이지만 커스터 마이징을 해 보면서 무엇을 모르고 무엇을 학습했는지 알 수 있었다. 책만 보고서는 절대 이해하지 못했을 지식이다. 사용되는 함수들은 소스 코드를 보지 않는 한 절대 사용하지 못할 함수 들이다. 이미 내가 고민했던 내용을 누군가 해결하여 코드로 구현했다. 소스코드 검색을 자주 해야 시간을 줄일 수 있다.

xpad.c에 보면 사용할 struct에 관심있는 주소를 모두 때려 넣었다. 구조가 복잡하고, 주소가 너무 많아 북마크처럼 주소를 관리해야 한다. 커널에서 내려오면 항상 메모리를 해제할 수 있도록 했다. 보면 볼수록 container_of 매크로로 위력을 실감한다. 부분을 알면 구조체 멤버에 무제한으로 접근할 수 있다. 와!! 이거 생각해낸 사람이 정말 대단하다.

나도 이제 usb, character device에서 하산해도 될 듯 하다.

https://github.com/now0930/xbox_driver

참조 자료

linux usb driver xbox360(4)

input 테스트

usb로 들어오는 버튼 입력을 테스트 했다. LED를 켜는 출력과 비슷하게 진행된다. 역시 출력을 보는 작업이 제일 어려웠다.

  • 입력 디바이스 할당: input_allocate_device();
  • urb 할당
    • usb_alloc_coherent
    • usb_alloc_urb
  • 인터럽트로 urb 설정: usb_rcvintpipe, usb_fill_int_urb
  • input device 주소 변경?: input_set_drvdata
  • open, close 함수 오버로드
    • input_dev->open = xpad_open
    • input_dev->close = xpad_close
  • usb_submit_urb call back 함수 설정

input_dev->close에 usb_kill_urb로 urb를 해제하지 않으면 다시 입력 디이스를 열었을 때 사용할 수 없다.

xpad.c 드라이버는 인터럽트 실행될 때 완료 함수를 등록했다. 그 함수 안에있는 work queue가 input_report_key, input_sync로 입력 키를 이벤트로 OS에 전달한다.

버튼이 눌렸을 경우 이벤트가 0, 1 값으로 전달되어야 한다. urb로 들어오는 data 패킷 중 2번, 3번 주소에 mask를 씌워 EV_KEY 값 상태를 확인한다.

  • 인터럽트 발생 확인.
  • urb data 값 수신.
  • 해당하는 주소에 mask를 씌워 특정 이벤트를 OS에 전달.

리눅스 시스템 내에서 사용할 수 있는 이벤트가 이미 정해져 있어 사용자 맘대로 설정할 수 없다. 그 말은 조이스틱 이동을 마우스 이동 키로 할당하면 마우스 대신 조이스틱을 사용할 수 있단 말이다. 조이스틱을 눌렀을 때 어느 데이터가 켜지는지는 기기마다 다르기 때문에 몇 번 테스트를 해야 한다.

data 패킷 중 버튼 값 위치
static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev,
                                   u16 cmd, unsigned char *data)
{
        if (data[0] != 0x00)
                return;
        /* buttons A,B,X,Y,TL,TR and MODE */
        input_report_key(dev, BTN_A,            data[3] & 0x10);
        input_report_key(dev, BTN_B,            data[3] & 0x20);
        input_sync(dev);
}

아날로그 버튼이라면 얼마나 눌렸는지 값을 전달할 수 있다. 콘솔에서 실행 가능한 evtest로 입력값을 확인할 수 있다.

xpad.c를 직접 해 보면서 다음을 학습했다. The Linux Kernel 에는 너무 간단한 예가 있다. 내가 무엇을 알고 있고, 모르는 지를 정확하게 확인되지 않고는 위 문서를 제대로 이해할 수 없다. 적절한 삽질로 정확하게 이해할 수 있다.

  • usb 등록, 해제
  • urb packet 사용 방법
  • input device 등록, 해제
  • input device 사용

참조 사이트