时间轴

时间轴

2026-06-29

init


linux 5.10.238

module_init / module_exit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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 是一个字符设备驱动,层次结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
     用户空间进程
│ read/write/ioctl ← rpmsg_char.c 提供的接口
┌────────▼─────────┐
│ rpmsg_char.c │ 字符设备层:把 rpmsg 暴露成 /dev/rpmsg_ctrl* 和 /dev/rpmsg*
│ (本文件) │ 负责 endpoint 的"实例化 + 收发缓冲队列"
└────────┬─────────┘
│ rpmsg_create_ept / rpmsg_send / rpmsg_trysend / 回调 ...
┌────────▼─────────┐
│ rpmsg_core.c │ 总线核心层:bus_type、设备/驱动匹配、API 包装函数
│ │ (rpmsg_create_ept 等只是转发到 backend 的 ops)
└────────┬─────────┘
│ rpmsg_endpoint_ops / rpmsg_device_ops
┌────────▼─────────┐
│virtio_rpmsg_bus.c│ 传输后端层:基于 virtqueue 的真正收发、endpoint idr 管理、
│ │ name service、TX 缓冲分配/回收、15s 超时阻塞发送
└──────────────────┘
│ virtqueue (rx/tx)
远程处理器 (DSP/MCU/另一个核)

注意它用的是 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 如下:

1
2
3
4
5
6
7
static struct rpmsg_driver rpmsg_chrdev_driver = {
.probe = rpmsg_chrdev_probe,
.remove = rpmsg_chrdev_remove,
.drv = {
.name = "rpmsg_chrdev",
},
};

关键数据结构

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

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

struct rpmsg_ctrldev

1
2
3
4
5
6
7
8
9
10
11
/**
* struct rpmsg_ctrldev - control device for instantiating endpoint devices
* @rpdev: underlaying 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* struct rpmsg_eptdev - endpoint device context
* @dev: endpoint device
* @cdev: cdev for the endpoint device
* @rpdev: underlaying 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:releasedestroy 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_interruptibleread_iter 在队列空时 wait_event_interruptible。这是典型的"生产者-消费者 + 等待队列"。

rpmsg_driver

1
2
3
4
5
6
7
static struct rpmsg_driver rpmsg_chrdev_driver = {
.probe = rpmsg_chrdev_probe,
.remove = rpmsg_chrdev_remove,
.drv = {
.name = "rpmsg_chrdev",
},
};

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

rpmsg_chrdev_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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_chrdev_remove()

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
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 函数为

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
};

rpmsg_eptdev_create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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;
}

compat_ptr_ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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实现

rpmsg_eptdev ept节点