USB热插拔原理及实现(HotPlug)

目录

🍅点击这里查看所有博文

随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。

想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。

很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。

同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。

既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。

本系列博客所述资料均来自互联网,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

简介

热插拔是一种允许在设备运行时安全地插拔硬件设备的技术。这种技术广泛应用于现代计算机和嵌入式系统中,使用户能够在不关闭系统的情况下添加或移除设备,从而提高了系统的灵活性和可用性。

热插拔技术的核心在于确保在设备连接或断开时,系统能够实时识别、初始化和管理这些设备,避免数据丢失和系统崩溃。

随着技术的发展,热插拔通讯在多个领域得到了广泛应用,例如服务器、存储设备和外设等。其中,USB(通用串行总线)技术是热插拔通讯的一个典型例子。USB的设计允许用户在计算机运行时方便地连接和断开各种设备,如鼠标、键盘、打印机和存储设备等。

USB热插拔的广泛应用不仅提升了用户体验,还促进了外设的多样化和普及,使得各种设备能够轻松地与计算机系统进行交互。通过USB接口,用户可以快速而方便地扩展和升级他们的计算机系统,充分利用现代技术带来的便利。

USB热插拔原理

在硬件层面上,USB热插拔需要做到以下两点:

当USB设备插入主机时,操作系统能够自动识别该设备,并加载相应的驱动程序,确保设备能够立即使用。当USB设备从主机移除时,系统会安全地卸载该设备,确保数据一致性和系统稳定性。

在USB集线器的每个下游端口的D+和D-上,分别接了一个15k的下拉电阻到地。这样,当集线器的端口悬空(即没有设备插入)时,输入端就被这两个下拉电阻拉到了低电平。

在USB设备端,在D+或者D-上接了一个1.5k的上拉电阻到3.3V的电源。1.5k的上拉电阻是接在D+还是D-上,由设备的速度来决定。

对于低速设备,其上拉电阻是接在D-上的。

而全速设备和高速设备,上拉电阻是接在D+上的。 高速设备首先会被检测为全速设备,然后通过被称为chirp sequence(线性调频序列)的总线握手机制来检测是否具有高速能力。

我们可以这样记忆:速度快的,上拉电阻接正的;速度慢的,上拉电阻接负的。

一个USB设备连接到主机后,大概分为以下几个阶段:

VBUS中断

对于设备端实现USB热插拔,VBUS的状态显得额外重要。接入主机时会检测VBUS为高,从而触发枚举。断开连接时检测VBUS为低,从而释放资源。

以sdx7x平台为例,硬件上将vbus连接到pmx75上,设备树配置了interrupt-names为usb_vbus的中断。

pmx75_vbus_detect: qcom,pmd-vbus-det@1500 {

compatible = "qcom,pm8941-misc";

reg = <0x1500>;

interrupts = <0x1 0x15 0x0 IRQ_TYPE_EDGE_BOTH>;

interrupt-names = "usb_vbus";

status = "disabled";

};

在QCOM USB ID extcon driver注册时将会读取usb_vbus的中断号,保存到info->vbus_irq变量中,随后将中断注册到qcom_usb_irq_handler函数处理。

由于vbus中断本质上是一个gpio中断,连接和断开时可能会出现抖动,导致多次触发,所以软件层面需要做防抖操作。

在驱动注册时还通过devm_delayed_work_autocancel配置了一个延迟工作(delayed work),使得在条件满足后指定的时间后执行qcom_usb_extcon_detect_cable函数。如果设备在工作执行前被释放,则会自动取消这个工作。这有助于管理资源,避免内存泄漏或对已释放设备的访问。

static int qcom_usb_extcon_probe(struct platform_device *pdev)

{

xxxxxxx

ret = devm_delayed_work_autocancel(dev, &info->wq_detcable,

qcom_usb_extcon_detect_cable);

if (ret)

return ret;

xxxxxxx

info->vbus_irq = platform_get_irq_byname(pdev, "usb_vbus");

if (info->vbus_irq > 0) {

ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,

qcom_usb_irq_handler,

IRQF_TRIGGER_RISING |

IRQF_TRIGGER_FALLING | IRQF_ONESHOT,

pdev->name, info);

if (ret < 0) {

dev_err(dev, "failed to request handler for VBUS IRQ\n");

return ret;

}

}

xxxxxxx

}

vbus的中断处理回调函数qcom_usb_irq_handler的动作就很简单了,只是激活前面提到的延时工作。

static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)

{

struct qcom_usb_extcon_info *info = dev_id;

queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,

info->debounce_jiffies);

return IRQ_HANDLED;

}

在延时工作中核心动作就是调extcon_set_state_sync更新外部连接器EXTCON_USB事件的状态。这个状态将会在DWC3的驱动程序中被使用到。

static void qcom_usb_extcon_detect_cable(struct work_struct *work)

{

xxxxxxxx

if (info->vbus_irq > 0) {

/* check VBUS and update cable state */

ret = irq_get_irqchip_state(info->vbus_irq,

IRQCHIP_STATE_LINE_LEVEL, &state);

if (ret)

return;

if (state) {

val.intval = true;

extcon_set_property(info->edev, EXTCON_USB,

EXTCON_PROP_USB_SS, val);

}

extcon_set_state_sync(info->edev, EXTCON_USB, state);

}

}

USB控制器

在DWC3的驱动中注册了EXTCON_USB事件回调函数为dwc3_msm_vbus_notifier。

static int dwc3_msm_extcon_register(struct dwc3_msm *mdwc)

{

xxxxxxx

mdwc->extcon[idx].vbus_nb.notifier_call =

dwc3_msm_vbus_notifier;

ret = extcon_register_notifier(edev, EXTCON_USB,

&mdwc->extcon[idx].vbus_nb);

if (ret < 0)

check_vbus_state = false;

xxxxxxx

}

当vbus状态发生变化时,会调用到该函数。关键的调用栈如下:

dwc3_msm_vbus_notifier

--dwc3_override_vbus_status

最后会在dwc3_override_vbus_status函数中会完成vbus的状态更新,将vbus状态写入USB控制器的寄存器中。以作出相应的处理。

static void dwc3_override_vbus_status(struct dwc3_msm *mdwc, bool vbus_present)

{

/* Update OTG VBUS Valid from HSPHY to controller */

dwc3_msm_write_reg_field(mdwc->base, HS_PHY_CTRL_REG,

UTMI_OTG_VBUS_VALID, !!vbus_present);

/* Update VBUS Valid from SSPHY to controller */

if (vbus_present) {

/* Update only if Super Speed is supported */

if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER)

dwc3_msm_write_reg_field(mdwc->base, SS_PHY_CTRL_REG,

LANE0_PWR_PRESENT, 1);

} else {

dwc3_msm_write_reg_field(mdwc->base, SS_PHY_CTRL_REG,

LANE0_PWR_PRESENT, 0);

}

}

最后当vbus的状态被写入USB控制器后。控制器则会在合适的时间触发dwc3_gadget_interrupt的相对应的事件。

当vbus断开连接后,这里USB控制器则会触发DWC3_DEVICE_EVENT_DISCONNECT事件。

当vbus重新连接后,USB控制器则会根据主机的请求,触发DWC3_DEVICE_EVENT_RESET事件,开始枚举过程。

static void dwc3_gadget_interrupt(struct dwc3 *dwc,

const struct dwc3_event_devt *event)

{

switch (event->type) {

case DWC3_DEVICE_EVENT_DISCONNECT:

dwc3_gadget_disconnect_interrupt(dwc);

break;

case DWC3_DEVICE_EVENT_RESET:

dwc3_gadget_reset_interrupt(dwc);

break;

case DWC3_DEVICE_EVENT_CONNECT_DONE:

dwc3_gadget_conndone_interrupt(dwc);

break;

xxxxxxxxx

}

}

案例分析

RM模组插拔USB无端口

根据前面的内容分析,USB的热插拔是由vbus决定的。故决定跟踪vbus的状态。

RG的模组不存在这个问题。通过interrupts文件可以查看vbus中断的触发次数,无论是插入USB还是移除USB,pmd-vbus-det中断的计数都会增加1,也就是插拔都会触发一次中断。

root@sdxlemur:/# cat /proc/interrupts | grep vbus

98: 10 pmic_arb 290455750 Edge c440000.qcom,spmi:qcom,pmx65@1:qcom,pmd-vbus-det@1500

RM的模组该中断则一直是0,后续无论怎么插拔都不会增加。

root@sdxlemur:/# cat /proc/interrupts | grep vbus

95: 0 pmic_arb 290455750 Edge c440000.qcom,spmi:qcom,pmx65@1:qcom,pmd-vbus-det@1500

推测RM模组的vbus中断存在问题。由于是普遍都存在问题,所有的RM模组都无法热插拔,怀疑是硬件上的设计存在问题。询问硬件后,得知RM模组设计上vbus是一直接通的,不能正确反映usb的接入与否。

那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。