Hi,Linux driver fans们好,我现在从事touch/display ic tuning工作,脱离linux 嵌入式开发好多年了,最近想捡起来温习一下。按照我自己的拙见,linux纯驱动开发一般分为几大类:
1. 字符类设备(char device)驱动开发,常见的串口,并口,canbus,以及键盘/鼠标/touchscreen等input之类的device,常见字符设备节点/dev/ttyS, /dev/tty, /dev/audio,/dev/lp0 etc.
2.块设备驱动(block device)开发,常见的硬盘驱动(IDE/SATA接口) 常见块设备设备节点,/dev/cdrom, /dev/fd0, /dev/hda,/dev/sda,
3.network device, 如常见的pci接口的intel e100/e1000, Realtek 8139 series, 访问节点eth0, wlan0
x86平台上,可能以字符驱动和网络驱动调试比较多,非x86基本上都会碰到,如果想提高综合能力,非x86我个人认为更合适(如arm/mips, powerpc)
一般驱动开发可以粗鲁地分为几个组成部分。
1.driver code
2.Makefile(如果是要上传到linux kernel,可能还需要提供Kconfig和内核版本的Makefile)
3.demo code
下面贴上一个简单的字符模拟驱动编写实例,(临时编写,代码风格不规范,不作为参考内容,重点请参考一个驱动架构和访问方式,入门式参考)
我的运行环境:
Centos [root@localhost chardemoRef]# uname -r
4.18.0-240.1.1.el8_3.x86_64
Vbox虚拟机
驱动部分:(driver会在/dev目录下自动创建/dev/demochar0 /dev/demochar1节点)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h> //head file for file_operations
#include <linux/uaccess.h> //for memory operations copy_to/from_user
#include <linux/device.h> //for class/device create
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/version.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin.Jiang");
#define DEV_NAME "chardev"
#define DEVICE_COUNT 2
#define DEV_SIZE 0x400 //define char device size, 400 bytes
static ssize_t demo_chardevRead(struct file *,char *,size_t,loff_t*);
static ssize_t demo_chardevWrite(struct file *,const char *,size_t,loff_t*);
static int char_major = 0;
static char chardevData[1024] = "Wellcome!pls input new data\n"; //char device global variable.
static struct class *char_demo_class; //for auto creating char device interface
//initialize char device’s file_operations struct
struct file_operations chardev_fops =
{
.read = demo_chardevRead,
.write = demo_chardevWrite
};
static int __init chardev_init(void)
{
int major;
int i;
int retval;
struct device *char_demo_device;
major = register_chrdev(char_major,DEV_NAME,&chardev_fops);
printk( "=====================chardev_init====================\n");
if(major < 0){
retval = major;
printk("chardev Reg Fail!\n");
goto chrdev_err;
}
else
{
printk("chardev Reg Success!\n");
char_major = major;
printk("Major = %d\n",char_major);
}
/*create device class*/
char_demo_class = class_create(THIS_MODULE,"demo_char_class");
if(IS_ERR(char_demo_class)){
retval = PTR_ERR(char_demo_class);
goto class_err;
}
/*create device file,notify user the interface created in /dev/ with named demoX*/
for(i=0; i<DEVICE_COUNT; i++){ /*create 255 nodes at most between range 0-255 for the slave device */
char_demo_device = device_create(char_demo_class,NULL, MKDEV(major, i), NULL,"demochar%d",i);
if(IS_ERR(char_demo_device)){
retval = PTR_ERR(char_demo_device);
goto device_err;
}
}
return 0;
device_err:
while(i--) //recycle operations for device;
class_destroy(char_demo_class);
class_err:
unregister_chrdev(major, "demo_chardev");
chrdev_err:
return retval;
return retval;
}
static void __exit chardev_exit(void)
{
int i = 0;
printk("[msg]:in module exit function\n");
/*unregister the char major device*/
unregister_chrdev(char_major,DEV_NAME);
/*delete the device nodes*/
for(i=0; i<DEVICE_COUNT; i++)
device_destroy(char_demo_class,MKDEV(char_major, i));
class_destroy(char_demo_class);
printk("chardev is dead now!\n");
}
static ssize_t demo_chardevRead(struct file *filp,char __user *buf,size_t len,loff_t *off)
{
// static char chardevData[1024] = "Wellcome!pls input new data\n"; //"chardev" device’s global variable.
unsigned long offset = *off;
unsigned int count = len;
unsigned int Maxlen = 0;
int ret = 0;
/*get the valid length for input content*/
printk("------------------Start Read------------------\n");
Maxlen = strlen(chardevData);
if (offset > DEV_SIZE)
{
printk("offset > DEV_SIZE\n");
return count ? - ENXIO: 0;
}
else if(offset == DEV_SIZE)
{
printk("offset = DEV_SIZE\n");
return 0; //prevent EOF error when cat /dev/chardev
}
if (count > DEV_SIZE - offset)
{
printk("count > DEV_SIZE -offset\n");
count = DEV_SIZE - offset;
}
// printk("read char_len=%ld\n",strlen(chardevData));
// printk("read chardevData=%s\n",chardevData);
if (!(copy_to_user(buf,(char*)chardevData, strlen(chardevData)+1)))
{
*off += count;
printk(KERN_INFO "read %d bytes(s) from %ld addr\n", count, offset);
//ret = count;
if(chardevData[Maxlen-1]=='\n')
{
ret = count;
printk("------------------Here xyz------------------\n");
}
else
{
//ret = snprintf(buf,1024,"%As\n",buf);
ret = snprintf(buf,1024,"%s\n",chardevData);
printk("------------------Here abc------------------\n");
}
printk("------------------Here A------------------\n");
}
else
{
return -EFAULT;
}
printk("chardev is read: buf=%s",buf);
printk("chardev is read: ref=%d\n",ret);
return ret;
}
static ssize_t demo_chardevWrite(struct file *filp,const char __user *buf,size_t len,loff_t *off)
{
unsigned long offset = *off;
unsigned int count = len;
// static char chardevData[1024] = "Wellcome!pls input new data\n"; //"chardev" device’s global variable.
int ret = 0;
offset = 0;
printk(KERN_ALERT "--------Start Write-------------\n");
//printk("chardev is write now!\n");
printk("chardev is write: buf=%s\n",buf);
memset(chardevData,0,strlen(chardevData));
if (offset > DEV_SIZE)
{
printk("offset > DEV_SIZE\n");
return count ? - ENXIO: 0;
}
else if(offset == DEV_SIZE)
{
printk("offset = DEV_SIZE\n");
return 0; // present for EOF error when echo /dev/chardev
}
if (count > DEV_SIZE - offset)
{
count = DEV_SIZE - offset;
}
printk("count= %d\n",count);
printk("need len:%ld, offset:%ld\n",len,offset);
if (copy_from_user(chardevData,buf,count))
{
printk("copy error! chardevData=%s\n",chardevData);
printk("------------------Here B------------------\n");
return -EFAULT;
}
else
{
*off += count;
printk("------------------Here C------------------\n");
printk("read %d bytes(s) from %ld addr\n", count, offset);
ret = count;
}
printk("chardevData=%s\n",chardevData);
return ret;
}
module_init(chardev_init);
module_exit(chardev_exit);
Makefile部分:
obj-m:=demochar.o
KERNELDIR:= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
default:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.* *.rc
demo部分:(自己写代码或者直接用管道方式写入)
运行编译方式:gcc demo.c -o demo
执行方式:./demo
从键盘输入随机字符串,并printf出来。(也可以在dmesg里面看driver里面printk出来的信息)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
//#define DEV_NAME "/dev/chardev"
#define DEV_NAME "/dev/demochar0"
int main(void)
{
int fd;
// char num[1024]={1,2};;
char num[1024]="This is my input data----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n";
// int no1 = 0;
/*打开"/dev/globalvar"*/
//fd = open(DEV_NAME,O_RDWR | O_NDELAY);
fd = open(DEV_NAME, O_RDWR, S_IRUSR | S_IWUSR);
if (fd != -1 )
{
/*初次读globalvar*/
#if 0 //not must
read(fd, &num, strlen(num));
printf("The input data to /dev/demo0 is %s\n", num);
#endif
/*写globalvar*/
printf("Please input the num written to /dev/demochar0\n");
scanf("%s", &num);
write(fd, &num, strlen(num));
#if 0
/*再次读/dev/demo0*/
read(fd, &num, strlen(num));
printf("The input data is %s\n", num);
#endif
/*关闭"/dev/demo0"*/
close(fd);
}
else
{
/* if not sudo, maybe come here */
printf("Device open failure\n");
}
return 0;
}
Pipe方式:
写:echo 1111111111222222222222 >/dev/demochar0
读:cat /dev/demochar0
本文完毕
我的email address:
xianqiaoren@163.com
Kevin.Jiang
欢迎大家来email一起探讨学习交流。