Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

HID: intel-ish-hid: Use dedicated unbound workqueues to prevent resume blocking

During suspend/resume tests with S2IDLE, some ISH functional failures were
observed because of delay in executing ISH resume handler. Here
schedule_work() is used from resume handler to do actual work.
schedule_work() uses system_wq, which is a per CPU work queue. Although
the queuing is not bound to a CPU, but it prefers local CPU of the caller,
unless prohibited.

Users of this work queue are not supposed to queue long running work.
But in practice, there are scenarios where long running work items are
queued on other unbound workqueues, occupying the CPU. As a result, the
ISH resume handler may not get a chance to execute in a timely manner.

In one scenario, one of the ish_resume_handler() executions was delayed
nearly 1 second because another work item on an unbound workqueue occupied
the same CPU. This delay causes ISH functionality failures.

A similar issue was previously observed where the ISH HID driver timed out
while getting the HID descriptor during S4 resume in the recovery kernel,
likely caused by the same workqueue contention problem.

Create dedicated unbound workqueues for all ISH operations to allow work
items to execute on any available CPU, eliminating CPU-specific bottlenecks
and improving resume reliability under varying system loads. Also ISH has
three different components, a bus driver which implements ISH protocols, a
PCI interface layer and HID interface. Use one dedicated work queue for all
of them.

Signed-off-by: Zhang Lixu <lixu.zhang@intel.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>

authored by

Zhang Lixu and committed by
Jiri Kosina
0d30dae3 54ba6d9b

+47 -7
+20 -1
drivers/hid/intel-ish-hid/ipc/ipc.c
··· 628 628 if (!ishtp_dev) { 629 629 ishtp_dev = dev; 630 630 } 631 - schedule_work(&fw_reset_work); 631 + queue_work(dev->unbound_wq, &fw_reset_work); 632 632 break; 633 633 634 634 case MNG_RESET_NOTIFY_ACK: ··· 933 933 .dma_no_cache_snooping = _dma_no_cache_snooping 934 934 }; 935 935 936 + static struct workqueue_struct *devm_ishtp_alloc_workqueue(struct device *dev) 937 + { 938 + struct workqueue_struct *wq; 939 + 940 + wq = alloc_workqueue("ishtp_unbound_%d", WQ_UNBOUND, 0, dev->id); 941 + if (!wq) 942 + return NULL; 943 + 944 + if (devm_add_action_or_reset(dev, (void (*)(void *))destroy_workqueue, 945 + wq)) 946 + return NULL; 947 + 948 + return wq; 949 + } 950 + 936 951 /** 937 952 * ish_dev_init() -Initialize ISH devoce 938 953 * @pdev: PCI device ··· 966 951 sizeof(struct ishtp_device) + sizeof(struct ish_hw), 967 952 GFP_KERNEL); 968 953 if (!dev) 954 + return NULL; 955 + 956 + dev->unbound_wq = devm_ishtp_alloc_workqueue(&pdev->dev); 957 + if (!dev->unbound_wq) 969 958 return NULL; 970 959 971 960 dev->devc = &pdev->dev;
+1 -1
drivers/hid/intel-ish-hid/ipc/pci-ish.c
··· 384 384 ish_resume_device = device; 385 385 dev->resume_flag = 1; 386 386 387 - schedule_work(&resume_work); 387 + queue_work(dev->unbound_wq, &resume_work); 388 388 389 389 return 0; 390 390 }
+2 -2
drivers/hid/intel-ish-hid/ishtp-hid-client.c
··· 860 860 hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, 861 861 hid_ishtp_cl); 862 862 863 - schedule_work(&client_data->work); 863 + queue_work(ishtp_get_workqueue(cl_device), &client_data->work); 864 864 865 865 return 0; 866 866 } ··· 902 902 903 903 hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, 904 904 hid_ishtp_cl); 905 - schedule_work(&client_data->resume_work); 905 + queue_work(ishtp_get_workqueue(cl_device), &client_data->resume_work); 906 906 return 0; 907 907 } 908 908
+17 -1
drivers/hid/intel-ish-hid/ishtp/bus.c
··· 541 541 return; 542 542 543 543 if (device->event_cb) 544 - schedule_work(&device->event_work); 544 + queue_work(device->ishtp_dev->unbound_wq, &device->event_work); 545 545 } 546 546 547 547 /** ··· 875 875 return device->ishtp_dev->devc; 876 876 } 877 877 EXPORT_SYMBOL(ishtp_get_pci_device); 878 + 879 + /** 880 + * ishtp_get_workqueue - Retrieve the workqueue associated with an ISHTP device 881 + * @cl_device: Pointer to the ISHTP client device structure 882 + * 883 + * Returns the workqueue_struct pointer (unbound_wq) associated with the given 884 + * ISHTP client device. This workqueue is typically used for scheduling work 885 + * related to the device. 886 + * 887 + * Return: Pointer to struct workqueue_struct. 888 + */ 889 + struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device) 890 + { 891 + return cl_device->ishtp_dev->unbound_wq; 892 + } 893 + EXPORT_SYMBOL(ishtp_get_workqueue); 878 894 879 895 /** 880 896 * ishtp_trace_callback() - Return trace callback
+2 -2
drivers/hid/intel-ish-hid/ishtp/hbm.c
··· 573 573 574 574 /* Start firmware loading process if it has loader capability */ 575 575 if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER) 576 - schedule_work(&dev->work_fw_loader); 576 + queue_work(dev->unbound_wq, &dev->work_fw_loader); 577 577 578 578 dev->version.major_version = HBM_MAJOR_VERSION; 579 579 dev->version.minor_version = HBM_MINOR_VERSION; ··· 864 864 dev->rd_msg_fifo_tail = (dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) % 865 865 (RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE); 866 866 spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); 867 - schedule_work(&dev->bh_hbm_work); 867 + queue_work(dev->unbound_wq, &dev->bh_hbm_work); 868 868 eoi: 869 869 return; 870 870 }
+3
drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
··· 175 175 struct hbm_version version; 176 176 int transfer_path; /* Choice of transfer path: IPC or DMA */ 177 177 178 + /* Alloc a dedicated unbound workqueue for ishtp device */ 179 + struct workqueue_struct *unbound_wq; 180 + 178 181 /* work structure for scheduling firmware loading tasks */ 179 182 struct work_struct work_fw_loader; 180 183 /* waitq for waiting for command response from the firmware loader */
+2
include/linux/intel-ish-client-if.h
··· 87 87 ishtp_print_log ishtp_trace_callback(struct ishtp_cl_device *cl_device); 88 88 /* Get device pointer of PCI device for DMA acces */ 89 89 struct device *ishtp_get_pci_device(struct ishtp_cl_device *cl_device); 90 + /* Get the ISHTP workqueue */ 91 + struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device); 90 92 91 93 struct ishtp_cl *ishtp_cl_allocate(struct ishtp_cl_device *cl_device); 92 94 void ishtp_cl_free(struct ishtp_cl *cl);