12-虚拟串口驱动

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知


  虚拟串口设备是驱动代码虚拟出来的,不能实现真正的串口数据收发,但是它能 够接收用户想要发送的数据,并且将该数据原封不动的环回给串口的收端,使用户 也能从该串口接受数据。也就是说,该虚拟串口设备是一个功能弱化之后的只具备内环回作用的串口,如下图所示。

 内核层				用户层
————————
|      |  《======= write
|      |
| FIFO |
|      |
|      |   =======》read
————————

 这一功能的实现主要是在驱动中实现一个FIFO,驱动接收用户传来的数据然后将其 放到FIFO,当应用层想要获取数据时,驱动将FIFO中的数据读出,然后复制给应用层。

1 常用接口

1.1 初始化FIFO

原    型: DEFINE_KFIFO(fifo, type, size)	
功    能: 定义并初始化一个FIFO
@param1: 声明的FIFO数据类型的名称
@param2: FIFO中成员的数据类型,eg: char int ...
@param3: FIFO中元素的数量,元素的个数必须是2的幂

1.2 读FIFO

原    型: kfifo_to_user(fifo, to, len, copied)
功    能: 将FIFO中的数据取出,复制到用户空间
@param1: 要使用的FIFO的地址
@param2: 复制数据的位置
@param3: 目标缓冲区的大小
@param4: 指向输出变量的指针,用于存储复制的字节数
@return: -EFAULT/0

1.3 写FIFO

原    型: kfifo_from_user(fifo, to, len, copied)
功    能: 将用户空间中的数据取出,复制到FIFO中
@param1: 要使用的FIFO的地址
@param2: 指向要添加的数据的指针
@param3: 要添加的数据的长度
@param4: 指向输出变量的指针,用于存储复制的字节数
@return: -EFAULT/0

1.4 FIFO中数据空/满的判断

原    型: kfifo_is_empty(fifo)
功    能: 判断fifo是否为空
@param1: 要判断的fifo
@return: fifo为空返回真

原    型: kfifo_is_full(fifo)
功    能: 判断fifo是否为满
@param1: 要判断的fifo
@return: fifo为满返回真

2.示例代码

2.1 demo.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>


struct class *cls = NULL;
struct device *dev = NULL;


struct cdev test_cdev;

/*
	原   型  : DEFINE_KFIFO(fifo, type, size)	
	功   能  : 定义并初始化一个FIFO
	@param1: 声明的FIFO数据类型的名称
	@param2: FIFO中成员的数据类型,eg: char int ...
	@param3: FIFO中元素的数量,元素的个数必须是2的幂
*/
DEFINE_KFIFO(virtual_serial_fifo, char, 32);

int demo_open(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

int demo_release(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}


/**
 * __user : 目的是提醒驱动的开发者,这个内存空间属于用户空间 
 *
 * 原型   : kfifo_to_user(fifo, to, len, copied)
 * 功能   : 将FIFO中的数据取出,复制到用户空间
 * @param1: 要使用的FIFO的地址
 * @param2: 复制数据的位置
 * @param3: 目标缓冲区的大小
 * @param4: 指向输出变量的指针,用于存储复制的字节数
 * @return: -EFAULT/0
 */
static ssize_t demo_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num = 0;
	unsigned int retval;
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	retval = kfifo_to_user(&virtual_serial_fifo, userbuf, size, &copied_num);
	if (retval == -EFAULT)
	{
		printk("kfifo_to_user failed.\n");
		goto err0;
	}
	printk("copied_num = %d.\n", copied_num);


	return copied_num;

err0:
	return retval;	
}

/**
 * __user : 目的是提醒驱动的开发者,这个内存空间属于用户空间 
 *
 * 原型   : kfifo_from_user(fifo, to, len, copied)
 * 功能   : 将用户空间中的数据取出,复制到FIFO中
 * @param1: 要使用的FIFO的地址
 * @param2: 指向要添加的数据的指针
 * @param3: 要添加的数据的长度
 * @param4: 指向输出变量的指针,用于存储复制的字节数
 * @return: -EFAULT/0
 */
static ssize_t demo_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num = 0;
	unsigned int retval;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	retval = kfifo_from_user(&virtual_serial_fifo, userbuf, size, &copied_num);
	if (retval == -EFAULT)
	{
		printk("kfifo_from_user failed.\n");
		goto err0;
	}
	printk("copied_num = %d.\n", copied_num);
	
	return copied_num;

err0:
	return retval;
}


const struct file_operations fops = {
	.open = demo_open,
	.release = demo_release,
	.read = demo_read,
	.write = demo_write,
};

static int __init demo_init(void)
{
	dev_t dev_no = MKDEV(255, 0);
	int ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	ret = register_chrdev_region(dev_no, 1, "test_cdev");
	if (ret)
	{
		printk("register_chrdev_region failed\n");
		return ret;
	}

	cdev_init(&test_cdev, &fops);

	ret = cdev_add(&test_cdev, dev_no, 1);
	if (ret)
	{
		printk("cdev_add failed\n");
		return ret;
	}

	cls = class_create(THIS_MODULE, "test_cls");
	if (cls == NULL)
	{
		printk("class_create failed\n");
		return -1;
	}

	dev = device_create(cls, NULL, dev_no, NULL, "test_dev");
	if (dev == NULL)
	{
		printk("device_create failed\n");
		return -1;
	}
	
	return 0;
}

static void __exit demo_exit(void)
{
	dev_t dev_no = MKDEV(255, 0);

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	device_destroy(cls, dev_no);
	class_destroy(cls);

	cdev_del(&test_cdev);
	unregister_chrdev_region(dev_no, 1);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

2.2 test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

const char *pathname = "/dev/test_dev";

int main(int argc, const char *argv[])
{
	int fd;
	char buf[32];

	if (argc < 2)
	{
		printf("please input argv[1]\n");
		return -1;
	}

	fd = open(pathname, O_RDWR, 0666);
	if (fd < 0)
	{
		printf("open failed\n");
		return fd;
	}

	strcpy(buf, argv[1]);
	write(fd, buf, sizeof(buf));
	read(fd, buf, sizeof(buf));

	printf("buf = %s\n", buf);

	close(fd);

	return 0;
}

2.3 Makefile

KERNELDIR ?= /home/lzj/ti-processor-sdk-linux-am335x-evm-05.02.00.10/board-support/linux-4.14.79/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

2.4 测试结果

root@am335x-evm:~# insmod demo.ko 
[  469.997454] demo_init -- 119.
root@am335x-evm:~# ./app hello world
[  475.559439] demo_open -- 28.
[  475.562883] demo_write -- 90.
[  475.565914] copied_num = 32.
[  475.568839] demo_read -- 57.
[  475.571751] copied_num = 32.
buf = hello
[  475.581090] demo_release -- 35.
root@am335x-evm:~# rmmod demo.ko 
[  479.871196] demo_exit -- 158.
```12-
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值