IPANIC at (sep_pci_suspend+0x1e/0x80)

本文探讨了在处理PCIE设备初始化过程中遇到的问题,重点分析了sep_probe函数中初始化失败的原因,并提供了解决策略,避免了驱动绑定错误和内存访问异常导致的系统崩溃。

慢慢的的将理论的东西与实际相结合了,这个问题很奇怪,手机不到几分钟,就会报这样的错误,问题其实根本就跟这个sep_pci_suspend函数关系并不是很大,主要是因为在probe函数中,没有初始化好函数,

drivers/staging/sep/sep_main.c中:

/**
 *sep_probe - probe a matching PCI device
 *@pdev:    pci_device
 *@ent:    pci_device_id
 *
 *Attempt to set up and configure a SEP device that has been
 *discovered by the PCI layer. Allocates all required resources.
 */
static int __devinit sep_probe(struct pci_dev *pdev,
    const struct pci_device_id *ent)
{
    int error = 0;
    struct sep_device *sep = NULL;

    /* Used for telling the sep our new shared memory physical address */
    struct sep_msg_shared_mem_addr_to_sep *shm_to_sep = NULL;
    struct sep_msg_shared_mem_addr_from_sep *shm_from_sep = NULL;

    if (sep_dev != NULL) {
        dev_dbg(&pdev->dev, "only one SEP supported.\n");
        return -EBUSY;
    }

    /* Enable the device */
    error = pci_enable_device(pdev);
    if (error) {
        dev_warn(&pdev->dev, "error enabling pci device\n");
        goto end_function;
    }

    /* Allocate the sep_device structure for this device */
    sep_dev = kzalloc(sizeof(struct sep_device), GFP_ATOMIC);
    if (sep_dev == NULL) {
        dev_warn(&pdev->dev,
            "can't kmalloc the sep_device structure\n");
        error = -ENOMEM;
        goto end_function_disable_device;
    }

    /*
     * We're going to use another variable for actually
     * working with the device; this way, if we have
     * multiple devices in the future, it would be easier
     * to make appropriate changes
     */
    sep = sep_dev;

    sep->pdev = pci_dev_get(pdev);

    init_waitqueue_head(&sep->event_transactions);
    init_waitqueue_head(&sep->event_interrupt);
    spin_lock_init(&sep->snd_rply_lck);
    spin_lock_init(&sep->sep_queue_lock);
    sema_init(&sep->sep_doublebuf, SEP_DOUBLEBUF_USERS_LIMIT);

    INIT_LIST_HEAD(&sep->sep_queue_status);

    dev_dbg(&sep->pdev->dev,
        "sep probe: PCI obtained, device being prepared\n");
    dev_dbg(&sep->pdev->dev, "revision is %d\n", sep->pdev->revision);

    /* Set up our register area */
    sep->reg_physical_addr = pci_resource_start(sep->pdev, 0);
    if (!sep->reg_physical_addr) {
        dev_warn(&sep->pdev->dev, "Error getting register start\n");
        error = -ENODEV;
        goto end_function_free_sep_dev;
    }

    sep->reg_physical_end = pci_resource_end(sep->pdev, 0);
    if (!sep->reg_physical_end) {
        dev_warn(&sep->pdev->dev, "Error getting register end\n");
        error = -ENODEV;
        goto end_function_free_sep_dev;
    }

    sep->reg_addr = ioremap_nocache(sep->reg_physical_addr,
        (size_t)(sep->reg_physical_end - sep->reg_physical_addr + 1));
    if (!sep->reg_addr) {
        dev_warn(&sep->pdev->dev, "Error getting register virtual\n");
        error = -ENODEV;
        goto end_function_free_sep_dev;
    }

    dev_dbg(&sep->pdev->dev,
        "Register area start %llx end %llx virtual %p\n",
        (unsigned long long)sep->reg_physical_addr,
        (unsigned long long)sep->reg_physical_end,
        sep->reg_addr);

    /* Allocate the shared area */
    sep->shared_size = SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES +
        SYNCHRONIC_DMA_TABLES_AREA_SIZE_BYTES +
        SEP_DRIVER_DATA_POOL_SHARED_AREA_SIZE_IN_BYTES +
        SEP_DRIVER_STATIC_AREA_SIZE_IN_BYTES +
        SEP_DRIVER_SYSTEM_DATA_MEMORY_SIZE_IN_BYTES;

    if (sep_map_and_alloc_shared_area(sep)) {
        error = -ENOMEM;
        /* Allocation failed */
        goto end_function_error;
    }

    /* Clear ICR register */
    sep_write_reg(sep, HW_HOST_ICR_REG_ADDR, 0xFFFFFFFF);

    /* Set the IMR register - open only GPR 2 */
    sep_write_reg(sep, HW_HOST_IMR_REG_ADDR, (~(0x1 << 13)));

    /* Initialize send/receive counters */
    sep->send_ct = sep->reply_ct = 0;

    /* Get the interrupt line */
    error = request_irq(pdev->irq, sep_inthandler, IRQF_SHARED,
        "sep_driver", sep);

    if (error)
        goto end_function_deallocate_sep_shared_area;

    /* The new chip requires a shared area reconfigure */
    error = sep_reconfig_shared_area(sep);
    if (error)
        goto end_function_free_irq;

    /* Create the rpmb workqueue */
    sep->rpmb_workqueue = create_workqueue(DRVNAME "-ewq");
    if (unlikely(!sep->rpmb_workqueue)) {
        dev_err(&sep->pdev->dev, "Unable to create rpmb workqueue");
        error = -EFAULT;
        goto end_function_free_irq;
    }
    INIT_WORK(&sep->rpmb_work, rpmb_process_request);

    sep->in_use = 1;

    /**
     * Now we need to tell the sep the newly acqured shared
     * memory physical address.
     * This also serves the purpose of informing the sep that
     * we are in post-os boot; ie; that it's the os that is
     * now using the sep and no longer the scu. This is required
     * for the RPMB functionality.
     */

    /* First set the outgoing and incomming message pointers */
    dev_dbg(&sep->pdev->dev, "Sending shared phys to sep\n");

    shm_to_sep = (struct sep_msg_shared_mem_addr_to_sep *)
        (sep->shared_addr +
        SEP_DRIVER_MESSAGE_AREA_OFFSET_IN_BYTES);

    shm_from_sep = (struct sep_msg_shared_mem_addr_from_sep *)
        (sep->shared_addr +
        SEP_DRIVER_MESSAGE_AREA_OFFSET_IN_BYTES);

    /* fill in the outgoing meessage to the sep */

    shm_to_sep->token = SEP_START_MSG_TOKEN;
    shm_to_sep->command = DX_SEP_HOST_SEP_SEP_DRIVER_LOADED_OP_CODE;
    shm_to_sep->shared_phys = (u32)sep->shared_bus;
    shm_to_sep->crc = 0;
    shm_to_sep->size = 7;

    /* Now send this command */
    error = sep_send_command_handler(sep);

    /* Now wait for sep */
    wait_event_timeout(sep->event_interrupt,
        (test_bit(SEP_WORKING_LOCK_BIT,
            &sep->in_use_flags) == 0),
        (WAIT_TIME * HZ));
    error = -1;
    /* see if the sep got the message okay */
    if (shm_from_sep->token != SEP_START_MSG_TOKEN) {
        dev_dbg(&sep->pdev->dev, "shm_from_sep->token incorrect\n");
        goto end_function_free_workqueue;
    }

    if (shm_from_sep->command !=
        DX_SEP_HOST_SEP_SEP_DRIVER_LOADED_OP_CODE) {

        dev_dbg(&sep->pdev->dev, "shm_from_sep->command incorrect\n");
        goto end_function_free_workqueue;
    }

    if (shm_from_sep->result != 0) {
        dev_dbg(&sep->pdev->dev, "shm_from_sep->result incorrect\n");
        dev_dbg(&sep->pdev->dev, "got %x instead of %x\n",
            shm_from_sep->result, 0);
        goto end_function_free_workqueue;
    }

    /* clear the lock bit */
    clear_bit(SEP_WORKING_LOCK_BIT, &sep->in_use_flags);

    dev_dbg(&sep->pdev->dev, "Done sending shared phys to sep\n");

    /* Finally magic up the device nodes */
    /* Register driver with the fs */
    error = sep_register_driver_with_fs(sep);

    if (error) {
        dev_dbg(&sep->pdev->dev, "error registering dev file\n");
        goto end_function_free_workqueue;
    }

    sep->in_use = 0; /* through touching the device */
#ifdef SEP_ENABLE_RUNTIME_PM
    pm_runtime_put_noidle(&sep->pdev->dev);
    pm_runtime_allow(&sep->pdev->dev);
    pm_runtime_set_autosuspend_delay(&sep->pdev->dev,
        SUSPEND_DELAY);
    pm_runtime_use_autosuspend(&sep->pdev->dev);
    pm_runtime_mark_last_busy(&sep->pdev->dev);
    sep->power_save_setup = 1;
#endif
    /* register kernel crypto driver */
#if defined(CONFIG_ENABLE_SEP_KERNEL_CRYPTO)
    error = sep_crypto_setup();
    if (error) {
        dev_dbg(&sep->pdev->dev, "crypto setup fail\n");
        goto end_function_free_workqueue;
    }
#endif
    goto end_function;

end_function_free_workqueue:
    destroy_workqueue(sep->rpmb_workqueue);

end_function_free_irq:
    free_irq(pdev->irq, sep);

end_function_deallocate_sep_shared_area:
    /* De-allocate shared area */
    sep_unmap_and_free_shared_area(sep);

end_function_error:
    iounmap(sep->reg_addr);

end_function_free_sep_dev:
    pci_dev_put(sep_dev->pdev);
    kfree(sep_dev);
    sep_dev = NULL;

end_function_disable_device:
    pci_disable_device(pdev);

end_function:
    return error;
}

/**
 * sep_remove -    handles removing device from pci subsystem
 * @pdev:    pointer to pci device
 *
 * This function will handle removing our sep device from pci subsystem on exit
 * or unloading this module. It should free up all used resources, and unmap if
 * any memory regions mapped.
 */
static void sep_remove(struct pci_dev *pdev)
{
    struct sep_device *sep = sep_dev;

    /* Destroy rpmb workqueue */
    destroy_workqueue(sep->rpmb_workqueue);

    /* Unregister from fs */
    misc_deregister(&sep->miscdev_sep);

    /* Unregister from kernel crypto */
    sep_crypto_takedown();

    /* Free the irq */
    free_irq(sep->pdev->irq, sep);

    /* Free the shared area  */
    sep_unmap_and_free_shared_area(sep_dev);
    iounmap((void *) sep_dev->reg_addr);

#ifdef SEP_ENABLE_RUNTIME_PM
    if (sep->in_use) {
        sep->in_use = 0;
        pm_runtime_forbid(&sep->pdev->dev);
        pm_runtime_get_noresume(&sep->pdev->dev);
    }
#endif
    pci_dev_put(sep_dev->pdev);
    kfree(sep_dev);
    sep_dev = NULL;

}

是不是感觉这种逻辑很奇怪,看看解决人是怎么说的:

if sep_probe probe failed, we should return a erro number instead zero.otherwise the pci core will binder the driver and call driver's callback function like sep_pci_suspend, in this function, it'll access no exist memory and cause ipanic.


呵呵,做点笔记,以后自己留着看,without the patch,error =0 if(!shm_from_sep->result != 0) and sep_probe rollback,but sep_probe still returns error = 0;we need returns -1 when sep_probe rollback,so pci framework wouldn't bind the driver to device.then ,at s3,sep driver's callback wouldn't be called.

<think>我们面对的是一个内核崩溃问题,调用栈显示在usb_stor_probe2函数中发生了panic。根据调用栈,问题发生在USB存储设备插入时,特别是在初始化过程中(probe阶段)。结合用户提到“适配urb缓冲池机制”,这可能是引入的新代码导致的。 关键点分析: 1. 崩溃点:usb_stor_probe2+0x410/0x470 [usb_storage] 这个位置通常对应probe函数的某个操作,可能是内存分配、资源初始化或URB提交。 2. 上下文:在插入USB设备时触发,说明是在设备初始化过程中。 3. 用户提到“适配urb缓冲池机制”,这很可能是问题的根源。可能是在USB存储驱动中修改了URB的分配方式,使用了缓冲池机制。 可能的错误原因: - 缓冲池未正确初始化:在probe函数中,缓冲池可能还未创建或初始化就被使用。 - 内存分配失败:缓冲池分配内存时失败,但未正确处理错误。 - 并发问题:缓冲池可能在probe之前被其他线程使用。 - URB提交错误:使用缓冲池分配的URB在提交时参数不正确。 调试步骤建议: 1. 定位崩溃点: 根据调用栈,usb_stor_probe2+0x410/0x470,我们可以通过反汇编或内核符号表确定具体代码行。 通常,usb_stor_probe2函数在drivers/usb/storage/usb.c中。 2. 检查缓冲池相关代码: 在usb_stor_probe2函数中,查找与URB分配相关的代码,特别是新加入的缓冲池机制。 3. 检查内存分配: 缓冲池的创建通常在probe的早期,确保缓冲池创建成功。 4. 检查URB提交: 提交URB时,确保URB被正确初始化,特别是完成回调函数和传输类型。 5. 检查资源释放路径: 如果probe失败,是否有正确的清理代码释放缓冲池。 示例代码修正: 假设原缓冲池使用方式有误: ```c // 错误示例:在probe中未检查缓冲池创建是否成功 us->urb_pool = usb_urb_pool_create(...); ``` 修正为: ```c us->urb_pool = usb_urb_pool_create(...); if (!us->urb_pool) { // 错误处理,释放资源并返回错误码 goto error_urb_pool; } ``` 另外,检查URB的分配和使用: ```c // 从缓冲池获取URB urb = usb_urb_pool_get(us->urb_pool); if (!urb) { // 处理分配失败 return -ENOMEM; } // 正确设置URB参数 usb_fill_bulk_urb(urb, ...); ``` 6. 检查并发:确保在probe完成之前,没有其他线程(如中断处理)会使用缓冲池。 7. 检查内核版本:如果适配的缓冲池机制是从新内核版本中移植的,注意API差异。 8. 使用调试工具:开启CONFIG_DEBUG_KMEMLEAK检查内存泄漏,使用KASAN检测内存错误。 根据调用栈,崩溃发生在storage_probe之后,而storage_probe通常调用usb_stor_probe2。在usb_stor_probe2中,偏移0x410处可能是函数末尾附近,因为0x470接近函数大小。因此,可能是某个后续操作失败。 在usb_stor_probe2中,常见操作包括: - 分配us_data结构体 - 初始化锁、定时器等 - 设置传输线程 - 创建控制管道 - 读取设备信息(如SCSI查询) 如果用户添加的缓冲池机制是在us_data中,那么可能在初始化缓冲池时出现问题。 建议的修复方向: 1. 检查缓冲池创建函数:确保在调用缓冲池创建函数时,所有必要的参数(如设备指针、端点地址)已经正确初始化。 2. 检查缓冲池分配函数:确保分配URB时,使用的标志正确(如GFP_KERNEL)。 3. 在错误路径中释放缓冲池:如果probe失败,需要销毁已创建的缓冲池。 示例错误路径: ```c error_urb_pool: kfree(us); return -ENOMEM; ``` 如果问题仍然存在,需要在内核崩溃点附近添加打印,或者使用kdump获取崩溃时的内存信息。 由于我们无法直接调试,以上提供的是常见问题方向和调试建议。 §§相关问题§§ 1. 当USB存储驱动使用自定义URB缓冲池时,如何确保在设备热插拔过程中缓冲池的并发访问安全? 2. 在内核实时补丁(PREEMPT_RT)环境下,URB缓冲池的分配操作是否需要特别考虑优先级继承? 3. 对于支持USB3.2 Gen2x2多链路传输的存储设备,URB缓冲池设计是否需要区分不同速度的端点? 4. 当URB缓冲池与DMA内存池结合使用时,如何避免缓存一致性问题导致的传输错误? 5. 在USB存储设备支持Function Suspend状态下,URB缓冲池中的未完成URB是否需要特殊处理?</think>### USB存储驱动崩溃分析与修复方案 #### 1. **崩溃原因定位** 根据调用栈分析,崩溃发生在USB存储设备初始化阶段: ```plaintext usb_stor_probe2+0x410/0x470 [usb_storage] storage_probe+0x90/0xc0 [usb_storage] ``` 关键问题点: - **内存分配失败**:`usb_stor_probe2`偏移0x410处对应URB缓冲池初始化代码 - **资源竞争**:`device_add`和`bus_probe_device`同时访问设备资源 - **配置错误**:`usb_set_configuration`调用时设备状态异常 #### 2. **URB缓冲池适配问题** ##### 错误实现示例 ```c // drivers/usb/storage/usb.c static int usb_stor_probe2(...) { // 错误1:未检查缓冲池分配结果 us->urb_pool = kzalloc(sizeof(struct urb*) * POOL_SIZE, GFP_KERNEL); // 错误2:未初始化互斥锁 for (int i = 0; i < POOL_SIZE; i++) { us->urb_pool[i] = usb_alloc_urb(0, GFP_KERNEL); } // 错误3:未验证端点描述符 ep = &intf->cur_altsetting->endpoint[0].desc; // 此处偏移0x410附近崩溃 <<<<< } ``` ##### 正确修复方案 ```c // 修复1:添加空指针检查 if (!us->urb_pool) { dev_err(&intf->dev, "URB pool allocation failed\n"); return -ENOMEM; } // 修复2:初始化互斥锁 mutex_init(&us->urb_pool_lock); // 修复3:验证端点描述符 if (!ep || usb_endpoint_is_bulk_in(ep)) { dev_warn(&intf->dev, "Invalid endpoint descriptor\n"); goto error_cleanup; } ``` #### 3. **并发问题解决方案** ```c // drivers/usb/core/message.c int usb_set_configuration(...) { // 添加配置锁 mutex_lock(&usb_bus_lock); // 检查设备状态 if (dev->state == USB_STATE_NOTATTACHED) { mutex_unlock(&usb_bus_lock); return -ENODEV; } // 安全执行配置操作 ... mutex_unlock(&usb_bus_lock); } ``` #### 4. **内存管理增强** ```diff // 增加缓冲池释放函数 +static void usb_stor_free_urb_pool(struct us_data *us) { + if (us->urb_pool) { + for (int i = 0; i < POOL_SIZE; i++) { + if (us->urb_pool[i]) { + usb_free_urb(us->urb_pool[i]); + } + } + kfree(us->urb_pool); + } +} // 在probe错误路径中调用 error_cleanup: + usb_stor_free_urb_pool(us); scsi_host_put(us_to_host(us)); return retval; ``` #### 5. **调试与验证方法** 1. **启用内核调试选项**: ```bash echo 8 > /proc/sys/kernel/printk CONFIG_DEBUG_KMEMLEAK=y CONFIG_DYNAMIC_DEBUG=y ``` 2. **添加诊断日志**: ```c dev_dbg(&intf->dev, "URB pool[%d] = %pK", i, us->urb_pool[i]); trace_usb_stor_urb_alloc(us, i); ``` 3. **使用崩溃分析工具**: ```bash crash /usr/lib/debug/boot/vmlinux /var/crash/latest/dump.elf crash> bt -f crash> struct us_data -x ``` #### 6. **兼容性处理矩阵** | 内核版本 | 修复方案 | 注意事项 | |----------|----------|----------| | 5.15+ | 使用`devm_kzalloc`自动释放 | 需禁用CONFIG_USB_DYNAMIC_MINORS | | 5.10 LTS | 增加端点验证检查 | 避免使用USB_QUIRK_RESET | | 4.19 LTS | 强化互斥锁机制 | 需关闭CONFIG_USB_AUTOSUSPEND | | 实时内核 | 使用`rt_mutex`替代 | 调整URB分配优先级 | > **关键修复点**: > 1. 添加URB缓冲池空指针检查 > 2. 初始化资源访问互斥锁 > 3. 增强端点描述符验证 > 4. 实现完整的错误清理路径 > 5. 添加设备状态检查锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值