| From 0ceef0fa2e24cf94e98cb61a57c0e910805e98be Mon Sep 17 00:00:00 2001 |
| From: Oliver Neukum <oneukum@suse.com> |
| Date: Wed, 15 Apr 2020 16:17:50 +0200 |
| Subject: [PATCH] UAS: fix deadlock in error handling and PM flushing work |
| |
| commit f6cc6093a729ede1ff5658b493237c42b82ba107 upstream. |
| |
| A SCSI error handler and block runtime PM must not allocate |
| memory with GFP_KERNEL. Furthermore they must not wait for |
| tasks allocating memory with GFP_KERNEL. |
| That means that they cannot share a workqueue with arbitrary tasks. |
| |
| Fix this for UAS using a private workqueue. |
| |
| Signed-off-by: Oliver Neukum <oneukum@suse.com> |
| Fixes: f9dc024a2da1f ("uas: pre_reset and suspend: Fix a few races") |
| Cc: stable <stable@vger.kernel.org> |
| Link: https://lore.kernel.org/r/20200415141750.811-2-oneukum@suse.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c |
| index 77805b753462..de4d33fbf6b8 100644 |
| --- a/drivers/usb/storage/uas.c |
| +++ b/drivers/usb/storage/uas.c |
| @@ -81,6 +81,19 @@ static void uas_free_streams(struct uas_dev_info *devinfo); |
| static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *prefix, |
| int status); |
| |
| +/* |
| + * This driver needs its own workqueue, as we need to control memory allocation. |
| + * |
| + * In the course of error handling and power management uas_wait_for_pending_cmnds() |
| + * needs to flush pending work items. In these contexts we cannot allocate memory |
| + * by doing block IO as we would deadlock. For the same reason we cannot wait |
| + * for anything allocating memory not heeding these constraints. |
| + * |
| + * So we have to control all work items that can be on the workqueue we flush. |
| + * Hence we cannot share a queue and need our own. |
| + */ |
| +static struct workqueue_struct *workqueue; |
| + |
| static void uas_do_work(struct work_struct *work) |
| { |
| struct uas_dev_info *devinfo = |
| @@ -109,7 +122,7 @@ static void uas_do_work(struct work_struct *work) |
| if (!err) |
| cmdinfo->state &= ~IS_IN_WORK_LIST; |
| else |
| - schedule_work(&devinfo->work); |
| + queue_work(workqueue, &devinfo->work); |
| } |
| out: |
| spin_unlock_irqrestore(&devinfo->lock, flags); |
| @@ -134,7 +147,7 @@ static void uas_add_work(struct uas_cmd_info *cmdinfo) |
| |
| lockdep_assert_held(&devinfo->lock); |
| cmdinfo->state |= IS_IN_WORK_LIST; |
| - schedule_work(&devinfo->work); |
| + queue_work(workqueue, &devinfo->work); |
| } |
| |
| static void uas_zap_pending(struct uas_dev_info *devinfo, int result) |
| @@ -1230,7 +1243,31 @@ static struct usb_driver uas_driver = { |
| .id_table = uas_usb_ids, |
| }; |
| |
| -module_usb_driver(uas_driver); |
| +static int __init uas_init(void) |
| +{ |
| + int rv; |
| + |
| + workqueue = alloc_workqueue("uas", WQ_MEM_RECLAIM, 0); |
| + if (!workqueue) |
| + return -ENOMEM; |
| + |
| + rv = usb_register(&uas_driver); |
| + if (rv) { |
| + destroy_workqueue(workqueue); |
| + return -ENOMEM; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static void __exit uas_exit(void) |
| +{ |
| + usb_deregister(&uas_driver); |
| + destroy_workqueue(workqueue); |
| +} |
| + |
| +module_init(uas_init); |
| +module_exit(uas_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR( |
| -- |
| 2.7.4 |
| |