Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx> writes: > On 2022/09/05 17:24, Schspa Shi wrote: >> >> Tejun Heo <tj@xxxxxxxxxx> writes: >> >>> Hello, >>> >>> On Sat, Sep 03, 2022 at 07:11:18PM -0700, Luiz Augusto von Dentz wrote: >>>> And we can check for __WQ_DRAINING? Anyway checking >>> >>> Please don't do that. That's an internal flag. It shouldn't be *that* >>> difficult to avoid this without peeking into wq internal state. >>> >>> Thanks. >> >> It seems we only need to change hdev->{cmd,ncmd}_timer to >> hdev->workqueue, there will be no race because drain_workqueue will >> flush all pending work internally. > > True for queue_work(), not always true for queue_delayed_work(). Explained below. > Ok, you are right, got it now. >> Any new timeout work will see HCI_CMD_DRAIN_WORKQUEUE flags after we >> cancel and flushed all the delayed work. >> > > If you don't mind calling > > queue_work(&hdev->cmd_work) followed by hci_cmd_work() (case A below) > > and/or > > queue_delayed_work(&hdev->ncmd_timer) potentially followed by hci_ncmd_timeout()/hci_reset_dev() (case B and C below) > > after observing HCI_CMD_DRAIN_WORKQUEUE flag. > We need to use RCU protection if you mind one of these. > > > > Case A: > > hci_dev_do_reset() { > hci_cmd_work() { > if (test_bit(HCI_RESET, &hdev->flags) || > hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE)) > cancel_delayed_work(&hdev->cmd_timer); > else > queue_delayed_work(hdev->workqueue, &hdev->cmd_timer, HCI_CMD_TIMEOUT); > } else { > skb_queue_head(&hdev->cmd_q, skb); > hci_dev_set_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); > cancel_delayed_work(&hdev->cmd_timer); > cancel_delayed_work(&hdev->ncmd_timer); > queue_work(hdev->workqueue, &hdev->cmd_work); // Queuing after setting HCI_CMD_DRAIN_WORKQUEUE despite the intent of HCI_CMD_DRAIN_WORKQUEUE... > drain_workqueue(hdev->workqueue); // Will wait for hci_cmd_timeout() queued by queue_work() to complete. > > } > > // Actual flush() happens here. > > hci_dev_clear_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); > } > > > > Case B: > > hci_dev_do_reset() { > handle_cmd_cnt_and_timer(struct hci_dev *hdev, u8 ncmd) { > if (!hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE)) > hci_dev_set_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); > cancel_delayed_work(&hdev->cmd_timer); > queue_delayed_work(hdev->workqueue, &hdev->ncmd_timer, HCI_NCMD_TIMEOUT); > cancel_delayed_work(&hdev->ncmd_timer); // May or may not cancel hci_ncmd_timeout() queued by queue_delayed_work(). > drain_workqueue(hdev->workqueue); // Will wait for hci_ncmd_timeout() queued by queue_delayed_work() to complete if cancel_delayed_work() failed to cancel. > > } > > // Actual flush() happens here. > > hci_dev_clear_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); > } > > > > Case C: > > hci_dev_do_reset() { > handle_cmd_cnt_and_timer(struct hci_dev *hdev, u8 ncmd) { > if (!hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE)) > hci_dev_set_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); > cancel_delayed_work(&hdev->cmd_timer); > cancel_delayed_work(&hdev->ncmd_timer); // Does nothing. > queue_delayed_work(hdev->workqueue, &hdev->ncmd_timer, HCI_NCMD_TIMEOUT); > drain_workqueue(hdev->workqueue); // Will wait for hci_ncmd_timeout() queued by queue_delayed_work() to complete if delay timer has expired. > > } > > // Actual flush() happens here, but hci_ncmd_timeout() queued by queue_delayed_work() can be running if delay timer has not expired as of calling drain_workqueue(). > > hci_dev_clear_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); > } -- BRs Schspa Shi