Linux_kernel字符设备驱动12

一、字符设备的编程框架

在Linux_kernel驱动开发11中,我们介绍的系统调用。只是为了做一个实验,在真正开发时,我们并不会直接在内核中添加一个新的系统调用,这样做会导致内核体积变大。

1、字符设备结构体

我们实现一个硬件字符设备的驱动程序,实际上是实例化一个struct cdev类型的对象。

【1】struct cdev; // 存储字符设备的相关信息

注意:

在该结构体中,我们只需关注这两个成员,其他的成员由内核自己维护

2**】dev_t dev; // 指示当前的设备号**

【3】const struct file_operations *ops; // 操作函数的集合

1)设备号
【1】区分主次设备号

设备号(32bit) = 主设备号(12bit [msb])+ 次设备号(20bit [lsb])

1】示例

ls -l /dev/tty0

主次设备号的范围理论上都是**[0, 255]**

主设备号:区分不同类型的设备

次设备号:区分同一类型设备的不同个体

MINORBITS:次设备号的位数

MINORMASK:次设备号掩码

MAJOR(dev):得到主设备号

MINOR(dev):得到次设备号

MKDEV(ma,mi):将主设备号与次设备号合为一个32bit整型数(dev_t

【2】静态注册设备号

就是自己先挑一个没有被内核占用的设备号去注册

0】查看被内核占用的设备号

cat /proc/devices

1】register_chrdev_region(注册设备号)

注释:

from:要注册的起始设备号

count:要连续注册的设备号个数

name:给设备起的名称

2】unregister_chrdev_region(注销设备号)

注释:

from:要注销的起始设备号

count:要连续注销的设备号个数

【3】静态注册实验

1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

2】创建新的工程

mkdir chrdev

3】编写程序

vim chrdev.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	10
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"leds"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int dev = 0;	// store the major dev number and the minor dev number

int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// dev = major << 20 | minor;
	// there is a define func to do this task
	dev = MKDEV(major, minor);

	register_chrdev_region(dev, CHRDEV_NUM, CHRDEV_NAME);	//register the number of device

	return 0;
}

void __exit chrdev_exit(void)
{
	unregister_chrdev_region(dev, CHRDEV_NUM);

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);

4】编写Makefile

vim Makefile

bash 复制代码
obj-m += chrdev.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

5】编译工程

make

6】下位机验证

注意:我们现在只有设备号,而没有设备文件

【4】动态注册设备号

内核自己找一个没有注册的设备号,注册完归程序员使用

1】alloc_chrdev_region(注册设备号)

注释:

dev:回填设备号

baseminor:次设备号的基值(起始值)

count:要连续注册的设备号个数

name:给设备起的名称

2】unregister_chrdev_region(注销设备号)

注释:

from:要注销的起始设备号

count:要连续注销的设备号个数

【5】动态注册实验

1】编写程序

vim chrdev.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	10
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"leds"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int dev = 0;	// store the major dev number and the minor dev number
#if 0
// fixed register
int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// dev = major << 20 | minor;
	// there is a define func to do this task
	dev = MKDEV(major, minor);

	register_chrdev_region(dev, CHRDEV_NUM, CHRDEV_NAME);	//register the number of device

	return 0;
}
#else
// variable register
int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// there is a define func to register the number of devices automatically
	alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME);
	major = MAJOR(dev);	// gain the major dev number
	minor = MINOR(dev);	// gain the minor dev number
	
	printk(KERN_EMERG "dev number is :%d\n major number is :%d\n minor number is :%d\n", dev, major, minor);

	return 0;
}
#endif

void __exit chrdev_exit(void)
{
	unregister_chrdev_region(dev, CHRDEV_NUM);

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);

2】编写Makefile

vim Makefile

bash 复制代码
obj-m += chrdev.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

3】编译工程

make

4】下位机验证

注意:我们现在只有设备号,而没有设备文件

2)操作函数的集合
【1】操作函数集合

const struct file_operations *ops; // 操作函数的集合

实现一个字符设备驱动程序的主要编程工作都集中在操作函数集合,我们将来具体到某一个字符设备驱动程序的时候,只需要实现下列函数集合的子集就可以了。

【2】内核中提供的操作cdev的API

1】cdev_init(初始化cdev结构体)

2】cdev_add(将cdev注册到内核)

注释:

p:要注册的cdev地址

dev:要注册的设备号

count:要连续注册的cdev个数

3】cdev_del(从内核中注销cdev)

注释:

p:要注销的cdev地址

【3】实验

1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

2】创建新的工程

mkdir chrdev_func

3】编写程序

vim chrdev_func.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	10
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"myleds"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int dev = 0;	// store the major dev number and the minor dev number

// 1_step :define a struct cdev be named led_cdev
struct cdev led_cdev;

// 3_step :implement the function of led_fops
int led_open(struct inode *inode, struct file *fp)
{
	printk(KERN_EMERG "enter:%s\n", __FUNCTION__);

	return 0;
}

int led_close(struct inode *inode, struct file *fp)
{
	printk(KERN_EMERG "enter:%s\n", __FUNCTION__);

	return 0;
}

// 2_step :define a struct file_operation be named led_fops
// what functions shall we to implement ?
// there is turn_on and turn_off of the leds
// So, we should to implement the function of open() and release(), eithor or we should keep up the same type as the definations of the struct file_operations
struct file_operations led_fops = {
	.owner = THIS_MODULE,
	// int (*open) (struct inode *, struct file *);
	.open = led_open,
	// int (*release) (struct inode *, struct file *);
	.release = led_close
};

#if 0
// fixed register
int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// dev = major << 20 | minor;
	// there is a define func to do this task
	dev = MKDEV(major, minor);

	register_chrdev_region(dev, CHRDEV_NUM, CHRDEV_NAME);	//register the number of device

	return 0;
}
#else
// variable register
int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// there is a define func to register the number of devices automatically
	alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME);
	major = MAJOR(dev);	// gain the major dev number
	minor = MINOR(dev);	// gain the minor dev number
	
	printk(KERN_EMERG "dev number is :%d\n major number is :%d\n minor number is :%d\n", dev, major, minor);

	// 4_step :initalize the struct cdev object led_cdev
	cdev_init(&led_cdev, &led_fops);

	// 5_step :register led_cdev into Kernel
	cdev_add(&led_cdev, dev, CHRDEV_NUM);

	return 0;
}
#endif

void __exit chrdev_exit(void)
{
	// 6_step :destory cdev
	cdev_del(&led_cdev);
	unregister_chrdev_region(dev, CHRDEV_NUM);

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);

4】编写Makefile

vim Makefile

bash 复制代码
obj-m += chrdev_func.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

5】编译工程

make

6】下位机安装模块

7】写一个应用层程序测试

mkdir test

cd test

vim led_test.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define CDEV_PATH	"/dev/myleds"

int main(void)
{
	int fd = 0;
	
	if((fd = open(CDEV_PATH, O_RDWR)) < 0) {
		perror("open()");
		return -1;
	}

	printf("open success!\n");

	sleep(5);

	printf("closing...\n");

	close(fd);

	return 0;
}

arm-cortex_a9-linux-gnueabi-gcc led_test.c -o led_test

cp led_test /nfs_share/_install/

8】下位机测试

手动创建设备文件

mknod /dev/myleds c 244 10

./led_test

9】疑惑

内核中的打印函数与应用程序中的打印函数,执行顺序孰先孰后?

不确定:内核打印输出的是自己维护的缓冲区,应用程序打印输出的是标准输出缓冲区

二、GPIO库

1、读懂开发板原理图

LED0 GPIOB26

LED1 GPIOC11

LED2 GPIOC7

LED3 GPIOC12

输出低电平,灯亮

输出高电平,灯灭

2、CPU Data Sheet

#define GPIOBOUT *(volatile unsigned int *)0xC001B000

#define GPIOBOUT *(volatile unsigned int *)0xC001B000

#define GPIOBOUT *(volatile unsigned int *)0xC001B000

#define GPIOBOUT *(volatile unsigned int *)0xC001B000

#define GPIOBOUT *(volatile unsigned int *)0xC001B000

#define GPIOBOUT *(volatile unsigned int *)0xC001B000

3、内核中提供的操作GPIO的API

【0】gpio宏定义
【1】gpio_request(申请GPIO管脚)

int gpio_request(unsigned gpio, const char *label)

【2】使用GPI管脚

1】gpio_direction_input(设置输入)

2】gpio_direction_output(设置输出)

3】gpio_set_value(设置value)

4】gpio_get_value(获取value)

【3】gpio_free(释放GPIO管脚)

void gpio_free(unsigned gpio)

4、实验

【1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

【2】创建新的工程

mkdir led_drv

【3】编写程序

vim led_drv.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	26
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"myleds"
#define HIGH			1
#define LOW				0

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int dev = 0;	// store the major dev number and the minor dev number

// 1_step :define a struct cdev be named led_cdev
struct cdev led_cdev;

// 3_step :implement the function of led_fops
int led_open(struct inode *inode, struct file *fp)
{
	printk(KERN_EMERG "enter:%s\n", __FUNCTION__);
	// c_step :set the value=0(turn on) of the gpio
	gpio_set_value(PAD_GPIOB26, LOW);

	return 0;
}

int led_close(struct inode *inode, struct file *fp)
{
	printk(KERN_EMERG "enter:%s\n", __FUNCTION__);
	// c_step :set the value=1(turn off) of the gpio
	gpio_set_value(PAD_GPIOB26, HIGH);

	return 0;
}

// 2_step :define a struct file_operation be named led_fops
// what functions shall we to implement ?
// there is turn_on and turn_off of the leds
// So, we should to implement the function of open() and release(), eithor or we should keep up the same type as the definations of the struct file_operations
struct file_operations led_fops = {
	.owner = THIS_MODULE,
	// int (*open) (struct inode *, struct file *);
	.open = led_open,
	// int (*release) (struct inode *, struct file *);
	.release = led_close
};

#if 0
// fixed register
int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// dev = major << 20 | minor;
	// there is a define func to do this task
	dev = MKDEV(major, minor);

	register_chrdev_region(dev, CHRDEV_NUM, CHRDEV_NAME);	//register the number of device

	return 0;
}
#else
// variable register
int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;	// major dev number
	int minor = CHRDEV_MINOR;		// minor dev number

	// there is a define func to register the number of devices automatically
	alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME);
	major = MAJOR(dev);	// gain the major dev number
	minor = MINOR(dev);	// gain the minor dev number
	
	printk(KERN_EMERG "dev number is :%d\n major number is :%d\n minor number is :%d\n", dev, major, minor);

	// 4_step :initalize the struct cdev object led_cdev
	cdev_init(&led_cdev, &led_fops);

	// 5_step :register led_cdev into Kernel
	cdev_add(&led_cdev, dev, CHRDEV_NUM);

	// a_step :apply gpio
	gpio_request(PAD_GPIOB26, "LED0");

	// b_step :set the default value=1(turn_off) of GPIOB26
	gpio_direction_output(PAD_GPIOB26, HIGH);

	return 0;
}
#endif

void __exit chrdev_exit(void)
{
	// e_step :release gpio
	gpio_free(PAD_GPIOB26);

	// 6_step :destory cdev
	cdev_del(&led_cdev);
	unregister_chrdev_region(dev, CHRDEV_NUM);	// unregister the number of dev

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);
【4】编写Makefile

vim Makefile

bash 复制代码
obj-m += led_drv.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程

make

【6】下位机安装模块
【7】编写应用层程序

mkdir test

cd test

vim led_test.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define CDEV_PATH	"/dev/myleds"

int main(void)
{
	int fd = 0;
	
	if((fd = open(CDEV_PATH, ORDWR)) < 0) {
		perror("open()");
		return -1;
	}

	printf("open success!\n");

	sleep(5);

	printf("closing...\n");

	close(fd);

	return 0;
}

arm-cortex_a9-linux-gnueabi-gcc led_test.c -o led_test

cp led_test /nfs_share/_install/

【8】下位机测试

手动创建设备文件

mknod /dev/myleds c 244 26

./led_test

三、用户态与内核态的数据交互

用户空间不能直接访问内核空间

内核空间不能直接访问用户空间

1)内核中提供的数据交互的API

【1】传递多数据
1】copy_to_user(内核到用户)

int copy_to_user(void __user *to, const void *from, int n)

注释:

to:内核空间缓冲区地址,

from:用户空间地址

n:数据字节数

retval:不能被复制的字节数,返回0表示全部复制成功。

2】copy_from_user(用户到内核)

int copy_from_user(void *to, const void __user *from, int n)

注释:

to:内核空间缓冲区地址,

from:用户空间地址

n:数据字节数

retval:不能被复制的字节数,返回0表示全部复制成功。

【2】传递单数据

可以从指定空间获取单个数据,单个数据并不是指一个字节数据,对ARM而言,一次性可获取一个char、short或者 int型的数据,即1、2或者4字节。

1】put_user(x, ptr)(内核到用户)

注释:

x :内核空间的数据,

p :用户空间的指针。

传递成功,返回 0,否则返回-EFAULT。

2】get_user(x, ptr)(用户到内核)

注释:

x :内核空间的数据,

p :用户空间的指针。

传递成功,返回 0,否则返回-EFAULT。

注意:

以上API与C标准库中memcpy(3)相似,但多了一个对访问的空间的权限检查

2、实验

【1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

【2】创建新的工程

mkdir param_drv

【3】编写程序

vim param_drv.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	26
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"myleds"
#define HIGH			1
#define LOW				0
#define LED0	(PAD_GPIO_B + 26)
#define LED1	(PAD_GPIO_C + 12)
#define LED2	(PAD_GPIO_C + 7)
#define LED3	(PAD_GPIO_C + 11)

unsigned int leds[] = {LED0, LED1, LED2, LED3};
const char *leds_label[] = {"LED0", "LED1", "LED2", "LED3"};

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int dev = 0;

struct cdev led_cdev;

int k_cmd = 0;	//kernel's buffer
int k_status = 0;	//the state of LEDs

int led_open(struct inode *inode, struct file *fp)
{
	return 0;
}

int led_close(struct inode *indoe, struct file *fp)
{
	return 0;
}

ssize_t led_read(struct file *fp, char __user *buf, size_t len, loff_t *offset)
{
	int ret = 0;

	ret = copy_to_user(buf, &k_status, len);

	return len;
}

ssize_t led_write(struct file *fp, const char __user *buf, size_t len, loff_t *offset)
{
	int ret = 0;
	int i = 0;

	ret = copy_from_user(&k_cmd, buf, len);

	for (i = 0; i < sizeof(leds) / sizeof(leds[0]); i++) {
		gpio_set_value(leds[i], k_cmd);
	}
	k_status = k_cmd;

	return len;
}

struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_close,
	.read = led_read,
	.write = led_write
};

int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;
	int minor = CHRDEV_MINOR;
	int i = 0;

	alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME);
	major = MAJOR(dev);
	minor = MINOR(dev);

	printk(KERN_EMERG "major = %d\nminor = %d\n", major, minor);

	cdev_init(&led_cdev, &led_fops);

	cdev_add(&led_cdev, dev, CHRDEV_NUM);

	for (i = 0; i < sizeof(leds) / sizeof(leds[0]); i++) {
		gpio_request(leds[i], leds_label[i]);
		gpio_direction_output(leds[i], HIGH);
	}

	return 0;
}

void __exit chrdev_exit(void)
{
	int i = 0;

	for (i = 0; i < sizeof(leds) / sizeof(leds[0]); i++) {
		gpio_free(LED1);
	}

	cdev_del(&led_cdev);
	unregister_chrdev_region(dev, CHRDEV_NUM);

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);
【4】编写Makefile

vim Makefile

bash 复制代码
obj-m += param_drv.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程

make

【6】下位机安装模块
【7】编写应用层程序

mkdir test

cd test

vim led_test.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define ON		0
#define OFF		1
#define CDEV_PATH	"/dev/myleds"

int main(int argc, char *argv[])
{
	int fd = 0;
	int cmd = 0;
	int status = 0;

	if (argc < 2) {
		printf("Usage : %s <on/off>\n", argv[0]);
		return -1;
	}

	if (!strcmp(argv[1], "on")) {
		cmd = ON;
	} else if (!strcmp(argv[1], "off")){
		cmd = OFF;
	} else {
		printf("illegal param\n");
		return -2;
	}
	
	if((fd = open(CDEV_PATH, O_RDWR)) < 0) {
		perror("open()");
		return -3;
	}

	printf("open success!\n");

	write(fd, &cmd, sizeof(cmd));
	read(fd, &status, sizeof(status));

	if (status == ON) {
		printf("Led is On!\n");
	} else {
		printf("Led is Off!\n");
	}

	printf("closing...\n");

	close(fd);

	return 0;
}

vim Makefile

bash 复制代码
SRC=led_test.c
OBJ=led_test

ARM_COMPILE=arm-cortex_a9-linux-gnueabi-
GCC=gcc

ROOTFS_PATH=/nfs_share/_install

all:
	$(ARM_COMPILE)$(GCC) $(SRC) -o $(OBJ)
	cp $(OBJ) $(ROOTFS_PATH)

clean:
	rm -rf $(OBJ)
【8】编译工程

make

【9】下位机测试

手动创建设备文件

mknod /dev/myleds c 244 26

./led_test

四、ioctl

1)介绍

2)实操

【1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

【2】创建新的工程

mkdir ioctl

【3】编写程序

vim ioctl.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	26
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"myleds"
#define HIGH			1
#define LOW				0
#define LED0	(PAD_GPIO_B + 26)
#define LED1	(PAD_GPIO_C + 12)
#define LED2	(PAD_GPIO_C + 7)
#define LED3	(PAD_GPIO_C + 11)
#define TURN_ON			LOW
#define TURN_OFF		HIGH



dev_t dev = 0;

struct cdev led_cdev;

typedef struct led_desc{
	unsigned int gpio;
	char *name;
}led_desc_t;

led_desc_t leds[] = {
	{LED0, "LED0"},
	{LED1, "LED1"},
	{LED2, "LED2"},
	{LED3, "LED3"}
};

long led_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
	int k_index = 0;
	int ret = 0;

	ret = copy_from_user(&k_index, (const void *)arg, sizeof(int));
	if (k_index > 4 || k_index < 1)
		return -EINVAL;
	
	switch (cmd) {
		case TURN_ON:
			gpio_set_value(leds[k_index - 1].gpio, LOW);
			break;
		case TURN_OFF:
			gpio_set_value(leds[k_index - 1].gpio, HIGH);
			break;
		default:
			return -EINVAL;
	}

	return arg;
}

struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = led_ioctl
};

int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;
	int minor = CHRDEV_MINOR;
	int i = 0;

	alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME);
	major = MAJOR(dev);
	minor = MINOR(dev);

	printk(KERN_EMERG "major = %d\nminor = %d\n", major, minor);

	cdev_init(&led_cdev, &led_fops);

	cdev_add(&led_cdev, dev, CHRDEV_NUM);

	for (i = 0; i < ARRAY_SIZE(leds); i++) {
		gpio_request(leds[i].gpio, leds[i].name);
		gpio_direction_output(leds[i].gpio, HIGH);
	}

	return 0;
}

void __exit chrdev_exit(void)
{
	int i = 0;

	for (i = 0; i < ARRAY_SIZE(leds); i++) {
		gpio_free(leds[i].gpio);
	}

	cdev_del(&led_cdev);
	unregister_chrdev_region(dev, CHRDEV_NUM);

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);
【4】编写Makefile

vim Makefile

bash 复制代码
obj-m += ioctl.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程

make

【6】下位机安装模块
【7】编写应用层程序

mkdir test

cd test

vim led_test.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#define ON		0
#define OFF		1
#define CDEV_PATH	"/dev/myleds"

int main(int argc, char *argv[])
{
	int fd = 0;
	int cmd = 0;
	int index = 0;

	if (argc < 3) {
		printf("Usage : %s <on/off> <1/2/3/4>\n", argv[0]);
		return -1;
	}

	if (!strcmp(argv[1], "on")) {
		cmd = ON;
	} else if (!strcmp(argv[1], "off")){
		cmd = OFF;
	} else {
		printf("illegal param\n");
		return -2;
	}
	
	index = atoi(argv[2]);
	if (index < 1 || index > 4) {
		printf("illegal param\n");
		return -2;
	}

	if((fd = open(CDEV_PATH, O_RDWR)) < 0) {
		perror("open()");
		return -3;
	}
	printf("open success!\n");

	ioctl(fd, cmd, &index);

	printf("closing...\n");

	close(fd);

	return 0;
}

vim Makefile

bash 复制代码
SRC=led_test.c
OBJ=led_test

ARM_COMPILE=arm-cortex_a9-linux-gnueabi-
GCC=gcc

ROOTFS_PATH=/nfs_share/_install

all:
	$(ARM_COMPILE)$(GCC) $(SRC) -o $(OBJ)
	cp $(OBJ) $(ROOTFS_PATH)

clean:
	rm -rf $(OBJ)
【8】编译工程

make

【9】下位机测试

手动创建设备文件

mknod /dev/myleds c 244 26

./led_test

相关推荐
pofenx4 小时前
使用nps创建隧道,进行内网穿透
linux·网络·内网穿透·nps
Ronin3054 小时前
【Linux系统】单例式线程池
linux·服务器·单例模式·线程池·线程安全·死锁
wanhengidc4 小时前
手机云服务是什么意思?
运维·网络·安全·游戏·智能手机
desssq4 小时前
ubuntu 18.04 泰山派编译报错
linux·运维·ubuntu
Lzc7744 小时前
Linux的多线程
linux·linux的多线程
清风笑烟语4 小时前
Ubuntu 24.04 搭建k8s 1.33.4
linux·ubuntu·kubernetes
Dovis(誓平步青云)5 小时前
《Linux 基础指令实战:新手入门的命令行操作核心教程(第一篇)》
linux·运维·服务器
好名字更能让你们记住我5 小时前
MYSQL数据库初阶 之 MYSQL用户管理
linux·数据库·sql·mysql·adb·数据库开发·数据库架构
半桔5 小时前
【网络编程】TCP 服务器并发编程:多进程、线程池与守护进程实践
linux·服务器·网络·c++·tcp/ip
维尔切6 小时前
Shell 脚本编程:函数
linux·运维·自动化