虚拟串口设备驱动

news/2024/11/30 3:36:43/

前面内容:
1 Linux驱动—内核模块基本使用

2 Linux驱动—内核模块参数,依赖(进一步讨论)

3 字符设备驱动

虚拟串口设备驱动

  • 虚拟串口设备
  • 虚拟串口设备驱动

先学习下虚拟串口设备是啥?

虚拟串口设备

在进一步实现字符设备驱动之前,我们先来讨论一下这本书中用到的一个虚拟串口设备。这个设备是驱动代码虚拟出来的,不能实现真正的串口数据收发,但是它能够接收用户想要发送的数据,并且将该数据原封不动地环回给串口的收端,使用户也能从该串口接收数据。也就是说,该虚拟串口设备是一个功能弱化之后的只具备内环回作用的串口,如图3.3所示。
在这里插入图片描述

这一功能的实现

主要是在驱动中实现了一个FIFO,驱动接收用户层传来的数据,然后将之放入FIFO,当应用层要获取数据时,驱动将FIFO中的数据读出,然后复制给应用层。
一个更贴近实际的形式应该是在驱动中有两个FIFO,一个用于发送,一个用于接收,但是这并不是实现这个简单的虛拟串口设备驱动的关键,所以为了简单起见,这里只用了一个FIFO。

内核中已经有了一个关于FIFO的数据结构struct kfifo,相关的操作宏或函数的声明、
定义都在“include/inux/kfifo.h"头文件中,下面将最常用的宏罗列如下。

DEFINE_KFIFO(fifo,type,size)
kfifo_from_user(fifo,from,len,copied)
kfifo_to_user(fifo,to,len,copied)

DEFINE_ KFIFO用于定义并初始化一个FIFO,这个变量的名字由fifo参数决定, type是FIFO中成员的类型size 则指定这个FIFO有多少个元素但是元素的个数必须是2的幂

kfifo_from_user 是将用户空间的数据(from) 放入FIFO中,元素个数由len来指定,实际放入的元素个数由copied返回

kfifo_to_user 则是将FIFO中的数据取出,复制到用户空间(to)。 len 和copied的含义同kfifo_ from_ user 中对应的参数。

虚拟串口设备驱动

字符设备驱动除了前面搭建好的框架外,接下来最重要的是实现设备的操作方法。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#define VSER_MAJOR	256
#define VSER_MINOR	0
#define VSER_DEV_CNT	1
#define VSER_DEV_NAME	"vser"static struct cdev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);static int vser_open(struct inode *inode, struct file *filp)
{return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;kfifo_to_user(&vsfifo, buf, count, &copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;kfifo_from_user(&vsfifo, buf, count, &copied);return copied;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if (ret)goto reg_err;cdev_init(&vsdev, &vser_ops);vsdev.owner = THIS_MODULE;ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);cdev_del(&vsdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");

新的代码驱动在代码DEFINE_KFIFO(vsfifo, char, 32);定义并初始化了一个名叫vsfifo的structu kfifo对象,每个对象的类型为char,共有32个元素的空间。

static int vser_open(struct inode *inode, struct file *filp)
{return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}

代码实现了设备的打开和关闭函数,分别对应于file_operations 内的openrelease方法。
因为是虚拟设备,所以这里并没有需要特别处理的操作,仅仅返回0表示成功。
这两个函数都有两个相同的形参,

  1. 第一个形参是要打开或关闭文件的inode,
  2. 第二个形参则是打开对应文件后由内核构造并初始化好的file 结构

在前面的章节中我们已经较深入地分析了这两个对象的作用。
在这里之所以叫release而不叫close是因为一个文件可以被打开多次,那么vser_ open函数相应地会被调用多次,但是关闭文件只有到最后一个close操作才会导致vser_ release函数被调用,所以用release更贴切。

代码

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;kfifo_to_user(&vsfifo, buf, count, &copied);return copied;
}

是read系统调用的驱动实现,这里主要把FIFO中的数据返回给用户层,使用了kfifo_to_user这个宏。返回给用户层。
read 系统调用要求用户返回实际读取的字节数,而copied变量的值正好符合这一要求。

代码第36行到第43行是对应的write系统、

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;kfifo_from_user(&vsfifo, buf, count, &copied);return copied;
}

调用的驱动实现,同read系统调用一样,只是数据流向相反而已。

读和写函数引入了3个新的形参,分别是buf, count 和pos,根据上面的代码,已经不难发现它们的含义。buf 代表的是用户空间的内存起始地址; count 表示用户想要读写多少个字节的数据:而pos是文件的位置指针,在虚拟串口这个不支持随机访问的设备中,该参数无用。_user是提醒驱动代码编写者,这个内存空间属于用户空间。

代码

static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,
};

这里是将file_operations 的函数指针分别指向上面定义的函数
你看 .read指向vser_read函数

这样在驱动这样在应用层发生相应的系统调用后,在驱动里面的函数就会被相应地调用

上面这个示例实现了一个功能非常简单,但是基本可用的虚拟串口驱动程序。

按照下面的步骤可以进行验证。

先创建设备号

sudo mknod /dev/vser0 c 256 0 

在这里插入图片描述

然后编译运行
make
make modules_install
sudo modpeobe vser
在这里插入图片描述

然后 往这个设备中dev/vser0写入数据
echo "vser deriver test" > /dev/vser0
然后查看
cat /dev/vser0
最后
会出现
vser deriver test

通过实验结果可以看到,对/dev/vser0 写入什么数据,就可以从这个设备读到什么数据,和一个具备内环回功能的串口是一致的。

为了方便读者对照查阅,特将file_operations 结构类型的定义代码列出。从中我们可以看到,还有很多接口函数还没有实现,在后面的章节中,我们会陆续再实现一些接口。

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*mremap)(struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endif
};

显然,一个驱动对下面的接口的实现越多,它对用户提供的功能就越多,但这也不是说我们必须要实现下面的所有函数接口。比如串口不支持随机访问,那么lseek函数接口自然就不用实现。


http://www.ppmy.cn/news/252071.html

相关文章

串口使用简介

1、 串口的作用 UART&#xff1a;通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter)&#xff0c;简称串口。 ①调试&#xff1a;移植u-boot、内核、应用程序时&#xff0c;主要使用串口查看打印信息 ②外接各种模块 2、串口参数 ①波特率&#xf…

多个USB转串口设备区分方法

概述 当计算机或者其他USB主机上使用多个USB转串口设备时&#xff0c;会遇到多个串口无法与具体的串口设备对应起来的问题&#xff0c;包括更换不同USB端口串口序号发生改变&#xff0c;多个设备USB插拔顺序不同导致串口序号改变等问题。 本文提出几种常见解决方式&#xff0…

串口相关

区分&#xff1a;串口&#xff0c;COM口&#xff0c;UART&#xff0c;USART https://blog.csdn.net/qq_26904271/article/details/79829363 串口通信 https://blog.csdn.net/zxh1592000/article/details/78656609 串口&#xff1a;传输一个字节&#xff08;8个位&#xff09;的…

USB转多串口设备固定串口号

1、概述 使用USB转串口设备或多个USB转串口设备级联扩展多串口时&#xff0c;会经常遇到USB设备插拔顺序或插入的USB主机口位置不固定&#xff0c;系统重新开机等操作导致设备对应串口号无法固定&#xff0c;影响产品使用。 针对如上应用问题我司提供内置USB Serial Number&a…

Android系统访问串口设备

在常见的嵌入式外设中&#xff0c;串口通信是经常使用的一种通信机制&#xff0c;本篇文章给你带来&#xff0c;如何在Android系统中实现对串口设备的访问。 在Android中如何访问底层Linux的设备驱动&#xff0c;必然要用到HAL&#xff0c;即&#xff1a;硬件抽象层。关于HAL的…

关于串口通信

串口通信在嵌入式领域使用非常广泛&#xff0c;如3G、4G、5G、NB-iot、Lora等几乎都是。因此&#xff0c;做嵌入式开发人员很有必要学习一下。 首先要了解计算机的通信方式&#xff0c;通过硬件发出的高、低电平&#xff0c;计算机处理0、1数字信号&#xff0c;完成通信。 一、…

查看串口设备

linux查看所有串口和usb设备方法 1、查看串口是否可用 可以对串口发送数据比如对com1口&#xff0c;echo /dev/ttyS0 2、查看串口名称使用 ls -l /dev/ttyS* 一般情况下串口的名称全部在dev下面&#xff0c;如果你没有外插串口卡的话默认是dev下的ttyS*,一般ttyS0对应com1&…

USB转串口设备如何固定串口号

使用USB转串口设备或多个USB转串口设备级联扩展多串口时&#xff0c;会经常遇到USB设备插拔顺序不固定、插入的USB主机口位置不固定、系统重新开机等操作导致设备对应串口号发生改变&#xff0c;影响产品使用的情况。 Windows系统固定串口号 在Windows系统上对于此需求推荐使…