mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-05 18:41:58 +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
|
* @otg_mux_irq: IRQ number which multiplex otg-id/otg-bvalid/linestate
|
||||||
* irqs to one irq in otg-port.
|
* irqs to one irq in otg-port.
|
||||||
* @mutex: for register updating in sm_work.
|
* @mutex: for register updating in sm_work.
|
||||||
* @chg_work: charge detect work.
|
|
||||||
* @bypass_uart_work: usb bypass uart work.
|
* @bypass_uart_work: usb bypass uart work.
|
||||||
* @otg_sm_work: OTG state machine work.
|
* @otg_sm_work: OTG state machine work.
|
||||||
* @sm_work: HOST 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.
|
* @vbus: vbus regulator supply on few rockchip boards.
|
||||||
* @sw: orientation switch, communicate with TCPM (Type-C Port Manager).
|
* @sw: orientation switch, communicate with TCPM (Type-C Port Manager).
|
||||||
* @port_cfg: port register configuration, assigned by driver data.
|
* @port_cfg: port register configuration, assigned by driver data.
|
||||||
@@ -304,9 +305,10 @@ struct rockchip_usb2phy_port {
|
|||||||
int otg_mux_irq;
|
int otg_mux_irq;
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
struct delayed_work bypass_uart_work;
|
struct delayed_work bypass_uart_work;
|
||||||
struct delayed_work chg_work;
|
|
||||||
struct delayed_work otg_sm_work;
|
struct delayed_work otg_sm_work;
|
||||||
struct delayed_work sm_work;
|
struct delayed_work sm_work;
|
||||||
|
struct kthread_work chg_work;
|
||||||
|
struct kthread_worker *chg_worker;
|
||||||
struct regulator *vbus;
|
struct regulator *vbus;
|
||||||
struct typec_switch_dev *sw;
|
struct typec_switch_dev *sw;
|
||||||
const struct rockchip_usb2phy_port_cfg *port_cfg;
|
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) {
|
switch (rphy->chg_state) {
|
||||||
case USB_CHG_STATE_UNDEFINED:
|
case USB_CHG_STATE_UNDEFINED:
|
||||||
mutex_unlock(&rport->mutex);
|
mutex_unlock(&rport->mutex);
|
||||||
schedule_delayed_work(&rport->chg_work, 0);
|
kthread_queue_work(rport->chg_worker, &rport->chg_work);
|
||||||
return;
|
return;
|
||||||
case USB_CHG_STATE_DETECTED:
|
case USB_CHG_STATE_DETECTED:
|
||||||
switch (rphy->chg_type) {
|
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_DCD_MAX_RETRIES 6
|
||||||
#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000)
|
#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000)
|
||||||
#define CHG_SECONDARY_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 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;
|
bool is_dcd, tmout, vout;
|
||||||
unsigned long delay;
|
unsigned long delay;
|
||||||
unsigned int mask;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",
|
dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",
|
||||||
rphy->chg_state);
|
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) {
|
switch (rphy->chg_state) {
|
||||||
case USB_CHG_STATE_UNDEFINED:
|
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 */
|
/* Start DCD processing stage 1 */
|
||||||
rockchip_chg_enable_dcd(rphy, true);
|
rockchip_chg_enable_dcd(rphy, true);
|
||||||
rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
|
rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
|
||||||
@@ -1585,37 +1558,75 @@ static void rockchip_chg_detect_work(struct work_struct *work)
|
|||||||
fallthrough;
|
fallthrough;
|
||||||
case USB_CHG_STATE_SECONDARY_DONE:
|
case USB_CHG_STATE_SECONDARY_DONE:
|
||||||
rphy->chg_state = USB_CHG_STATE_DETECTED;
|
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;
|
return;
|
||||||
default:
|
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);
|
mutex_unlock(&rport->mutex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Set the PHY in charger detection mode.
|
||||||
* Hold the mutex lock during the whole charger
|
* The conditions for charger detection:
|
||||||
* detection stage, and release it after detect
|
* 1. Set the PHY in normal mode to keep the UTMI_CLK on.
|
||||||
* the charger type.
|
* 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,
|
INIT_DELAYED_WORK(&rport->bypass_uart_work,
|
||||||
rockchip_usb_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);
|
INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work);
|
||||||
|
|
||||||
if (!IS_ERR(rphy->edev)) {
|
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:
|
out:
|
||||||
/*
|
/*
|
||||||
* Let us put phy-port into suspend mode here for saving power
|
* Let us put phy-port into suspend mode here for saving power
|
||||||
@@ -2626,6 +2642,31 @@ disable_clks:
|
|||||||
return ret;
|
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
|
static int __maybe_unused
|
||||||
rockchip_usb2phy_low_power_enable(struct rockchip_usb2phy *rphy,
|
rockchip_usb2phy_low_power_enable(struct rockchip_usb2phy *rphy,
|
||||||
struct rockchip_usb2phy_port *rport,
|
struct rockchip_usb2phy_port *rport,
|
||||||
@@ -4802,6 +4843,7 @@ MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match);
|
|||||||
|
|
||||||
static struct platform_driver rockchip_usb2phy_driver = {
|
static struct platform_driver rockchip_usb2phy_driver = {
|
||||||
.probe = rockchip_usb2phy_probe,
|
.probe = rockchip_usb2phy_probe,
|
||||||
|
.remove = rockchip_usb2phy_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "rockchip-usb2phy",
|
.name = "rockchip-usb2phy",
|
||||||
.pm = ROCKCHIP_USB2PHY_DEV_PM,
|
.pm = ROCKCHIP_USB2PHY_DEV_PM,
|
||||||
|
|||||||
Reference in New Issue
Block a user