Linux设备驱动初始化流程

Linux设备驱动初始化的流程一直不是很清楚,今天仔细看了一下linux初始化部分的代码才真正的搞明白,记录下来。

 

        做过驱动的同学都知道,在arch/arm/目录下有和板级配置相关的文件,我使用的是Fresscale i.MX28开发板,在arch/arm/mach-mx28/目录下有mx28evk.c文件,该文件中有开发板初始化需要调用的函数。

MACHINE_START(MX28EVK, "Freescale MX28EVK board")
	.phys_io	= 0x80000000,
	.io_pg_offst	= ((0xf0000000) >> 18) & 0xfffc,
	.boot_params	= 0x40000100,
	.fixup		= fixup_board,
	.map_io		= mx28_map_io,
	.init_irq	= mx28_irq_init,
	.init_machine	= mx28evk_init_machine,
	.timer		= &mx28_timer.timer,
MACHINE_END

 

MACHINE_START的定义才发现,上面的这几行代码其实就是定义一个 machine_desc的结构体变量,并对其中部分成员赋值。

#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};


函数mx28evk_init_machine主要初始化开发板的一些gpio、特殊引脚、平台设备,并将这些平台设备添加到内核。这个函数名被赋值给init_machine这个指针。

static void __init mx28evk_init_machine(void)
{
	mx28_pinctrl_init();
	/* Init iram allocate */
#ifdef CONFIG_VECTORS_PHY_ADDR
	/* reserve the first page for irq vector table*/
	iram_init(MX28_OCRAM_PHBASE + PAGE_SIZE, MX28_OCRAM_SIZE - PAGE_SIZE);
#else
	iram_init(MX28_OCRAM_PHBASE, MX28_OCRAM_SIZE);
#endif

	mx28_gpio_init();
	mx28evk_pins_init();
	mx28_device_init();
	mx28evk_device_init();
}


我现在要关注的就是这个函数在什么地方调用,查阅源码发现在arch/arm/kernel/setup.c文件中的 setup_arch函数中对init_machine有赋值操作。仔细阅读源码

void __init setup_arch(char **cmdline_p)
{
	struct tag *tags = (struct tag *)&init_tags;
	struct machine_desc *mdesc;
	char *from = default_command_line;

	unwind_init();

	setup_processor();
	mdesc = setup_machine(machine_arch_type);
	machine_name = mdesc->name;
          *******************(省略部分代码)
	  	/*
	 * Set up various architecture-specific pointers
	 */
	init_arch_irq = mdesc->init_irq;
	system_timer = mdesc->timer;
	init_machine = mdesc->init_machine;

	early_trap_init();
}


通过setup_machine获得这个开发板对应的machine_desc结构体变量,machine_arch_type其实是一个宏,定义在include/generated/mach-types.h,这个文件是编译生成的。

#ifdef CONFIG_MACH_MX28EVK
# ifdef machine_arch_type
#  undef machine_arch_type
#  define machine_arch_type	__machine_arch_type
# else
#  define machine_arch_type	MACH_TYPE_MX28EVK
# endif
# define machine_is_mx28evk()	(machine_arch_type == MACH_TYPE_MX28EVK)
#else
# define machine_is_mx28evk()	(0)
#endif

MACH_TYPE_MX28EVK宏为

#define MACH_TYPE_MX28EVK              2531

setup_arch函数的后面将mdesc->init_machine赋值给函数指针init_machine 

static void (*init_machine)(void) __initdata;

static int __init customize_machine(void)
{
	/* customizes platform devices, or adds new ones */
	if (init_machine)
		init_machine();
	return 0;
}
arch_initcall(customize_machine);


看到这里终于知道mx28evk_init_machine函数是在哪里调用的了

但它没有被明显的调用,而是被放进arch_initcall()里面,去看看arch_initcall()是怎么定义的。在include/linux/init.h文件中有

#define arch_initcall(fn)		__define_initcall("3",fn,3)
#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn


凭着看代码的经验,customize_machine函数被放在.initcall3.init里,.initcall3.init定义在arch/arm/kernel/vmlinux.lds

 __initcall_start = .; 

*(.initcallearly.init) __early_initcall_end = .; 

*(.initcall0.init) *(.initcall0s.init) 

*(.initcall1.init) *(.initcall1s.init) 

*(.initcall2.init) *(.initcall2s.init) 

*(.initcall3.init) *(.initcall3s.init) 

*(.initcall4.init) *(.initcall4s.init) 

*(.initcall5.init) *(.initcall5s.init) 

*(.initcallrootfs.init) 

*(.initcall6.init) *(.initcall6s.init) 

*(.initcall7.init) *(.initcall7s.init) 

__initcall_end = .;

在init/main.c do_initcalls函数中被调用

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)
{
	initcall_t *fn;

	for (fn = __early_initcall_end; fn < __initcall_end; fn++)
		do_one_initcall(*fn);

	/* Make sure there is no pending stuff from the initcall sequence */
	flush_scheduled_work();
}

do_initcalls中有一个for循环,customize_machine在这里被调用,也就是说mx28evk_init_machine函数在这for循环期间被调用。mx28evk_init_machine函数执行后,platform_device全部被添加到内核中。接下来就该看看driver是在什么时候加载的。

 

还记得我们写驱动时都要使用 module_init()driver入口函数告诉内核。其实module_init是一个宏,也定义在include/linux/init.h文件中

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)	__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)		__define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn

根据上面这些代码我们知道,使用module_init()的驱动,入口函数被放在 .initcall6.init里,也是在init/main.c do_initcalls函数中被调用。相比platform_device,各种driver相对要靠后一点才会被调用。

 总结一下:设备驱动初始化的大概流程是这样

Start_kernel()----->Reset_init()------->Kernel_init()------->do_basic_setup------->do_initcalls



 

 

                        

 

Linux 杂项 (misc) 驱动是一种简单的字符设备驱动框架,主要用于将小规模的、不需要复杂功能的硬件或软件模拟模块注册到系统中。它通过 `miscdevice` 结构体简化了字符设备的创建过程。 ### 初始化流程及关键步骤 以下是 Linux Misc 驱动初始化的主要流程及相关函数: #### 1. 定义 `struct miscdevice` 在杂项设备驱动程序中,首先需要定义一个结构体实例 `struct miscdevice`。这个结构体会告诉内核该设备的信息以及相关的文件操作回调函数。 ```c struct miscdevice { int minor; // 子设备号,可以指定静态子设备号或者MISC_DYNAMIC_MINOR表示动态分配 const char *name; // 设备名称 const struct file_operations *fops;// 文件操作指针集合 void *data; }; ``` #### 2. 注册设备 使用 `misc_register()` 函数向系统注册 misc 设备: - **函数原型**: ```c int misc_register(struct miscdevice *misc); ``` - 功能:此函数会完成以下几个任务: - 如果设置为 `MISC_DYNAMIC_MINOR`,则会自动分配一个未使用的次要号码(minor number); - 将对应的 `file_operations` 指针绑定至新创建的设备节点; - 创建 `/dev/misc/<name>` 的设备节点供用户空间访问。 #### 3. 取消注册设备 当不再需要使用某个杂项设备时,应该调用 `misc_deregister()` 进行注销操作,释放占用资源并移除对应设备节点: - **函数原型** ```c int misc_deregister(struct miscdevice *misc); ``` --- ### 示例代码片段 ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/major.h> #include <linux/init.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/ioctl.h> #include <linux/miscdevice.h> static ssize_t miscdrv_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { return simple_read_from_buffer(buf, count, ppos, "Hello World\n", strlen("Hello World\n")); } static ssize_t miscdrv_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { pr_info("Write %zu bytes to device.\n", count); return count; } static const struct file_operations fops = { .owner = THIS_MODULE, .read = miscdrv_read, .write = miscdrv_write, }; static struct miscdevice my_misc_device = { MISC_DYNAMIC_MINOR,// 或者直接给定固定次设备号如240等 "my_misc_drv", &fops, }; static int __init my_init(void){ if(misc_register(&my_misc_device)){ printk(KERN_ERR"Unable to register my_misc_dev\n"); return -EBUSY; } printk(KERN_INFO"Register my_misc_dev successfully!\n"); return 0; } static void __exit my_exit(void){ misc_deregister(&my_misc_device); printk(KERN_INFO"Deregistered my_misc_dev.\n"); } module_init(my_init); module_exit(my_exit); MODULE_LICENSE("GPL"); ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值