最近在测试某些嵌入式设备,在研究过程中需要在系统启动后替换m25p80驱动以达到特定效果。而该设备固件将m25p80直接编译到内核中,并非以LKM的形式存在,无法使用rmmod卸载。

查阅资料后发现,内核在处理spi相关驱动的时候,使用spi_register_driver函数注册m25p80驱动,并且提供spi_unregister_driver函数用于卸载驱动。进一步查看这两个函数的定义,可以发现主要参数都是一个spi_driver结构体。

1
2
int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)

查看m25p80.c的代码,其在尾部定义了spi_driver结构体,名称为m25p80_driver,并使用module_spi_driver宏注册驱动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static struct spi_driver m25p80_driver = {
.driver = {
.name = "m25p80",
.of_match_table = m25p_of_table,
},
.id_table = m25p_ids,
.probe = m25p_probe,
.remove = m25p_remove,

/* REVISIT: many of these chips have deep power-down modes, which
* should clearly be entered on suspend() to minimize power use.
* And also when they're otherwise idle...
*/
};
MODULE_LICENSE("GPL");
module_spi_driver(m25p80_driver);

进一步确定目标设备固件的/proc/kallsyms中有m25p80_driver的地址,这就是我们需要的spi_driver结构体。

在确定上述信息后,编写一个LKM,作用是调用spi_unregister_driver函数来卸载内核中的m25p80驱动,卸载后即可insmod加载我们自定义的m25p80驱动,达到效果。

LKM关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static char *m25p80_addr = NULL;
module_param(m25p80_addr, charp, 0000);
MODULE_PARM_DESC(m25p80_addr, "m25p80_driver addr");

static int __init hello_start(void)
{
unsigned long m25p80_dri;
if ( m25p80_addr != NULL && kstrtoul(m25p80_addr, 16, (unsigned long *)&m25p80_dri)==0 ) {
spi_unregister_driver((struct spi_driver *)(uintptr_t)m25p80_dri);
printk("unload m25p80 done\n");
} else {
printk("unload m25p80 failed\n");
}
return 0;
}