mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-05 10:31:46 +09:00
phy: rockchip: inno-usb2: Fix DEBUG_LOCKS_WARN_ON in chg work
The following trace can be seen if usb is connecting to Host while do rockchip_chg_detect_work. DEBUG_LOCKS_WARN_ON(rt_mutex_owner(lock) != current) WARNING: CPU: 6 PID: 512 at kernel/locking/rtmutex-debug.c:47 debug_rt_mutex_unlock+0x58/0x64 Modules linked in: CPU: 6 PID: 512 Comm: kworker/6:3 Not tainted 5.10.226-rt89 #186 Hardware name: Rockchip RK3588 EVB1 LP4 V10 Board (DT) Workqueue: events rockchip_chg_detect_work pstate: 60c00089 (nZCv daIf +PAN +UAO -TCO BTYPE=--) pc : debug_rt_mutex_unlock+0x58/0x64 lr : debug_rt_mutex_unlock+0x58/0x64 ...... Call trace: debug_rt_mutex_unlock+0x58/0x64 __rt_mutex_unlock+0x48/0xf8 _mutex_unlock+0xc/0x14 rockchip_chg_detect_work+0x44c/0x6f0 process_one_work+0x1bc/0x27c worker_thread+0x268/0x488 kthread+0x170/0x210 ret_from_fork+0x10/0x18 This issue can cause the preempt-rt Linux kernel to crash. The reason is that all mutexes in preempt-rt have been replaced with rt_mutexes. An rt_mutex has a PI (Priority Inversion) feature, which means that when a high-priority task waits for a lock held by a low-priority task, the priority of the low-priority task is elevated. A linked list is established on p->pi_waiters. This requires that lock/unlock operations be handled by the same task. If unlock is performed and pi_waiters is released by another task, the task that holds the lock will encounter an exception when accessing pi_waiters. When executing rockchip_chg_detect_work, a schedule_delayed_work operation is performed while holding the mutex lock, causing the mutex lock to be released by a different worker task, which triggers a kernel panic. This patch use kthread_work instead of delayed_work to avoid long-running chg work affecting other tasks in the system workqueue. And also avoid chg work to be scheduled while hold a mutex lock. Signed-off-by: William Wu <william.wu@rock-chips.com> Change-Id: I1e49a22f002b0dfcf0e04d243d99624d34c9a701
This commit is contained in:
@@ -267,10 +267,11 @@ struct rockchip_usb2phy_cfg {
|
||||
* @otg_mux_irq: IRQ number which multiplex otg-id/otg-bvalid/linestate
|
||||
* irqs to one irq in otg-port.
|
||||
* @mutex: for register updating in sm_work.
|
||||
* @chg_work: charge detect work.
|
||||
* @bypass_uart_work: usb bypass uart work.
|
||||
* @otg_sm_work: OTG state machine work.
|
||||
* @sm_work: HOST state machine work.
|
||||
* @chg_work: charge detect kthread work.
|
||||
* @chg_worker: charge detect kthread worker.
|
||||
* @vbus: vbus regulator supply on few rockchip boards.
|
||||
* @sw: orientation switch, communicate with TCPM (Type-C Port Manager).
|
||||
* @port_cfg: port register configuration, assigned by driver data.
|
||||
@@ -304,9 +305,10 @@ struct rockchip_usb2phy_port {
|
||||
int otg_mux_irq;
|
||||
struct mutex mutex;
|
||||
struct delayed_work bypass_uart_work;
|
||||
struct delayed_work chg_work;
|
||||
struct delayed_work otg_sm_work;
|
||||
struct delayed_work sm_work;
|
||||
struct kthread_work chg_work;
|
||||
struct kthread_worker *chg_worker;
|
||||
struct regulator *vbus;
|
||||
struct typec_switch_dev *sw;
|
||||
const struct rockchip_usb2phy_port_cfg *port_cfg;
|
||||
@@ -1306,7 +1308,7 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
|
||||
switch (rphy->chg_state) {
|
||||
case USB_CHG_STATE_UNDEFINED:
|
||||
mutex_unlock(&rport->mutex);
|
||||
schedule_delayed_work(&rport->chg_work, 0);
|
||||
kthread_queue_work(rport->chg_worker, &rport->chg_work);
|
||||
return;
|
||||
case USB_CHG_STATE_DETECTED:
|
||||
switch (rphy->chg_type) {
|
||||
@@ -1472,46 +1474,17 @@ static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy,
|
||||
#define CHG_DCD_MAX_RETRIES 6
|
||||
#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000)
|
||||
#define CHG_SECONDARY_DET_TIME (40 * HZ / 1000)
|
||||
static void rockchip_chg_detect_work(struct work_struct *work)
|
||||
static void rockchip_run_chg_detect_machine(struct rockchip_usb2phy_port *rport)
|
||||
{
|
||||
struct rockchip_usb2phy_port *rport =
|
||||
container_of(work, struct rockchip_usb2phy_port, chg_work.work);
|
||||
struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
|
||||
struct regmap *base = get_reg_base(rphy);
|
||||
const struct usb2phy_reg *phy_sus_reg;
|
||||
bool is_dcd, tmout, vout;
|
||||
unsigned long delay;
|
||||
unsigned int mask;
|
||||
int ret;
|
||||
|
||||
dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",
|
||||
rphy->chg_state);
|
||||
|
||||
/*
|
||||
* The conditions for charger detection:
|
||||
* 1. Set the PHY in normal mode to keep the UTMI_CLK on.
|
||||
* 2. Set the utmi_opmode in non-driving mode.
|
||||
* 3. Set the utmi_xcvrselect to FS speed.
|
||||
* 4. Set the utmi_termselect to FS speed.
|
||||
* 5. Enable the DP/DM pulldown resistor.
|
||||
*/
|
||||
switch (rphy->chg_state) {
|
||||
case USB_CHG_STATE_UNDEFINED:
|
||||
mutex_lock(&rport->mutex);
|
||||
/* Store the PHY current suspend configuration */
|
||||
phy_sus_reg = &rport->port_cfg->phy_sus;
|
||||
ret = regmap_read(base, phy_sus_reg->offset,
|
||||
&rphy->phy_sus_cfg);
|
||||
if (ret) {
|
||||
dev_err(&rport->phy->dev,
|
||||
"Fail to read phy_sus reg offset 0x%x, ret %d\n",
|
||||
phy_sus_reg->offset, ret);
|
||||
mutex_unlock(&rport->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the PHY in charger detection mode */
|
||||
property_enable(base, &rphy->phy_cfg->chg_det.chg_mode, true);
|
||||
/* Start DCD processing stage 1 */
|
||||
rockchip_chg_enable_dcd(rphy, true);
|
||||
rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
|
||||
@@ -1585,37 +1558,75 @@ static void rockchip_chg_detect_work(struct work_struct *work)
|
||||
fallthrough;
|
||||
case USB_CHG_STATE_SECONDARY_DONE:
|
||||
rphy->chg_state = USB_CHG_STATE_DETECTED;
|
||||
fallthrough;
|
||||
case USB_CHG_STATE_DETECTED:
|
||||
if (rphy->phy_cfg->chg_det.chg_mode.offset !=
|
||||
rport->port_cfg->phy_sus.offset)
|
||||
property_enable(base, &rphy->phy_cfg->chg_det.chg_mode, false);
|
||||
|
||||
/* Restore the PHY suspend configuration */
|
||||
phy_sus_reg = &rport->port_cfg->phy_sus;
|
||||
mask = GENMASK(phy_sus_reg->bitend, phy_sus_reg->bitstart);
|
||||
ret = regmap_write(base, phy_sus_reg->offset,
|
||||
(rphy->phy_sus_cfg | (mask << BIT_WRITEABLE_SHIFT)));
|
||||
if (ret)
|
||||
dev_err(&rport->phy->dev,
|
||||
"Fail to set phy_sus reg offset 0x%x, ret %d\n",
|
||||
phy_sus_reg->offset, ret);
|
||||
mutex_unlock(&rport->mutex);
|
||||
rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
|
||||
dev_dbg(&rport->phy->dev, "charger = %s\n",
|
||||
chg_to_string(rphy->chg_type));
|
||||
return;
|
||||
default:
|
||||
rphy->chg_state = USB_CHG_STATE_DETECTED;
|
||||
rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
if (delay)
|
||||
msleep(jiffies_to_msecs(delay));
|
||||
}
|
||||
|
||||
static void rockchip_chg_detect_work(struct kthread_work *work)
|
||||
{
|
||||
struct rockchip_usb2phy_port *rport =
|
||||
container_of(work, struct rockchip_usb2phy_port, chg_work);
|
||||
struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
|
||||
struct regmap *base = get_reg_base(rphy);
|
||||
const struct usb2phy_reg *phy_sus_reg;
|
||||
unsigned int mask;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&rport->mutex);
|
||||
|
||||
/* Store the PHY current suspend configuration */
|
||||
phy_sus_reg = &rport->port_cfg->phy_sus;
|
||||
ret = regmap_read(base, phy_sus_reg->offset, &rphy->phy_sus_cfg);
|
||||
if (ret) {
|
||||
dev_err(&rport->phy->dev,
|
||||
"Fail to read phy_sus reg offset 0x%x, ret %d\n",
|
||||
phy_sus_reg->offset, ret);
|
||||
mutex_unlock(&rport->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hold the mutex lock during the whole charger
|
||||
* detection stage, and release it after detect
|
||||
* the charger type.
|
||||
/* Set the PHY in charger detection mode.
|
||||
* The conditions for charger detection:
|
||||
* 1. Set the PHY in normal mode to keep the UTMI_CLK on.
|
||||
* 2. Set the utmi_opmode in non-driving mode.
|
||||
* 3. Set the utmi_xcvrselect to FS speed.
|
||||
* 4. Set the utmi_termselect to FS speed.
|
||||
* 5. Enable the DP/DM pulldown resistor.
|
||||
*/
|
||||
schedule_delayed_work(&rport->chg_work, delay);
|
||||
property_enable(base, &rphy->phy_cfg->chg_det.chg_mode, true);
|
||||
|
||||
do {
|
||||
rockchip_run_chg_detect_machine(rport);
|
||||
} while (rphy->chg_state != USB_CHG_STATE_UNDEFINED &&
|
||||
rphy->chg_state != USB_CHG_STATE_DETECTED);
|
||||
|
||||
dev_info(&rport->phy->dev, "charger = %s\n", chg_to_string(rphy->chg_type));
|
||||
|
||||
/* Disable charger detection mode */
|
||||
if (rphy->phy_cfg->chg_det.chg_mode.offset != rport->port_cfg->phy_sus.offset)
|
||||
property_enable(base, &rphy->phy_cfg->chg_det.chg_mode, false);
|
||||
|
||||
/* Restore the PHY suspend configuration */
|
||||
phy_sus_reg = &rport->port_cfg->phy_sus;
|
||||
mask = GENMASK(phy_sus_reg->bitend, phy_sus_reg->bitstart);
|
||||
ret = regmap_write(base, phy_sus_reg->offset,
|
||||
(rphy->phy_sus_cfg | (mask << BIT_WRITEABLE_SHIFT)));
|
||||
if (ret)
|
||||
dev_err(&rport->phy->dev,
|
||||
"Fail to set phy_sus reg offset 0x%x, ret %d\n",
|
||||
phy_sus_reg->offset, ret);
|
||||
|
||||
mutex_unlock(&rport->mutex);
|
||||
|
||||
if (rphy->chg_state == USB_CHG_STATE_DETECTED)
|
||||
rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2382,7 +2393,6 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
|
||||
|
||||
INIT_DELAYED_WORK(&rport->bypass_uart_work,
|
||||
rockchip_usb_bypass_uart_work);
|
||||
INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work);
|
||||
INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work);
|
||||
|
||||
if (!IS_ERR(rphy->edev)) {
|
||||
@@ -2396,6 +2406,12 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
|
||||
}
|
||||
}
|
||||
|
||||
rport->chg_worker = kthread_create_worker(0, "usbchg-worker-%d",
|
||||
rport->phy->id);
|
||||
if (IS_ERR(rport->chg_worker))
|
||||
return PTR_ERR(rport->chg_worker);
|
||||
kthread_init_work(&rport->chg_work, rockchip_chg_detect_work);
|
||||
|
||||
out:
|
||||
/*
|
||||
* Let us put phy-port into suspend mode here for saving power
|
||||
@@ -2626,6 +2642,31 @@ disable_clks:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rockchip_usb2phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rockchip_usb2phy *rphy = platform_get_drvdata(pdev);
|
||||
struct rockchip_usb2phy_port *rport;
|
||||
unsigned int index;
|
||||
|
||||
for (index = 0; index < rphy->phy_cfg->num_ports; index++) {
|
||||
rport = &rphy->ports[index];
|
||||
if (!rport->phy)
|
||||
continue;
|
||||
|
||||
if (rport->port_id == USB2PHY_PORT_HOST) {
|
||||
cancel_delayed_work_sync(&rport->sm_work);
|
||||
} else if (rport->port_id == USB2PHY_PORT_OTG) {
|
||||
if (rport->chg_worker)
|
||||
kthread_destroy_worker(rport->chg_worker);
|
||||
|
||||
if (rport->otg_sm_work.work.func)
|
||||
cancel_delayed_work_sync(&rport->otg_sm_work);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused
|
||||
rockchip_usb2phy_low_power_enable(struct rockchip_usb2phy *rphy,
|
||||
struct rockchip_usb2phy_port *rport,
|
||||
@@ -4802,6 +4843,7 @@ MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match);
|
||||
|
||||
static struct platform_driver rockchip_usb2phy_driver = {
|
||||
.probe = rockchip_usb2phy_probe,
|
||||
.remove = rockchip_usb2phy_remove,
|
||||
.driver = {
|
||||
.name = "rockchip-usb2phy",
|
||||
.pm = ROCKCHIP_USB2PHY_DEV_PM,
|
||||
|
||||
Reference in New Issue
Block a user