Cover image for Rpmsg Char Driver

Rpmsg Char Driver

字数 3.5k
阅读
访客

时间轴

时间轴

2026-06-29

init

本文介绍了Linux 5.10.x中Rpmsg字符设备驱动的设计与实现。该驱动注册到Rpmsg总线,采用控制设备与端点设备分离的模式:控制节点通过ioctl创建端点,端点设备负责用户态读写消息。文章分析了关键数据结构、锁与等待队列的使用、probe/remove流程、端点的延迟创建机制,以及读写路径中的阻塞唤醒、内存拷贝等细节,并指出了缓冲区大小等注意事项。

当前代码分析基于 linux 5.10.x

rpmsg.drawio
rpmsg.drawio

rpmsg_char.c实现了rpmsg_driver并注册到 Rpmsg Bus 总线上,提供与用户态交互的接口,承接virtio_rpmsg_bus.c注册的rpmsg_device

module_init / module_exit

12345678910111213141516171819202122232425262728293031323334353637383940414243
#define RPMSG_DEV_MAX	(MINORMASK + 1)static dev_t rpmsg_major;static struct class *rpmsg_class;static int rpmsg_char_init(void){	int ret;	ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg");	if (ret < 0) {		pr_err("rpmsg: failed to allocate char dev region\n");		return ret;	}	rpmsg_class = class_create(THIS_MODULE, "rpmsg");	if (IS_ERR(rpmsg_class)) {		pr_err("failed to create rpmsg class\n");		unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);		return PTR_ERR(rpmsg_class);	}	ret = register_rpmsg_driver(&rpmsg_chrdev_driver);	if (ret < 0) {		pr_err("rpmsgchr: failed to register rpmsg driver\n");		class_destroy(rpmsg_class);		unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);	}	return ret;}postcore_initcall(rpmsg_char_init);static void rpmsg_chrdev_exit(void){	unregister_rpmsg_driver(&rpmsg_chrdev_driver);	class_destroy(rpmsg_class);	unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);}module_exit(rpmsg_chrdev_exit);MODULE_ALIAS("rpmsg:rpmsg_chrdev");MODULE_LICENSE("GPL v2");

rpmsg_char.c是一个字符设备驱动,层次结构如下:

rpmsg_char.drawio
rpmsg_char.drawio

注意它用的是postcore_initcall,比module_init早,因为rpmsg_core.c的总线也用postcore_initcall,两者都在驱动模型就绪后注册。

rpmsg_char_init做三件事:

  1. alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg") —— 动态申请主设备号,RPMSG_DEV_MAX = MINORMASK+1(即全部次设备号空间)。
  2. class_create(THIS_MODULE, "rpmsg")—— 创建/sys/class/rpmsg,这样cdev_device_add后会自动在/dev/下生成设备节点(依赖udev/devtmpfs)。
  3. register_rpmsg_driver(&rpmsg_chrdev_driver)—— 注册一个 rpmsg 驱动(不是平台驱动!是挂在 rpmsg 总线上的驱动)。

而rpmsg_chrdev_driver如下:

1234567
static struct rpmsg_driver rpmsg_chrdev_driver = {	.probe = rpmsg_chrdev_probe,	.remove = rpmsg_chrdev_remove,	.drv = {		.name = "rpmsg_chrdev",	},};

关键数据结构

类型设备节点struct谁创建作用
控制设备/dev/rpmsg_ctrl0rpmsg_ctrldevrpmsg_chrdev_probe一个 channel 一个;用户通过 ioctl 在它上面"创建端点"
端点设备/dev/rpmsg0rpmsg_eptdevrpmsg_ctrldev_ioctl(CREATE)一个端点一个;用户 read/write/poll 它来收发消息

这种"控制设备 + 数据设备"的分离是 Linux 里很经典的模式:控制节点用来实例化数据节点,数据节点承载真正的 IO。

struct rpmsg_ctrldev

1234567891011
/** * struct rpmsg_ctrldev - control device for instantiating endpoint devices * @rpdev:	underlying rpmsg device * @cdev:	cdev for the ctrl device * @dev:	device for the ctrl device */struct rpmsg_ctrldev {    struct rpmsg_device *rpdev;   // 底层 rpmsg 通道设备    struct cdev cdev;              // 字符设备    struct device dev;             // 嵌入的 device,挂在 rpmsg_class 下};

struct rpmsg_eptdev

1234567891011121314151617181920212223242526
/** * struct rpmsg_eptdev - endpoint device context * @dev:	endpoint device * @cdev:	cdev for the endpoint device * @rpdev:	underlying rpmsg device * @chinfo:	info used to open the endpoint * @ept_lock:	synchronization of @ept modifications * @ept:	rpmsg endpoint reference, when open * @queue_lock:	synchronization of @queue operations * @queue:	incoming message queue * @readq:	wait object for incoming queue */struct rpmsg_eptdev {    struct device dev;    struct cdev cdev;    struct rpmsg_device *rpdev;          // 底层通道    struct rpmsg_channel_info chinfo;    // open 时用的通道信息(name/src/dst)    struct mutex ept_lock;               // 保护 ept 指针的修改    struct rpmsg_endpoint *ept;          // open 时才创建的真实端点,可为 NULL    spinlock_t queue_lock;               // 保护接收队列    struct sk_buff_head queue;           // 接收消息队列(每条消息一个 skb)    wait_queue_head_t readq;             // 阻塞读的等待队列};

锁和等待队列:

  • struct mutex ept_lock;:保护 ept 指针。ept 会在三种情况下变 NULL:release、destroy ioctl、父设备remove。读/写路径要先拿这个锁确认 ept 还活着。
  • spinlock_t queue_lock;:保护 queue(skb 链表)。回调rpmsg_ept_cb在中断/原子上下文里入队,read_iter 在进程上下文出队,所以这里用自旋锁且 cb 里用spin_lock,read 里用spin_lock_irqsave。
  • wait_queue_head_t readq;:rpmsg_ept_cb入队后wake_up_interruptible,read_iter在队列空时wait_event_interruptible。这是典型的"生产者-消费者 + 等待队列"。

rpmsg_driver

1234567
static struct rpmsg_driver rpmsg_chrdev_driver = {	.probe = rpmsg_chrdev_probe,	.remove = rpmsg_chrdev_remove,	.drv = {		.name = "rpmsg_chrdev",	},};

注意:这里没有定义callback函数

rpmsg_chrdev_probe()

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#define RPMSG_DEV_MAX	(MINORMASK + 1)static dev_t rpmsg_major;static struct class *rpmsg_class;static DEFINE_IDA(rpmsg_ctrl_ida);static DEFINE_IDA(rpmsg_ept_ida);static DEFINE_IDA(rpmsg_minor_ida);static int rpmsg_chrdev_probe(struct rpmsg_device *rpdev){	struct rpmsg_ctrldev *ctrldev;	struct device *dev;	int ret;	ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);	if (!ctrldev)		return -ENOMEM;	ctrldev->rpdev = rpdev;	dev = &ctrldev->dev;	device_initialize(dev);	dev->parent = &rpdev->dev;	dev->class = rpmsg_class;	cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);	ctrldev->cdev.owner = THIS_MODULE;	ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);	if (ret < 0)		goto free_ctrldev;	dev->devt = MKDEV(MAJOR(rpmsg_major), ret);	ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);	if (ret < 0)		goto free_minor_ida;	dev->id = ret;	dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);	ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev);	if (ret)		goto free_ctrl_ida;	/* We can now rely on the release function for cleanup */	dev->release = rpmsg_ctrldev_release_device;	dev_set_drvdata(&rpdev->dev, ctrldev);	return ret;free_ctrl_ida:	ida_simple_remove(&rpmsg_ctrl_ida, dev->id);free_minor_ida:	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));free_ctrldev:	put_device(dev);	kfree(ctrldev);	return ret;}

当 rpmsg 总线把一个rpmsg_device匹配给rpmsg_chrdev_driver时调用rpmsg_chrdev_probe。流程:

  1. kzalloc 一个 ctrldev,ctrldev->rpdev = rpdev。
  2. device_initialize+ 设parent/class,cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops)。
  3. 两次ida_simple_get:从rpmsg_minor_ida拿次设备号,从rpmsg_ctrl_ida拿 id(用于rpmsg_ctrl%d)。
  4. cdev_device_add—— 把 cdev 和 device 一起注册,/dev/rpmsg_ctrlN出现。
  5. 重点:dev->release = rpmsg_ctrldev_release_device是在cdev_device_add之后才赋值。注释说"We can now rely on the release function for cleanup"——意思是cdev_device_add成功前若失败,走手动的 error label 释放;成功后则交给 device 模型的 release 回调兜底(最后一个引用归零时 kfree)。这是 Linux 设备模型里很标准的 refcount + release 范式。
  6. dev_set_drvdata(&rpdev->dev, ctrldev)—— 让 remove 时能从 rpdev 找回 ctrldev。

rpmsg_chrdev_remove()

12345678910111213
static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev){	struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);	int ret;	/* Destroy all endpoints */	ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_eptdev_destroy);	if (ret)		dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);	cdev_device_del(&ctrldev->cdev, &ctrldev->dev);	put_device(&ctrldev->dev);}

rpmsg_ctrldev 控制节点

file_operations

1234567
static const struct file_operations rpmsg_ctrldev_fops = {	.owner = THIS_MODULE,	.open = rpmsg_ctrldev_open,	.release = rpmsg_ctrldev_release,	.unlocked_ioctl = rpmsg_ctrldev_ioctl,	.compat_ioctl = compat_ptr_ioctl,};

rpmsg_ctrldev_open()

123456789
static int rpmsg_ctrldev_open(struct inode *inode, struct file *filp){	struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);	get_device(&ctrldev->dev);	filp->private_data = ctrldev;	return 0;}

rpmsg_ctrldev_release()

12345678
static int rpmsg_ctrldev_release(struct inode *inode, struct file *filp){	struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);	put_device(&ctrldev->dev);	return 0;}

probe 函数中有dev->release = rpmsg_ctrldev_release_device;,因此实际release函数为

12345678
static void rpmsg_ctrldev_release_device(struct device *dev){	struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);	ida_simple_remove(&rpmsg_ctrl_ida, dev->id);	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));	kfree(ctrldev);}

rpmsg_ctrldev_ioctl()

123456789101112131415161718192021
static long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,				unsigned long arg){	struct rpmsg_ctrldev *ctrldev = fp->private_data;	void __user *argp = (void __user *)arg;	struct rpmsg_endpoint_info eptinfo;	struct rpmsg_channel_info chinfo;	if (cmd != RPMSG_CREATE_EPT_IOCTL)		return -EINVAL;	if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))		return -EFAULT;	memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);	chinfo.name[RPMSG_NAME_SIZE-1] = '\0';	chinfo.src = eptinfo.src;	chinfo.dst = eptinfo.dst;	return rpmsg_eptdev_create(ctrldev, chinfo);};

通过该函数来创建 ept,调用rpmsg_eptdev_create()

rpmsg_eptdev_create()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
static int rpmsg_eptdev_create(struct rpmsg_ctrldev *ctrldev,			       struct rpmsg_channel_info chinfo){	struct rpmsg_device *rpdev = ctrldev->rpdev;	struct rpmsg_eptdev *eptdev;	struct device *dev;	int ret;	eptdev = kzalloc(sizeof(*eptdev), GFP_KERNEL);	if (!eptdev)		return -ENOMEM;	dev = &eptdev->dev;	eptdev->rpdev = rpdev;	eptdev->chinfo = chinfo;	mutex_init(&eptdev->ept_lock);	spin_lock_init(&eptdev->queue_lock);	skb_queue_head_init(&eptdev->queue);	init_waitqueue_head(&eptdev->readq);	device_initialize(dev);	dev->class = rpmsg_class;	dev->parent = &ctrldev->dev;	dev->groups = rpmsg_eptdev_groups;	dev_set_drvdata(dev, eptdev);	cdev_init(&eptdev->cdev, &rpmsg_eptdev_fops);	eptdev->cdev.owner = THIS_MODULE;	ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);	if (ret < 0)		goto free_eptdev;	dev->devt = MKDEV(MAJOR(rpmsg_major), ret);	ret = ida_simple_get(&rpmsg_ept_ida, 0, 0, GFP_KERNEL);	if (ret < 0)		goto free_minor_ida;	dev->id = ret;	dev_set_name(dev, "rpmsg%d", ret);	ret = cdev_device_add(&eptdev->cdev, &eptdev->dev);	if (ret)		goto free_ept_ida;	/* We can now rely on the release function for cleanup */	dev->release = rpmsg_eptdev_release_device;	return ret;free_ept_ida:	ida_simple_remove(&rpmsg_ept_ida, dev->id);free_minor_ida:	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));free_eptdev:	put_device(dev);	kfree(eptdev);	return ret;}
  1. kzalloc eptdev,填 rpdev、chinfo。
  2. 初始化锁、skb 队列、等待队列头。
  3. device_initialize + 设class/parent/groups/drvdata。groups = rpmsg_eptdev_groups给 sysfs 提供 name/src/dst 只读属性
  4. cdev_init 用rpmsg_eptdev_fops。
  5. 两次 ida_simple_get 拿 minor 和 ept id,dev_set_name(dev, "rpmsg%d", ret)→/dev/rpmsgN。
  6. cdev_device_add,成功后设 dev->release。
  7. 注意:此时eptdev->ept == NULL!真正的 endpoint 还没创建。端点是在 open 时才建的。这是延迟创建,避免占着地址却没人用。

错误处理路径的 goto 顺序与获取资源的顺序严格相反,是标准的 unwind 风格。注意 free_eptdev 里 put_device 后又 kfree——这里其实有个细节:device_initialize之后该用 put_device 让 release 回调释放,但因为 release 在 cdev_device_add 成功后才设置,此路径下还没设,所以 put_device 不会真的 kfree,需要手动 kfree。这正是 release 延迟赋值带来的代价。

compat_ptr_ioctl()

123456789101112131415161718192021222324252627282930313233
#ifdef CONFIG_COMPAT/** * compat_ptr_ioctl - generic implementation of .compat_ioctl file operation * * This is not normally called as a function, but instead set in struct * file_operations as * *     .compat_ioctl = compat_ptr_ioctl, * * On most architectures, the compat_ptr_ioctl() just passes all arguments * to the corresponding ->ioctl handler. The exception is arch/s390, where * compat_ptr() clears the top bit of a 32-bit pointer value, so user space * pointers to the second 2GB alias the first 2GB, as is the case for * native 32-bit s390 user space. * * The compat_ptr_ioctl() function must therefore be used only with ioctl * functions that either ignore the argument or pass a pointer to a * compatible data type. * * If any ioctl command handled by fops->unlocked_ioctl passes a plain * integer instead of a pointer, or any of the passed data types * is incompatible between 32-bit and 64-bit architectures, a proper * handler is required instead of compat_ptr_ioctl. */long compat_ptr_ioctl(struct file *file, unsigned int cmd, unsigned long arg){	if (!file->f_op->unlocked_ioctl)		return -ENOIOCTLCMD;	return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)compat_ptr(arg));}EXPORT_SYMBOL(compat_ptr_ioctl);#endif

直接使用fs/ioctl.c中的compat_ptr_ioctl实现,64 位内核兼容 32 位用户态

rpmsg_eptdev ept节点

file_operations

12345678910
static const struct file_operations rpmsg_eptdev_fops = {	.owner = THIS_MODULE,	.open = rpmsg_eptdev_open,	.release = rpmsg_eptdev_release,	.read_iter = rpmsg_eptdev_read_iter,	.write_iter = rpmsg_eptdev_write_iter,	.poll = rpmsg_eptdev_poll,	.unlocked_ioctl = rpmsg_eptdev_ioctl,	.compat_ioctl = compat_ptr_ioctl,};

rpmsg_eptdev_open()

123456789101112131415161718192021
static int rpmsg_eptdev_open(struct inode *inode, struct file *filp){	struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);	struct rpmsg_endpoint *ept;	struct rpmsg_device *rpdev = eptdev->rpdev;	struct device *dev = &eptdev->dev;	get_device(dev);	ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);	if (!ept) {		dev_err(dev, "failed to open %s\n", eptdev->chinfo.name);		put_device(dev);		return -EINVAL;	}	eptdev->ept = ept;	filp->private_data = eptdev;	return 0;}
  • rpmsg_create_ept转发到 core 再到 virtio 后端的virtio_rpmsg_create_ept→__rpmsg_create_ept(virtio_rpmsg_bus.c)。后者在 idr 里分配本地地址(若chinfo.src == RPMSG_ADDR_ANY则从 1024 起动态分配),把回调rpmsg_ept_cb和priv=eptdev绑定到该地址。
  • 回调注册后,远程发来的、dst 等于这个本地地址的消息,就会触发rpmsg_ept_cb(见后端rpmsg_recv_single按msg->dst查 idr 并调用ept->cb)。
  • get_device/ open 失败put_device:保证设备在文件打开期间不会被释放。

而 ept 的 callback 如下:

123456789101112131415161718192021
static int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len,			void *priv, u32 addr){	struct rpmsg_eptdev *eptdev = priv;	struct sk_buff *skb;	skb = alloc_skb(len, GFP_ATOMIC);	if (!skb)		return -ENOMEM;	skb_put_data(skb, buf, len);	spin_lock(&eptdev->queue_lock);	skb_queue_tail(&eptdev->queue, skb);	spin_unlock(&eptdev->queue_lock);	/* wake up any blocking processes, waiting for new data */	wake_up_interruptible(&eptdev->readq);	return 0;}
  • 后端在 virtqueue 回调(softirq 上下文)里调 ept->cb,所以这里不能用 mutex、不能睡眠,用 GFP_ATOMIC 和自旋锁。
  • 这里做了一次内存拷贝(buf → skb)。后端接收时是零拷贝拿到 vring buffer,但为了把数据交给用户态、且不让 buffer 长期被用户持有(后端要尽快把 buffer 还回 rvq),这里拷一份到 skb 里。rpmsg_recv_single调完 cb 后立刻virtqueue_add_inbuf把原 buffer 还回去。

rpmsg_eptdev_release()

1234567891011121314151617181920
static int rpmsg_eptdev_release(struct inode *inode, struct file *filp){	struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);	struct device *dev = &eptdev->dev;	/* Close the endpoint, if it's not already destroyed by the parent */	mutex_lock(&eptdev->ept_lock);	if (eptdev->ept) {		rpmsg_destroy_ept(eptdev->ept);		eptdev->ept = NULL;	}	mutex_unlock(&eptdev->ept_lock);	/* Discard all SKBs */	skb_queue_purge(&eptdev->queue);	put_device(dev);	return 0;}

当设备需要被彻底移除时(如父设备 remove 或用户态RPMSG_DESTROY_EPT_IOCTL),实际调用的是rpmsg_eptdev_destroy:

12345678910111213141516171819
static int rpmsg_eptdev_destroy(struct device *dev, void *data){	struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);	mutex_lock(&eptdev->ept_lock);	if (eptdev->ept) {		rpmsg_destroy_ept(eptdev->ept);		eptdev->ept = NULL;	}	mutex_unlock(&eptdev->ept_lock);	/* wake up any blocked readers */	wake_up_interruptible(&eptdev->readq);	cdev_device_del(&eptdev->cdev, &eptdev->dev);	put_device(&eptdev->dev);	return 0;}

rpmsg_eptdev_read_iter()

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
static ssize_t rpmsg_eptdev_read_iter(struct kiocb *iocb, struct iov_iter *to){	struct file *filp = iocb->ki_filp;	struct rpmsg_eptdev *eptdev = filp->private_data;	unsigned long flags;	struct sk_buff *skb;	int use;	if (!eptdev->ept)		return -EPIPE;	spin_lock_irqsave(&eptdev->queue_lock, flags);	/* Wait for data in the queue */	if (skb_queue_empty(&eptdev->queue)) {		spin_unlock_irqrestore(&eptdev->queue_lock, flags);		if (filp->f_flags & O_NONBLOCK)			return -EAGAIN;		/* Wait until we get data or the endpoint goes away */		if (wait_event_interruptible(eptdev->readq,					     !skb_queue_empty(&eptdev->queue) ||					     !eptdev->ept))			return -ERESTARTSYS;		/* We lost the endpoint while waiting */		if (!eptdev->ept)			return -EPIPE;		spin_lock_irqsave(&eptdev->queue_lock, flags);	}	skb = skb_dequeue(&eptdev->queue);	spin_unlock_irqrestore(&eptdev->queue_lock, flags);	if (!skb)		return -EFAULT;	use = min_t(size_t, iov_iter_count(to), skb->len);	if (copy_to_iter(skb->data, use, to) != use)		use = -EFAULT;	kfree_skb(skb);	return use;}
  • 等待条件里同时检查!ept:这样当 destroy 把 ept 置 NULL 并 wake_up 时,阻塞的读能立刻醒来返回 -EPIPE,不会永远卡死。这是把"资源消失"作为唤醒条件的标准写法。
  • 一次 read 取一条消息:rpmsg 是消息边界明确的协议,一条 skb = 一条 rpmsg 消息。use = min_t(size_t, iov_iter_count(to), skb->len);意味着如果用户 buffer 比消息小,多余部分被丢弃(skb 直接 free)。这对初学者是个坑:buffer 要足够大(后端 buffer 是 512 字节减 16 字节头 = 496 字节 payload)。
  • 用spin_lock_irqsave而非spin_lock:因为读可能在普通进程上下文,但锁可能被中断上下文的 cb 持有,关中断更安全。

rpmsg_eptdev_write_iter()

12345678910111213141516171819202122232425262728293031323334353637383940
static ssize_t rpmsg_eptdev_write_iter(struct kiocb *iocb,				       struct iov_iter *from){	struct file *filp = iocb->ki_filp;	struct rpmsg_eptdev *eptdev = filp->private_data;	size_t len = iov_iter_count(from);	void *kbuf;	int ret;	kbuf = kzalloc(len, GFP_KERNEL);	if (!kbuf)		return -ENOMEM;	if (!copy_from_iter_full(kbuf, len, from)) {		ret = -EFAULT;		goto free_kbuf;	}	if (mutex_lock_interruptible(&eptdev->ept_lock)) {		ret = -ERESTARTSYS;		goto free_kbuf;	}	if (!eptdev->ept) {		ret = -EPIPE;		goto unlock_eptdev;	}	if (filp->f_flags & O_NONBLOCK)		ret = rpmsg_trysend(eptdev->ept, kbuf, len);	else		ret = rpmsg_send(eptdev->ept, kbuf, len);unlock_eptdev:	mutex_unlock(&eptdev->ept_lock);free_kbuf:	kfree(kbuf);	return ret < 0 ? ret : len;}
  • 先copy_from_iter再拿锁,因为用户态拷贝可能缺页睡眠,不应在持有 ept_lock 时进行——否则会长时间阻塞 destroy 路径。

  • rpmsg_send阻塞版会一路走到后端rpmsg_send_offchannel_raw(..., wait=true):没 TX buffer 就wait_event_interruptible_timeout(..., 15000)。rpmsg_trysend是wait=false,没 buffer 立即-ENOMEM。

  • 成功返回 len(写入字节数),不是 ret(后端 ret 是 0)。注意单条消息受后端 buffer 限制:len > buf_size - sizeof(hdr)即 496 字节会-EMSGSIZE。

  • 发送同样是一次 write = 一条消息,没有流式拼接。

rpmsg_eptdev_poll()

1234567891011121314151617
static __poll_t rpmsg_eptdev_poll(struct file *filp, poll_table *wait){	struct rpmsg_eptdev *eptdev = filp->private_data;	__poll_t mask = 0;	if (!eptdev->ept)		return EPOLLERR;	poll_wait(filp, &eptdev->readq, wait);	if (!skb_queue_empty(&eptdev->queue))		mask |= EPOLLIN | EPOLLRDNORM;	mask |= rpmsg_poll(eptdev->ept, filp, wait);	return mask;}

rpmsg_eptdev_ioctl()

12345678910
static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,			       unsigned long arg){	struct rpmsg_eptdev *eptdev = fp->private_data;	if (cmd != RPMSG_DESTROY_EPT_IOCTL)		return -EINVAL;	return rpmsg_eptdev_destroy(&eptdev->dev, NULL);}

rpmsg_poll 转发到后端的ops->poll——但注意 virtio 后端的virtio_endpoint_ops没有实现 poll,所以rpmsg_poll里if (!ept->ops->poll) return 0,即该后端下 poll 只报可读,不报可写。这是后端可选实现。

参考资料

评论加载中…