[Ubuntu] Debug card (80 port) driver sample code

要編譯要先安裝 build-essential 與 libelf-dev 套件, build-essential 包含了編譯 C / C++ 所需的套件,lebelf-dev 則包含編譯 driver (kernel module) 所需的程式。

$ sudo apt-get update
$ sudo apt-get install build-essential
$ sudo apt-get install libelf-dev
// debug_card.h
#ifndef _DEBUG_CARD_H_
#define MSG(format, arg...) printk(KERN_INFO "DEBUG CARD: " format "\n", ## arg)
#include <linux/ioctl.h>
#define DEV_MAJOR 121
#define DEV_NAME "debug"
#define DEV_IOCTLID 0xD0
#define IOCTL_WRITE _IOW(DEV_IOCTLID, 10, int)
#define IOCTL_RESET _IOW(DEV_IOCTLID, 0, int)
#endif

//debug_card.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
//#include <asm/uaccess.h>
#include <linux/uaccess.h>

#include "debug_card.h"

unsigned long IOPort = 0x80;

#define DRIVER_NAME "debug_card"
static unsigned int chrdev_sys_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev debug_card_cdev;
static struct class *chrdev_sys_class = NULL;


void write_debug_card(unsigned int num) {
	MSG("write 0x%02X (%d) to debug card", (unsigned char)num, num);
	outb((unsigned char)num, IOPort);
}

static long debug_card_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
	switch (cmd) {
	case IOCTL_RESET:
		write_debug_card(0x00);
		break;

	default:
		return -1;
	}
	return 0;
}


ssize_t debug_card_write(struct file *filp, const char *buff,
						 size_t count, loff_t *offp) {
	char *str;
	unsigned int num;
	int i;

	if (count == 0) return 0;
	filp->private_data = (char *)kmalloc(64, GFP_KERNEL);
	str = filp->private_data;

	if (copy_from_user(str, buff, count)) return -EFAULT;

	/* atoi() */
	num = str[0] - '0'; for (i = 1; i < count; i++) {
		num = num * 10 + (str[i] - '0');
	}

	write_debug_card(num);

	return 1;
};


/**************************************************/

int debug_card_release(struct inode *inode, struct file *filp) {
	module_put(THIS_MODULE);
	kfree(filp->private_data);
	return 0;
};


int debug_card_open(struct inode *inode, struct file *filp) {
	try_module_get(THIS_MODULE);
	return 0;
};

struct file_operations card_fops = {
	.owner = THIS_MODULE,
	.open = debug_card_open,
	.write = debug_card_write,
	.release = debug_card_release,
	.unlocked_ioctl = debug_card_ioctl,
};

static char *devnode(struct device *dev, umode_t *mode)
{
        if (!mode)
                return NULL;
        if (dev->devt == MKDEV(chrdev_sys_major, 0) ||
            dev->devt == MKDEV(chrdev_sys_major, 2))
                *mode = 0666;
        return NULL;
}

static int card_init_module(void)
{
	dev_t dev = MKDEV(chrdev_sys_major, 0);
	int alloc_ret = 0;
	int cdev_ret = 0;

	alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
	if (alloc_ret) goto error;

	chrdev_sys_major = MAJOR(dev);
	cdev_init(&debug_card_cdev, &card_fops);
	cdev_ret = cdev_add(&debug_card_cdev, dev, num_of_dev);
	if (cdev_ret) goto error;

	chrdev_sys_class = class_create(THIS_MODULE ,"debug_card_80port");		//Name to be displayed inside /sys/class/ when driver is loaded.
	if (IS_ERR(chrdev_sys_class)) goto error;

	chrdev_sys_class->devnode = devnode;									//Set device file (/dev/debug_card_80port) permission to 0666 on creation

	device_create(chrdev_sys_class, NULL, dev, NULL, "debug_card_80port");	//device file to be created under /dev.

	printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, chrdev_sys_major);
	return 0;

error:
	if (cdev_ret == 0) cdev_del(&debug_card_cdev);
	if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev);
	return -1;
}


static void card_exit_module(void) {
	dev_t dev = MKDEV(chrdev_sys_major, 0);

	device_destroy(chrdev_sys_class, dev);
	class_destroy(chrdev_sys_class);

	cdev_del(&debug_card_cdev);
	unregister_chrdev_region(dev, num_of_dev);
	printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}


module_init(card_init_module);
module_exit(card_exit_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("RayKuo's blog");
MODULE_INFO(intree, "Y");//fix err: "giodrv: loading out-of-tree module taints kernel."

# Makefile
KVERSION := $(shell uname -r)

obj-m := debug_card.o

all:
	$(MAKE) -C /lib/modules/$(KVERSION)/build M=${PWD} modules

clean:
	$(MAKE) -C /lib/modules/$(KVERSION)/build M=${PWD} clean

 

測試程式 sample code:

// test.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../drv/debug_card.h"

int main(int argc, char *argv[]) 
{

 int devfd;
 unsigned int num = 0;

 if (argc == 1) argv[1] = "0";

 devfd = open("/dev/debug_card_80port", O_RDWR);
 if (devfd == -1) {
 printf("Can't open /dev/debug_card_80port\n");
 return -1;
 }

 printf("Resetting debug card...\n");
 ioctl(devfd, IOCTL_RESET, NULL);
 printf("Done. Wait 1 second...\n");
 sleep(1);

 printf("Writing %s...\n", argv[1]);
 write(devfd, argv[1], strlen(argv[1]));
 printf("Done.\n");

 close(devfd);

 return 0;
}

# Makefile

all:
	gcc test.c -o test

clean:
	rm test

 

編譯與測試:

切到 debug_card driver 目錄,使用 make 指令 compiler:

cd debug_card/drv
make

沒有錯誤的話可以得到 debug_card.ko (driver)。

使用 insmod 安裝 driver :

$ sudo insmod debug_card.ko

看到 kernel 分配給 driver 的 major number。

# 查看 kernel 分配給 drive 的 major number
$ cat /proc/devices|grep debug_card    
243 debug_card

# list 已載入的 driver
$ lsmod|grep debug_card   
debug_card 16384 0

# driver 載入後建立的 class 
$ ls -dl /sys/class/debug*
drwxr-xr-x 2 root root 0 10月 1 16:09 /sys/class/debug_card_80port

# driver 載入後建立的 device node (不需使用 mknod 來手動建立),用來與 user mode 程式溝通 
$ ls -d /dev/debug*
crw-rw-rw- 1 root root 243, 0 10月 1 16:09 /dev/debug_card_80port

到此為止,driver 與 device 已完成建置,test 測試程式將通過讀寫 device /dev/debug_card_80port (檔案)與 driver 溝通。

再來切到 test 測試程式目錄,使用 make 來 compiler 測試程式。

cd ../test
make

執行 test 即可看到 debug card (80 port) 的變化。

# 往 80 port 丟 12h
./test 12 

 

移除已安裝的 driver:

cd ../drv
sudo rmmod debug_card.ko

 

Download source code : debug_card_src

 

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *