0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

WK2XXX SPI 驱动:动态申请设备号解决双实例加载失败

jf_44130326 来源:Linux1024 作者:Linux1024 2026-04-08 08:04 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

嵌入式Linux驱动开发中,基于WK2XXX系列SPI转串口芯片的驱动开发是很常见的需求,不少开发者会遇到两个WK2XXX SPI驱动无法同时加载的问题,排查日志后发现无复杂的资源争抢,核心症结其实就藏在设备号的硬编码里。本文就从问题现象出发,剖析根本原因,并用动态申请设备号这一简单方法彻底解决,同时附上实操步骤和排查修复流程图,新手也能快速上手。

一、问题背景:双驱动加载失败,日志无复杂报错

在基于WK2XXX系列芯片的项目开发中,需要同时加载wk2xxx_spi.c和wk2xxx_spi2.c两个SPI转串口驱动,实现多串口扩展。但实际操作中,第一个驱动能正常加载,加载第二个驱动时直接失败,查看内核日志dmesg仅出现以下关键信息,无其他资源冲突报错:

[  3.752046]SPI driver wk2xxxspi0 has no spi_device_id for wkmic,wk2xxx_spi[  3.752311]spi0.0: ttysWK0 at I/O0x1 (irq =0, base_baud =691200) is a wk2xxx[  3.752669] wk02xxx_init: SPI driver for spi to Uart chip WK02XXX, etc.[  3.752673] SPI driver wk2xxxspi02 has no spi_device_id for wkmic,wk2xxx_spi02

反复排查SPI控制器、片选、中断等资源,均未发现冲突,最终定位到设备号的硬编码是导致该问题的唯一原因。

二、根本原因:Linux主设备号全局唯一,硬编码导致冲突

要理解这个问题,首先要掌握Linux字符设备号的核心机制:

Linux的字符设备号由主设备号次设备号组成,主设备号是全局唯一的,用于标识驱动类型,内核通过主设备号匹配对应的驱动程序;次设备号用于区分同一驱动类型下的不同设备实例。

在WK2XXX SPI驱动的原始代码中,开发人员对主设备号做了硬编码定义,且wk2xxx_spi.c和wk2xxx_spi2.c两个驱动使用了完全相同的主设备号宏定义:

// 两个驱动均使用该硬编码,无任何区分#defineSERIAL_WK2XXX_MAJOR  207 // 串口主设备号#defineCALLOUT_WK2XXX_MAJOR208// 呼叫主设备号

当第一个驱动加载时,会向内核申请并占用207、208这两个主设备号;第二个驱动加载时,再次向内核申请相同的主设备号,内核检测到该主设备号已被占用,会直接拒绝分配,导致驱动加载失败。

这也是本次问题的核心:无其他资源冲突,仅因主设备号硬编码重复,导致双驱动无法同时加载,因此只需修改设备号的分配方式,即可解决问题。

三、核心解决方法:弃用硬编码,动态申请主设备号

Linux内核推荐动态申请主设备号,而非硬编码指定,这也是解决本次问题的唯一方法。

动态申请设备号的优势

1.内核会自动分配未被占用的主设备号,从根本上避免全局主设备号冲突;

2.无需关注内核预留的主设备号范围,适配性更强,跨内核版本无需修改设备号;

3.多驱动、多实例场景下,无需手动区分设备号,开发更高效。

关键API说明

Linux内核提供了专门的字符设备号动态申请和释放API,本次修改仅需用到两个核心函数,无需引入其他头文件,原驱动代码已包含相关依赖:

1.动态申请:alloc_chrdev_region(&dev, minor_start, count, name)

○入参:dev(保存分配到的设备号)、minor_start(次设备号起始值)、count(申请的设备号数量)、name(驱动名称,用于标识);

○出参:成功返回0,失败返回负的错误码。

2.释放设备号:unregister_chrdev_region(dev, count)

○入参:dev(已分配的设备号)、count(申请的设备号数量);

○无返回值,用于驱动卸载时释放已申请的设备号,避免内存泄漏。

四、实操步骤:修改两个驱动代码,实现动态申请

本次修改基于原始的wk2xxx_spi.c和wk2xxx_spi2.c,两个驱动的修改逻辑完全一致,仅需保证驱动名称标识唯一即可,步骤分4步,新手也能快速完成。

步骤1:新增全局变量,保存动态分配的主设备号

在两个驱动的宏定义区下方,新增全局变量用于保存动态分配的主设备号,替代原硬编码的宏:

// 新增:保存动态分配的主设备号staticintser_major =0;// 原硬编码宏保留(无需删除,后续赋值为0即可)#defineSERIAL_WK2XXX_MAJOR  207#defineCALLOUT_WK2XXX_MAJOR208#defineMINOR_START      5#defineNR_PORTS         4    

步骤2:修改uart_driver结构体,主设备号赋值为0

原驱动中定义了uart_driver结构体,硬编码指定了major字段,需将其修改为0,告诉内核采用动态分配方式:

// wk2xxx_spi.c的uart_driver修改staticstructuart_driverwk2xxx_uart_driver = {  owner:         THIS_MODULE,  major:         0, // 关键修改:0表示动态分配主设备号  driver_name:      "ttySWK",  dev_name:       "ttysWK",  minor:         MINOR_START,  nr:           NR_PORTS,  cons:         NULL};// wk2xxx_spi2.c的uart_driver修改,仅需保证driver_name/dev_name唯一即可staticstructuart_driverwk02xxx_uart_driver = {  owner:         THIS_MODULE,  major:         0, // 关键修改:0表示动态分配主设备号  driver_name:      "ttySWK02",  dev_name:       "ttysWK02",  minor:         MINOR_START,  nr:           NR_PORTS,  cons:         NULL};

步骤3:修改驱动初始化函数,添加动态申请设备号逻辑

找到驱动的__init初始化函数(wk2xxx_init/wk02xxx_init),在注册SPI驱动前添加动态申请设备号的代码,原硬编码相关逻辑无需删除,仅需新增:

// wk2xxx_spi.c的初始化函数修改staticint__init wk2xxx_init(void){ intret;  dev_t dev;//新增:定义设备号变量  // 新增:动态申请主设备号,次设备号起始为MINOR_START,共NR_PORTS个  ret = alloc_chrdev_region(&dev, MINOR_START, NR_PORTS,"wk2xxxspi0"); if(ret < 0) {        printk(KERN_ALERT "%s: 动态申请设备号失败, ret= :%dn",__func__,ret);        return ret;    }    // 保存动态分配的主设备号    ser_major = MAJOR(dev);    printk(KERN_ALERT "%s: 动态分配主设备号为:%dn",__func__,ser_major);    // 原代码:注册SPI驱动    printk(KERN_ALERT"%s: " DRIVER_DESC "n",__func__);printk(KERN_ALERT "%s: " VERSION_DESC "n",__func__);    ret = spi_register_driver(&wk2xxx_driver);    if(ret<0){        printk(KERN_ALERT "%s,failed to init wk2xxx spi;ret= :%dn",__func__,ret);        // 申请失败后释放设备号,避免泄漏        unregister_chrdev_region(dev, NR_PORTS);    }    return ret;}// wk2xxx_spi2.c的初始化函数修改,仅需修改驱动名称为wk2xxxspi02static int __init wk02xxx_init(void){    int ret;    dev_t dev;    // 新增:动态申请设备号,驱动名称唯一    ret = alloc_chrdev_region(&dev, MINOR_START, NR_PORTS, "wk2xxxspi02");    if (ret < 0) {        printk(KERN_ALERT "%s: 动态申请设备号失败, ret= :%dn",__func__,ret);        return ret;    }    ser_major = MAJOR(dev);    printk(KERN_ALERT "%s: 动态分配主设备号为:%dn",__func__,ser_major);    // 原代码    printk(KERN_ALERT"%s: " DRIVER_DESC "n",__func__);printk(KERN_ALERT "%s: " VERSION_DESC "n",__func__);    ret = spi_register_driver(&wk02xxx_driver);    if(ret<0){        printk(KERN_ALERT "%s,failed to init wk2xxx spi;ret= :%dn",__func__,ret);        unregister_chrdev_region(dev, NR_PORTS);    }    return ret;}

步骤4:修改驱动退出函数,添加释放设备号逻辑

找到驱动的__exit退出函数(wk2xxx_exit/wk02xxx_exit),在注销SPI驱动后添加释放设备号的代码,保证资源正常回收:

// wk2xxx_spi.c的退出函数修改staticvoid__exitwk2xxx_exit(void){ printk(KERN_ALERT"%s!!--in--n", __func__); // 原代码:注销SPI驱动 spi_unregister_driver(&wk2xxx_driver); // 新增:释放动态分配的设备号 if(ser_major >0) {   unregister_chrdev_region(MKDEV(ser_major,MINOR_START),NR_PORTS);   printk(KERN_ALERT"%s: 释放主设备号:%dn",__func__,ser_major);  }}// wk2xxx_spi2.c的退出函数修改staticvoid__exitwk02xxx_exit(void){ printk(KERN_ALERT"%s!!--in--n", __func__); // 原代码 spi_unregister_driver(&wk02xxx_driver); // 新增:释放设备号 if(ser_major >0) {   unregister_chrdev_region(MKDEV(ser_major,MINOR_START),NR_PORTS);   printk(KERN_ALERT"%s: 释放主设备号:%dn",__func__,ser_major);  }}

关键注意点

两个驱动的修改仅需保证动态申请设备号的驱动名称(alloc_chrdev_region的第四个参数)和uart_driver的driver_name/dev_name唯一即可,其余逻辑完全一致,无需做额外修改。

五、WK2XXX双驱动加载失败排查&修复流程图

为了方便大家在实际开发中快速排查同类问题,整理了专属流程图,从问题发现到功能验证,一步到位,可直接收藏备用:

wKgZPGnVm6-AZ_jAAANmpxJIX8E417.png

六、验证:三步确认驱动加载正常

修改代码并编译出.ko驱动模块后,依次加载两个驱动,通过以下三步验证是否修复成功,操作简单且高效。

步骤1:查看动态分配的主设备号

执行命令cat /proc/devices,查看字符设备列表,能看到两个驱动被分配了不同的主设备号,说明设备号申请成功:

# 示例输出,实际主设备号由内核分配Characterdevices: ...245ttySWK   # wk2xxx_spi.c的驱动246ttySWK02  # wk2xxx_spi2.c的驱动 ...

步骤2:查看/dev下的设备节点

执行命令ls /dev/ttysWK*,能看到两个驱动对应的设备节点均成功创建,无重复:

/dev/ttysWK0 /dev/ttysWK1 /dev/ttysWK2 /dev/ttysWK3 /dev/ttysWK020 /dev/ttysWK021 /dev/ttysWK022 /dev/ttysWK023

步骤3:串口功能测试

通过minicom/screen等工具分别操作两个驱动的串口节点,向串口发送/接收数据,验证通信正常,说明双驱动已实现同时加载且功能正常。

七、拓展知识点:Linux主设备号的那些事

1.主设备号范围:Linux内核中主设备号的取值范围是1255,其中1127是内核预留的主设备号,128~255是用户自定义的主设备号,硬编码时建议使用128以后的数值,但仍有冲突风险;

2.硬编码的弊端:除了本次的多驱动冲突,硬编码主设备号还会导致跨内核版本适配失败(部分内核版本会调整预留主设备号),以及与其他第三方驱动冲突;

3.动态申请的适配性:动态申请主设备号时,内核会从未被占用的数值中自动分配,无需关注内核版本和其他驱动,是嵌入式Linux驱动开发的最佳实践。

八、总结

本次WK2XXX SPI双驱动无法同时加载的问题,是嵌入式Linux驱动开发中典型的全局资源硬编码冲突问题,核心仅因主设备号硬编码重复,无需排查复杂的SPI、中断、IO资源,仅通过动态申请主设备号这一招即可彻底解决。

同时也给我们一个开发提醒:在嵌入式Linux驱动开发中,尤其是多驱动、多实例的场景下,应尽量避免硬编码全局唯一的资源(如主设备号、IO地址、中断号),遵循Linux内核的动态分配原则,既能避免资源冲突,又能提升驱动的适配性和可移植性。

本次的修改方法不仅适用于WK2XXX SPI驱动,也适用于其他Linux字符设备驱动的多实例加载场景,可直接复用!

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    88

    文章

    11807

    浏览量

    219508
  • 驱动开发
    +关注

    关注

    0

    文章

    141

    浏览量

    12715
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    PIC18F2XXX/4XXX系列闪存微控制器编程规范解析

    PIC18F2XXX/4XXX系列闪存微控制器编程规范解析 一、引言 今天我们来深入探讨PIC18F2XXX/4XXX系列闪存微控制器的编程规范。这个系列包含了众多型号的
    的头像 发表于 04-08 10:05 328次阅读

    SGM42541H桥驱动IC:自动化设备的得力助手

    SGM42541H桥驱动IC:自动化设备的得力助手 在电子设备的自动化定位和运动控制领域,电机驱动IC起着至关重要的作用。今天,我们就来深
    的头像 发表于 03-26 11:15 177次阅读

    Microchip PIC18F2XXX/4XXX 系列闪存微控制器编程指南

    Microchip PIC18F2XXX/4XXX 系列闪存微控制器编程指南 在电子设计领域,微控制器的编程是一项关键工作。今天,我们就来深入探讨一下 Microchip 的 PIC18F2XXX
    的头像 发表于 02-09 16:50 1083次阅读

    深入解析Rockchip SFC驱动SPI Flash传输流程与问题排查指南

    、关键机制,并给出实用的问题排查方案,助力开发者快速定位问题。 一、SFC驱动核心功能概览 Rockchip SFC驱动是Linux内核级SPI内存设备
    的头像 发表于 02-04 07:13 776次阅读
    深入解析Rockchip SFC<b class='flag-5'>驱动</b>:<b class='flag-5'>SPI</b> Flash传输流程与问题排查指南

    【书籍评测活动NO.67】成为硬核Linux开发者:《Linux 设备驱动开发(第 2 版)》

    设备驱动实例,涵盖设备管理、核心数据结构填充及用户与内核空间数据交互,帮助读者完成从理论到简单驱动
    发表于 11-17 17:52

    CherryUSB怎样实现U盘动态加载

    USB线时,再动态地卸载U盘并挂载文件系统。自己偿试在CherryUSB的事件回调usbd_event_handler中完成以上操作,但没成功,相关代码执行不到。是CherryUSB不支持动态加载
    发表于 10-14 07:31

    基于RT-Thread的EK-RA2E2 设备驱动移植与应用 | 技术集结

    目录前言环境配置HelloRT-ThreadGPIO输入与中断I2C主机驱动SPI主机驱动ADC设备驱动
    的头像 发表于 10-05 10:06 6321次阅读
    基于RT-Thread的EK-RA<b class='flag-5'>2E2</b> <b class='flag-5'>设备</b><b class='flag-5'>驱动</b>移植与应用 | 技术集结

    使用NRF24l01软件包注册设备失败怎么解决?

    1.根据软件包提示第一步先打开了SPI驱动框架 2.然后软件包添加了 nrf24l01最新版本以及demo、debug 3.在board.h #define using_spi1 在
    发表于 09-23 06:51

    RK3588 PCIe设备识别失败?一招避坑“非法Class”陷阱

    前言:在RK3588平台开发过程中,你是否遇到过这样的窘境:明明PCIe总线上挂好了网卡模块,lspci能识别到芯片,可驱动就是加载失败,排查半天找不到关键问题?别慌!本文将带你一步步解决这个棘手
    的头像 发表于 08-29 08:32 2053次阅读
    RK3588 PCIe<b class='flag-5'>设备</b>识别<b class='flag-5'>失败</b>?一招避坑“非法Class”陷阱

    CX3无法将固件加载SPI闪存如何解决?

    我无法将固件加载SPI 闪存。 步骤如下: 1. 开机,运行 USB 控制中心, 2.点击boot loader,点击FX3,然后选择“SPI flash”。然后USB控制中心显示
    发表于 07-16 07:37

    USB设备设备ID按照什么逻辑进行分配?

    这样分配的。 我想要多个USB设备先插入时,第一个设备ID应该为0,第二个设备ID应该为1,以此类推。 问题1: 请问,是否有什么方法,
    发表于 07-16 06:29

    求助,关于55513 Linux驱动程序问题求解

    :0001:1: cypress/cyfmac55500-sdio.t.fsl、imx6ul-14x14-evkrxse 的直接固件加载失败,错误为 -2 [110.253088]usbcore:注册
    发表于 07-09 08:02

    请问CCyUSBDevice如何同时实例2个?

    CYAPI编程手册中的解释,CCyUSBDevice实例化后是连接到了cyusb driver驱动上,它能查找实例中的多台USB设备。现在的问题是假如我有
    发表于 05-19 07:27

    如何使用EZUSB-CX3实现阶段引导加载程序?

    我对如何使用 EZUSB-CX3 实现阶段引导加载程序有点困惑。我想要的是,当有新的 cx3 映像时,我希望能够从 cx3 固件引导到第二阶段引导加载程序,然后将新映像刷新到 SPI
    发表于 05-12 08:26

    USB 3.0CX3中的辅助引导加载程序后无法识别怎么解决?

    我正在为CYPRESS™ CX3 (FX3) 开发辅助引导加载程序 (SBL),它从 SPI Flash 加载应用程序并执行它。 但是,通过 SBL 启动应用程序时,USB 3.0 枚举失败
    发表于 05-06 08:39