From 69cca3bbc7c131f88e24a8ee3f5aae50020eed55 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 12 Mar 2013 15:02:37 -0700 Subject: [PATCH] 3.8-stable patches added patches: efi-be-more-paranoid-about-available-space-when-creating-variables.patch efivars-disable-external-interrupt-while-holding-efivars-lock.patch usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch usb-fix-connected-device-switch-to-inactive-state.patch usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch usb-rip-out-recursive-call-on-warm-port-reset.patch --- ...ilable-space-when-creating-variables.patch | 202 +++++++++ ...interrupt-while-holding-efivars-lock.patch | 383 ++++++++++++++++++ queue-3.8/series | 6 + ...ehci-port-sempahore-for-usb-3.0-hubs.patch | 66 +++ ...cted-device-switch-to-inactive-state.patch | 91 +++++ ...actoring-by-adding-extra-udev-checks.patch | 75 ++++ ...ut-recursive-call-on-warm-port-reset.patch | 280 +++++++++++++ 7 files changed, 1103 insertions(+) create mode 100644 queue-3.8/efi-be-more-paranoid-about-available-space-when-creating-variables.patch create mode 100644 queue-3.8/efivars-disable-external-interrupt-while-holding-efivars-lock.patch create mode 100644 queue-3.8/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch create mode 100644 queue-3.8/usb-fix-connected-device-switch-to-inactive-state.patch create mode 100644 queue-3.8/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch create mode 100644 queue-3.8/usb-rip-out-recursive-call-on-warm-port-reset.patch diff --git a/queue-3.8/efi-be-more-paranoid-about-available-space-when-creating-variables.patch b/queue-3.8/efi-be-more-paranoid-about-available-space-when-creating-variables.patch new file mode 100644 index 00000000000..4b68a892548 --- /dev/null +++ b/queue-3.8/efi-be-more-paranoid-about-available-space-when-creating-variables.patch @@ -0,0 +1,202 @@ +From 68d929862e29a8b52a7f2f2f86a0600423b093cd Mon Sep 17 00:00:00 2001 +From: Matthew Garrett +Date: Sat, 2 Mar 2013 19:40:17 -0500 +Subject: efi: be more paranoid about available space when creating variables + +From: Matthew Garrett + +commit 68d929862e29a8b52a7f2f2f86a0600423b093cd upstream. + +UEFI variables are typically stored in flash. For various reasons, avaiable +space is typically not reclaimed immediately upon the deletion of a +variable - instead, the system will garbage collect during initialisation +after a reboot. + +Some systems appear to handle this garbage collection extremely poorly, +failing if more than 50% of the system flash is in use. This can result in +the machine refusing to boot. The safest thing to do for the moment is to +forbid writes if they'd end up using more than half of the storage space. +We can make this more finegrained later if we come up with a method for +identifying the broken machines. + +Signed-off-by: Matthew Garrett +Cc: Josh Boyer +Signed-off-by: Matt Fleming +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/firmware/efivars.c | 106 +++++++++++++++++++++++++++++++++------------ + 1 file changed, 79 insertions(+), 27 deletions(-) + +--- a/drivers/firmware/efivars.c ++++ b/drivers/firmware/efivars.c +@@ -419,6 +419,44 @@ get_var_data(struct efivars *efivars, st + return status; + } + ++static efi_status_t ++check_var_size_locked(struct efivars *efivars, u32 attributes, ++ unsigned long size) ++{ ++ u64 storage_size, remaining_size, max_size; ++ efi_status_t status; ++ const struct efivar_operations *fops = efivars->ops; ++ ++ if (!efivars->ops->query_variable_info) ++ return EFI_UNSUPPORTED; ++ ++ status = fops->query_variable_info(attributes, &storage_size, ++ &remaining_size, &max_size); ++ ++ if (status != EFI_SUCCESS) ++ return status; ++ ++ if (!storage_size || size > remaining_size || size > max_size || ++ (remaining_size - size) < (storage_size / 2)) ++ return EFI_OUT_OF_RESOURCES; ++ ++ return status; ++} ++ ++ ++static efi_status_t ++check_var_size(struct efivars *efivars, u32 attributes, unsigned long size) ++{ ++ efi_status_t status; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&efivars->lock, flags); ++ status = check_var_size_locked(efivars, attributes, size); ++ spin_unlock_irqrestore(&efivars->lock, flags); ++ ++ return status; ++} ++ + static ssize_t + efivar_guid_read(struct efivar_entry *entry, char *buf) + { +@@ -540,11 +578,16 @@ efivar_store_raw(struct efivar_entry *en + } + + spin_lock_irq(&efivars->lock); +- status = efivars->ops->set_variable(new_var->VariableName, +- &new_var->VendorGuid, +- new_var->Attributes, +- new_var->DataSize, +- new_var->Data); ++ ++ status = check_var_size_locked(efivars, new_var->Attributes, ++ new_var->DataSize + utf16_strsize(new_var->VariableName, 1024)); ++ ++ if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED) ++ status = efivars->ops->set_variable(new_var->VariableName, ++ &new_var->VendorGuid, ++ new_var->Attributes, ++ new_var->DataSize, ++ new_var->Data); + + spin_unlock_irq(&efivars->lock); + +@@ -695,8 +738,7 @@ static ssize_t efivarfs_file_write(struc + u32 attributes; + struct inode *inode = file->f_mapping->host; + unsigned long datasize = count - sizeof(attributes); +- unsigned long newdatasize; +- u64 storage_size, remaining_size, max_size; ++ unsigned long newdatasize, varsize; + ssize_t bytes = 0; + + if (count < sizeof(attributes)) +@@ -715,28 +757,18 @@ static ssize_t efivarfs_file_write(struc + * amounts of memory. Pick a default size of 64K if + * QueryVariableInfo() isn't supported by the firmware. + */ +- spin_lock_irq(&efivars->lock); + +- if (!efivars->ops->query_variable_info) +- status = EFI_UNSUPPORTED; +- else { +- const struct efivar_operations *fops = efivars->ops; +- status = fops->query_variable_info(attributes, &storage_size, +- &remaining_size, &max_size); +- } +- +- spin_unlock_irq(&efivars->lock); ++ varsize = datasize + utf16_strsize(var->var.VariableName, 1024); ++ status = check_var_size(efivars, attributes, varsize); + + if (status != EFI_SUCCESS) { + if (status != EFI_UNSUPPORTED) + return efi_status_to_err(status); + +- remaining_size = 65536; ++ if (datasize > 65536) ++ return -ENOSPC; + } + +- if (datasize > remaining_size) +- return -ENOSPC; +- + data = kmalloc(datasize, GFP_KERNEL); + if (!data) + return -ENOMEM; +@@ -758,6 +790,19 @@ static ssize_t efivarfs_file_write(struc + */ + spin_lock_irq(&efivars->lock); + ++ /* ++ * Ensure that the available space hasn't shrunk below the safe level ++ */ ++ ++ status = check_var_size_locked(efivars, attributes, varsize); ++ ++ if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED) { ++ spin_unlock_irq(&efivars->lock); ++ kfree(data); ++ ++ return efi_status_to_err(status); ++ } ++ + status = efivars->ops->set_variable(var->var.VariableName, + &var->var.VendorGuid, + attributes, datasize, +@@ -1348,7 +1393,6 @@ static int efi_pstore_write(enum pstore_ + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + struct efivars *efivars = psi->data; + int i, ret = 0; +- u64 storage_space, remaining_space, max_variable_size; + efi_status_t status = EFI_NOT_FOUND; + unsigned long flags; + +@@ -1359,11 +1403,11 @@ static int efi_pstore_write(enum pstore_ + * size: a size of logging data + * DUMP_NAME_LEN * 2: a maximum size of variable name + */ +- status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES, +- &storage_space, +- &remaining_space, +- &max_variable_size); +- if (status || remaining_space < size + DUMP_NAME_LEN * 2) { ++ ++ status = check_var_size_locked(efivars, PSTORE_EFI_ATTRIBUTES, ++ size + DUMP_NAME_LEN * 2); ++ ++ if (status) { + spin_unlock_irqrestore(&efivars->lock, flags); + *id = part; + return -ENOSPC; +@@ -1541,6 +1585,14 @@ static ssize_t efivar_create(struct file + return -EINVAL; + } + ++ status = check_var_size_locked(efivars, new_var->Attributes, ++ new_var->DataSize + utf16_strsize(new_var->VariableName, 1024)); ++ ++ if (status && status != EFI_UNSUPPORTED) { ++ spin_unlock_irq(&efivars->lock); ++ return efi_status_to_err(status); ++ } ++ + /* now *really* create the variable via EFI */ + status = efivars->ops->set_variable(new_var->VariableName, + &new_var->VendorGuid, diff --git a/queue-3.8/efivars-disable-external-interrupt-while-holding-efivars-lock.patch b/queue-3.8/efivars-disable-external-interrupt-while-holding-efivars-lock.patch new file mode 100644 index 00000000000..04c35d15488 --- /dev/null +++ b/queue-3.8/efivars-disable-external-interrupt-while-holding-efivars-lock.patch @@ -0,0 +1,383 @@ +From 81fa4e581d9283f7992a0d8c534bb141eb840a14 Mon Sep 17 00:00:00 2001 +From: Seiji Aguchi +Date: Tue, 12 Feb 2013 12:59:07 -0800 +Subject: efivars: Disable external interrupt while holding efivars->lock + +From: Seiji Aguchi + +commit 81fa4e581d9283f7992a0d8c534bb141eb840a14 upstream. + +[Problem] +There is a scenario which efi_pstore fails to log messages in a panic case. + + - CPUA holds an efi_var->lock in either efivarfs parts + or efi_pstore with interrupt enabled. + - CPUB panics and sends IPI to CPUA in smp_send_stop(). + - CPUA stops with holding the lock. + - CPUB kicks efi_pstore_write() via kmsg_dump(KSMG_DUMP_PANIC) + but it returns without logging messages. + +[Patch Description] +This patch disables an external interruption while holding efivars->lock +as follows. + +In efi_pstore_write() and get_var_data(), spin_lock/spin_unlock is +replaced by spin_lock_irqsave/spin_unlock_irqrestore because they may +be called in an interrupt context. + +In other functions, they are replaced by spin_lock_irq/spin_unlock_irq. +because they are all called from a process context. + +By applying this patch, we can avoid the problem above with +a following senario. + + - CPUA holds an efi_var->lock with interrupt disabled. + - CPUB panics and sends IPI to CPUA in smp_send_stop(). + - CPUA receives the IPI after releasing the lock because it is + disabling interrupt while holding the lock. + - CPUB waits for one sec until CPUA releases the lock. + - CPUB kicks efi_pstore_write() via kmsg_dump(KSMG_DUMP_PANIC) + And it can hold the lock successfully. + +Signed-off-by: Seiji Aguchi +Acked-by: Mike Waychison +Acked-by: Matt Fleming +Signed-off-by: Tony Luck +Cc: Josh Boyer +Signed-off-by: Greg Kroah-Hartman + + +--- + drivers/firmware/efivars.c | 84 +++++++++++++++++++++++---------------------- + 1 file changed, 43 insertions(+), 41 deletions(-) + +--- a/drivers/firmware/efivars.c ++++ b/drivers/firmware/efivars.c +@@ -406,10 +406,11 @@ static efi_status_t + get_var_data(struct efivars *efivars, struct efi_variable *var) + { + efi_status_t status; ++ unsigned long flags; + +- spin_lock(&efivars->lock); ++ spin_lock_irqsave(&efivars->lock, flags); + status = get_var_data_locked(efivars, var); +- spin_unlock(&efivars->lock); ++ spin_unlock_irqrestore(&efivars->lock, flags); + + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", +@@ -538,14 +539,14 @@ efivar_store_raw(struct efivar_entry *en + return -EINVAL; + } + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + status = efivars->ops->set_variable(new_var->VariableName, + &new_var->VendorGuid, + new_var->Attributes, + new_var->DataSize, + new_var->Data); + +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", +@@ -714,7 +715,7 @@ static ssize_t efivarfs_file_write(struc + * amounts of memory. Pick a default size of 64K if + * QueryVariableInfo() isn't supported by the firmware. + */ +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + + if (!efivars->ops->query_variable_info) + status = EFI_UNSUPPORTED; +@@ -724,7 +725,7 @@ static ssize_t efivarfs_file_write(struc + &remaining_size, &max_size); + } + +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + if (status != EFI_SUCCESS) { + if (status != EFI_UNSUPPORTED) +@@ -755,7 +756,7 @@ static ssize_t efivarfs_file_write(struc + * set_variable call, and removal of the variable from the efivars + * list (in the case of an authenticated delete). + */ +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + + status = efivars->ops->set_variable(var->var.VariableName, + &var->var.VendorGuid, +@@ -763,7 +764,7 @@ static ssize_t efivarfs_file_write(struc + data); + + if (status != EFI_SUCCESS) { +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + kfree(data); + + return efi_status_to_err(status); +@@ -784,21 +785,21 @@ static ssize_t efivarfs_file_write(struc + NULL); + + if (status == EFI_BUFFER_TOO_SMALL) { +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + mutex_lock(&inode->i_mutex); + i_size_write(inode, newdatasize + sizeof(attributes)); + mutex_unlock(&inode->i_mutex); + + } else if (status == EFI_NOT_FOUND) { + list_del(&var->list); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + efivar_unregister(var); + drop_nlink(inode); + d_delete(file->f_dentry); + dput(file->f_dentry); + + } else { +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + pr_warn("efivarfs: inconsistent EFI variable implementation? " + "status = %lx\n", status); + } +@@ -820,11 +821,11 @@ static ssize_t efivarfs_file_read(struct + void *data; + ssize_t size = 0; + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + &attributes, &datasize, NULL); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + if (status != EFI_BUFFER_TOO_SMALL) + return efi_status_to_err(status); +@@ -834,12 +835,12 @@ static ssize_t efivarfs_file_read(struct + if (!data) + return -ENOMEM; + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + &attributes, &datasize, + (data + sizeof(attributes))); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + if (status != EFI_SUCCESS) { + size = efi_status_to_err(status); +@@ -1005,9 +1006,9 @@ static int efivarfs_create(struct inode + goto out; + + kobject_uevent(&var->kobj, KOBJ_ADD); +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + list_add(&var->list, &efivars->list); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + d_instantiate(dentry, inode); + dget(dentry); + out: +@@ -1024,7 +1025,7 @@ static int efivarfs_unlink(struct inode + struct efivars *efivars = var->efivars; + efi_status_t status; + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + + status = efivars->ops->set_variable(var->var.VariableName, + &var->var.VendorGuid, +@@ -1032,14 +1033,14 @@ static int efivarfs_unlink(struct inode + + if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) { + list_del(&var->list); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + efivar_unregister(var); + drop_nlink(dentry->d_inode); + dput(dentry); + return 0; + } + +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + return -EINVAL; + }; + +@@ -1194,13 +1195,13 @@ static int efivarfs_fill_super(struct su + /* copied by the above to local storage in the dentry. */ + kfree(name); + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + efivars->ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, + &entry->var.Attributes, + &size, + NULL); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + mutex_lock(&inode->i_mutex); + inode->i_private = entry; +@@ -1263,7 +1264,7 @@ static int efi_pstore_open(struct pstore + { + struct efivars *efivars = psi->data; + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + efivars->walk_entry = list_first_entry(&efivars->list, + struct efivar_entry, list); + return 0; +@@ -1273,7 +1274,7 @@ static int efi_pstore_close(struct pstor + { + struct efivars *efivars = psi->data; + +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + return 0; + } + +@@ -1349,8 +1350,9 @@ static int efi_pstore_write(enum pstore_ + int i, ret = 0; + u64 storage_space, remaining_space, max_variable_size; + efi_status_t status = EFI_NOT_FOUND; ++ unsigned long flags; + +- spin_lock(&efivars->lock); ++ spin_lock_irqsave(&efivars->lock, flags); + + /* + * Check if there is a space enough to log. +@@ -1362,7 +1364,7 @@ static int efi_pstore_write(enum pstore_ + &remaining_space, + &max_variable_size); + if (status || remaining_space < size + DUMP_NAME_LEN * 2) { +- spin_unlock(&efivars->lock); ++ spin_unlock_irqrestore(&efivars->lock, flags); + *id = part; + return -ENOSPC; + } +@@ -1376,7 +1378,7 @@ static int efi_pstore_write(enum pstore_ + efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES, + size, psi->buf); + +- spin_unlock(&efivars->lock); ++ spin_unlock_irqrestore(&efivars->lock, flags); + + if (size) + ret = efivar_create_sysfs_entry(efivars, +@@ -1403,7 +1405,7 @@ static int efi_pstore_erase(enum pstore_ + sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count, + time.tv_sec); + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name[i] = name[i]; +@@ -1447,7 +1449,7 @@ static int efi_pstore_erase(enum pstore_ + if (found) + list_del(&found->list); + +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + if (found) + efivar_unregister(found); +@@ -1517,7 +1519,7 @@ static ssize_t efivar_create(struct file + return -EINVAL; + } + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + + /* + * Does this variable already exist? +@@ -1535,7 +1537,7 @@ static ssize_t efivar_create(struct file + } + } + if (found) { +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + return -EINVAL; + } + +@@ -1549,10 +1551,10 @@ static ssize_t efivar_create(struct file + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", + status); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + return -EIO; + } +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + /* Create the entry in sysfs. Locking is not required here */ + status = efivar_create_sysfs_entry(efivars, +@@ -1580,7 +1582,7 @@ static ssize_t efivar_delete(struct file + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + + /* + * Does this variable already exist? +@@ -1598,7 +1600,7 @@ static ssize_t efivar_delete(struct file + } + } + if (!found) { +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + return -EINVAL; + } + /* force the Attributes/DataSize to 0 to ensure deletion */ +@@ -1614,12 +1616,12 @@ static ssize_t efivar_delete(struct file + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", + status); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + return -EIO; + } + list_del(&search_efivar->list); + /* We need to release this lock before unregistering. */ +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + efivar_unregister(search_efivar); + + /* It's dead Jim.... */ +@@ -1734,9 +1736,9 @@ efivar_create_sysfs_entry(struct efivars + kfree(short_name); + short_name = NULL; + +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + list_add(&new_efivar->list, &efivars->list); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + + return 0; + } +@@ -1805,9 +1807,9 @@ void unregister_efivars(struct efivars * + struct efivar_entry *entry, *n; + + list_for_each_entry_safe(entry, n, &efivars->list, list) { +- spin_lock(&efivars->lock); ++ spin_lock_irq(&efivars->lock); + list_del(&entry->list); +- spin_unlock(&efivars->lock); ++ spin_unlock_irq(&efivars->lock); + efivar_unregister(entry); + } + if (efivars->new_var) diff --git a/queue-3.8/series b/queue-3.8/series index 039df3cc333..f5da0f53e62 100644 --- a/queue-3.8/series +++ b/queue-3.8/series @@ -90,3 +90,9 @@ arm-mxs_defconfig-make-usb-host-functional-again.patch arm-kirkwood-of_serial-fix-clock-gating-by-removing-clock-frequency.patch powerpc-apply-early-paca-fixups-to-boot_paca-and-the-boot-cpu-s-paca.patch ftrace-update-the-kconfig-for-dynamic_ftrace.patch +efivars-disable-external-interrupt-while-holding-efivars-lock.patch +efi-be-more-paranoid-about-available-space-when-creating-variables.patch +usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch +usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch +usb-rip-out-recursive-call-on-warm-port-reset.patch +usb-fix-connected-device-switch-to-inactive-state.patch diff --git a/queue-3.8/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch b/queue-3.8/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch new file mode 100644 index 00000000000..b859916bb42 --- /dev/null +++ b/queue-3.8/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch @@ -0,0 +1,66 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 14:57:57 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:23:30 -0800 +Subject: USB: Don't use EHCI port sempahore for USB 3.0 hubs. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002330.GA13281@xanatos> + +From: Sarah Sharp + +[This is upstream commit 0fe51aa5eee51db7c7ecd201d42a977ad79c58b6. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 65bdac5effd15d6af619b3b7218627ef4d84ed6a "USB: Handle warm +reset failure on empty port."] + +The EHCI host controller needs to prevent EHCI initialization when the +UHCI or OHCI companion controller is in the middle of a port reset. It +uses ehci_cf_port_reset_rwsem to do this. USB 3.0 hubs can't be under +an EHCI host controller, so it makes no sense to down the semaphore for +USB 3.0 hubs. It also makes the warm port reset code more complex. + +Don't down ehci_cf_port_reset_rwsem for USB 3.0 hubs. + +Signed-off-by: Sarah Sharp +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2661,17 +2661,16 @@ static int hub_port_reset(struct usb_hub + { + int i, status; + +- if (!warm) { +- /* Block EHCI CF initialization during the port reset. +- * Some companion controllers don't like it when they mix. +- */ +- down_read(&ehci_cf_port_reset_rwsem); +- } else { +- if (!hub_is_superspeed(hub->hdev)) { ++ if (!hub_is_superspeed(hub->hdev)) { ++ if (warm) { + dev_err(hub->intfdev, "only USB3 hub support " + "warm reset\n"); + return -EINVAL; + } ++ /* Block EHCI CF initialization during the port reset. ++ * Some companion controllers don't like it when they mix. ++ */ ++ down_read(&ehci_cf_port_reset_rwsem); + } + + /* Reset the port */ +@@ -2709,7 +2708,7 @@ static int hub_port_reset(struct usb_hub + port1); + + done: +- if (!warm) ++ if (!hub_is_superspeed(hub->hdev)) + up_read(&ehci_cf_port_reset_rwsem); + + return status; diff --git a/queue-3.8/usb-fix-connected-device-switch-to-inactive-state.patch b/queue-3.8/usb-fix-connected-device-switch-to-inactive-state.patch new file mode 100644 index 00000000000..ffe0c81d24a --- /dev/null +++ b/queue-3.8/usb-fix-connected-device-switch-to-inactive-state.patch @@ -0,0 +1,91 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 14:59:14 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:23:43 -0800 +Subject: USB: Fix connected device switch to Inactive state. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002343.GA13329@xanatos> + +From: Sarah Sharp + +[This is upstream commit d3b9d7a9051d7024a93c76a84b2f84b3b66ad6d5. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 65bdac5effd15d6af619b3b7218627ef4d84ed6a "USB: Handle warm +reset failure on empty port."] + +A USB 3.0 device can transition to the Inactive state if a U1 or U2 exit +transition fails. The current code in hub_events simply issues a warm +reset, but does not call any pre-reset or post-reset driver methods (or +unbind/rebind drivers without them). Therefore the drivers won't know +their device has just been reset. + +hub_events should instead call usb_reset_device. This means +hub_port_reset now needs to figure out whether it should issue a warm +reset or a hot reset. + +Remove the FIXME note about needing disconnect() for a NOTATTACHED +device. This patch fixes that. + +Signed-off-by: Sarah Sharp +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 30 +++++++++++++++++++++++++----- + 1 file changed, 25 insertions(+), 5 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2605,7 +2605,6 @@ static void hub_port_finish_reset(struct + case -ENODEV: + clear_port_feature(hub->hdev, + port1, USB_PORT_FEAT_C_RESET); +- /* FIXME need disconnect() for NOTATTACHED device */ + if (hub_is_superspeed(hub->hdev)) { + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_BH_PORT_RESET); +@@ -2639,6 +2638,18 @@ static int hub_port_reset(struct usb_hub + * Some companion controllers don't like it when they mix. + */ + down_read(&ehci_cf_port_reset_rwsem); ++ } else if (!warm) { ++ /* ++ * If the caller hasn't explicitly requested a warm reset, ++ * double check and see if one is needed. ++ */ ++ status = hub_port_status(hub, port1, ++ &portstatus, &portchange); ++ if (status < 0) ++ goto done; ++ ++ if (hub_port_warm_reset_required(hub, portstatus)) ++ warm = true; + } + + /* Reset the port */ +@@ -4730,12 +4741,21 @@ static void hub_events(void) + */ + if (hub_port_warm_reset_required(hub, portstatus)) { + int status; ++ struct usb_device *udev = ++ hub->ports[i - 1]->child; + + dev_dbg(hub_dev, "warm reset port %d\n", i); +- status = hub_port_reset(hub, i, NULL, +- HUB_BH_RESET_TIME, true); +- if (status < 0) +- hub_port_disable(hub, i, 1); ++ if (!udev) { ++ status = hub_port_reset(hub, i, ++ NULL, HUB_BH_RESET_TIME, ++ true); ++ if (status < 0) ++ hub_port_disable(hub, i, 1); ++ } else { ++ usb_lock_device(udev); ++ status = usb_reset_device(udev); ++ usb_unlock_device(udev); ++ } + connect_change = 0; + } + diff --git a/queue-3.8/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch b/queue-3.8/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch new file mode 100644 index 00000000000..32a7545bb0d --- /dev/null +++ b/queue-3.8/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch @@ -0,0 +1,75 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 14:58:36 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:23:34 -0800 +Subject: USB: Prepare for refactoring by adding extra udev checks. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002334.GA13297@xanatos> + +From: Sarah Sharp + +[This is upstream commit 2d4fa940f99663c82ba55b2244638833b388e4e2. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 65bdac5effd15d6af619b3b7218627ef4d84ed6a "USB: Handle warm +reset failure on empty port."] + +The next patch will refactor the hub port code to rip out the recursive +call to hub_port_reset on a failed hot reset. In preparation for that, +make sure all code paths can deal with being called with a NULL udev. +The usb_device will not be valid if warm reset was issued because a port +transitioned to the Inactive or Compliance Mode on a device connect. + +This patch should have no effect on current behavior. + +Signed-off-by: Sarah Sharp +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 21 +++++++++++++-------- + 1 file changed, 13 insertions(+), 8 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2584,6 +2584,9 @@ static int hub_port_wait_reset(struct us + return -ENOTCONN; + + if ((portstatus & USB_PORT_STAT_ENABLE)) { ++ if (!udev) ++ return 0; ++ + if (hub_is_wusb(hub)) + udev->speed = USB_SPEED_WIRELESS; + else if (hub_is_superspeed(hub->hdev)) +@@ -2627,13 +2630,15 @@ static void hub_port_finish_reset(struct + struct usb_hcd *hcd; + /* TRSTRCY = 10 ms; plus some extra */ + msleep(10 + 40); +- update_devnum(udev, 0); +- hcd = bus_to_hcd(udev->bus); +- /* The xHC may think the device is already reset, +- * so ignore the status. +- */ +- if (hcd->driver->reset_device) +- hcd->driver->reset_device(hcd, udev); ++ if (udev) { ++ update_devnum(udev, 0); ++ hcd = bus_to_hcd(udev->bus); ++ /* The xHC may think the device is already ++ * reset, so ignore the status. ++ */ ++ if (hcd->driver->reset_device) ++ hcd->driver->reset_device(hcd, udev); ++ } + } + /* FALL THROUGH */ + case -ENOTCONN: +@@ -2647,7 +2652,7 @@ static void hub_port_finish_reset(struct + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } +- if (!warm) ++ if (!warm && udev) + usb_set_device_state(udev, *status + ? USB_STATE_NOTATTACHED + : USB_STATE_DEFAULT); diff --git a/queue-3.8/usb-rip-out-recursive-call-on-warm-port-reset.patch b/queue-3.8/usb-rip-out-recursive-call-on-warm-port-reset.patch new file mode 100644 index 00000000000..6300ac82fa8 --- /dev/null +++ b/queue-3.8/usb-rip-out-recursive-call-on-warm-port-reset.patch @@ -0,0 +1,280 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 14:58:54 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:23:39 -0800 +Subject: USB: Rip out recursive call on warm port reset. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002339.GA13313@xanatos> + +From: Sarah Sharp + +[This is upstream commit a24a6078754f28528bc91e7e7b3e6ae86bd936d8. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 65bdac5effd15d6af619b3b7218627ef4d84ed6a "USB: Handle warm +reset failure on empty port."] + +When a hot reset fails on a USB 3.0 port, the current port reset code +recursively calls hub_port_reset inside hub_port_wait_reset. This isn't +ideal, since we should avoid recursive calls in the kernel, and it also +doesn't allow us to issue multiple warm resets on reset failures. + +Rip out the recursive call. Instead, add code to hub_port_reset to +issue a warm reset if the hot reset fails, and try multiple warm resets +before giving up on the port. + +In hub_port_wait_reset, remove the recursive call and re-indent. The +code is basically the same, except: + +1. It bails out early if the port has transitioned to Inactive or +Compliance Mode after the reset completed. + +2. It doesn't consider a connect status change to be a failed reset. If +multiple warm resets needed to be issued, the connect status may have +changed, so we need to ignore that and look at the port link state +instead. hub_port_reset will now do that. + +3. It unconditionally sets udev->speed on all types of successful +resets. The old recursive code would set the port speed when the second +hub_port_reset returned. + +The old code did not handle connected devices needing a warm reset well. +There were only two situations that the old code handled correctly: an +empty port needing a warm reset, and a hot reset that migrated to a warm +reset. + +When an empty port needed a warm reset, hub_port_reset was called with +the warm variable set. The code in hub_port_finish_reset would skip +telling the USB core and the xHC host that the device was reset, because +otherwise that would result in a NULL pointer dereference. + +When a USB 3.0 device reset migrated to a warm reset, the recursive call +made the call stack look like this: + +hub_port_reset(warm = false) + hub_wait_port_reset(warm = false) + hub_port_reset(warm = true) + hub_wait_port_reset(warm = true) + hub_port_finish_reset(warm = true) + (return up the call stack to the first wait) + + hub_port_finish_reset(warm = false) + +The old code didn't want to notify the USB core or the xHC host of device reset +twice, so it only did it in the second call to hub_port_finish_reset, +when warm was set to false. This was necessary because +before patch two ("USB: Ignore xHCI Reset Device status."), the USB core +would pay attention to the xHC Reset Device command error status, and +the second call would always fail. + +Now that we no longer have the recursive call, and warm can change from +false to true in hub_port_reset, we need to have hub_port_finish_reset +unconditionally notify the USB core and the xHC of the device reset. + +In hub_port_finish_reset, unconditionally clear the connect status +change (CSC) bit for USB 3.0 hubs when the port reset is done. If we +had to issue multiple warm resets for a device, that bit may have been +set if the device went into SS.Inactive and then was successfully warm +reset. + +Signed-off-by: Sarah Sharp +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 148 ++++++++++++++++++++++--------------------------- + 1 file changed, 67 insertions(+), 81 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2538,73 +2538,35 @@ static int hub_port_wait_reset(struct us + if ((portstatus & USB_PORT_STAT_RESET)) + goto delay; + +- /* +- * Some buggy devices require a warm reset to be issued even +- * when the port appears not to be connected. ++ if (hub_port_warm_reset_required(hub, portstatus)) ++ return -ENOTCONN; ++ ++ /* Device went away? */ ++ if (!(portstatus & USB_PORT_STAT_CONNECTION)) ++ return -ENOTCONN; ++ ++ /* bomb out completely if the connection bounced. A USB 3.0 ++ * connection may bounce if multiple warm resets were issued, ++ * but the device may have successfully re-connected. Ignore it. + */ +- if (!warm) { +- /* +- * Some buggy devices can cause an NEC host controller +- * to transition to the "Error" state after a hot port +- * reset. This will show up as the port state in +- * "Inactive", and the port may also report a +- * disconnect. Forcing a warm port reset seems to make +- * the device work. +- * +- * See https://bugzilla.kernel.org/show_bug.cgi?id=41752 +- */ +- if (hub_port_warm_reset_required(hub, portstatus)) { +- int ret; ++ if (!hub_is_superspeed(hub->hdev) && ++ (portchange & USB_PORT_STAT_C_CONNECTION)) ++ return -ENOTCONN; + +- if ((portchange & USB_PORT_STAT_C_CONNECTION)) +- clear_port_feature(hub->hdev, port1, +- USB_PORT_FEAT_C_CONNECTION); +- if (portchange & USB_PORT_STAT_C_LINK_STATE) +- clear_port_feature(hub->hdev, port1, +- USB_PORT_FEAT_C_PORT_LINK_STATE); +- if (portchange & USB_PORT_STAT_C_RESET) +- clear_port_feature(hub->hdev, port1, +- USB_PORT_FEAT_C_RESET); +- dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n", +- port1); +- ret = hub_port_reset(hub, port1, +- udev, HUB_BH_RESET_TIME, +- true); +- if ((portchange & USB_PORT_STAT_C_CONNECTION)) +- clear_port_feature(hub->hdev, port1, +- USB_PORT_FEAT_C_CONNECTION); +- return ret; +- } +- /* Device went away? */ +- if (!(portstatus & USB_PORT_STAT_CONNECTION)) +- return -ENOTCONN; +- +- /* bomb out completely if the connection bounced */ +- if ((portchange & USB_PORT_STAT_C_CONNECTION)) +- return -ENOTCONN; +- +- if ((portstatus & USB_PORT_STAT_ENABLE)) { +- if (!udev) +- return 0; +- +- if (hub_is_wusb(hub)) +- udev->speed = USB_SPEED_WIRELESS; +- else if (hub_is_superspeed(hub->hdev)) +- udev->speed = USB_SPEED_SUPER; +- else if (portstatus & USB_PORT_STAT_HIGH_SPEED) +- udev->speed = USB_SPEED_HIGH; +- else if (portstatus & USB_PORT_STAT_LOW_SPEED) +- udev->speed = USB_SPEED_LOW; +- else +- udev->speed = USB_SPEED_FULL; ++ if ((portstatus & USB_PORT_STAT_ENABLE)) { ++ if (!udev) + return 0; +- } +- } else { +- if (!(portstatus & USB_PORT_STAT_CONNECTION) || +- hub_port_warm_reset_required(hub, +- portstatus)) +- return -ENOTCONN; + ++ if (hub_is_wusb(hub)) ++ udev->speed = USB_SPEED_WIRELESS; ++ else if (hub_is_superspeed(hub->hdev)) ++ udev->speed = USB_SPEED_SUPER; ++ else if (portstatus & USB_PORT_STAT_HIGH_SPEED) ++ udev->speed = USB_SPEED_HIGH; ++ else if (portstatus & USB_PORT_STAT_LOW_SPEED) ++ udev->speed = USB_SPEED_LOW; ++ else ++ udev->speed = USB_SPEED_FULL; + return 0; + } + +@@ -2622,23 +2584,21 @@ delay: + } + + static void hub_port_finish_reset(struct usb_hub *hub, int port1, +- struct usb_device *udev, int *status, bool warm) ++ struct usb_device *udev, int *status) + { + switch (*status) { + case 0: +- if (!warm) { +- struct usb_hcd *hcd; +- /* TRSTRCY = 10 ms; plus some extra */ +- msleep(10 + 40); +- if (udev) { +- update_devnum(udev, 0); +- hcd = bus_to_hcd(udev->bus); +- /* The xHC may think the device is already +- * reset, so ignore the status. +- */ +- if (hcd->driver->reset_device) +- hcd->driver->reset_device(hcd, udev); +- } ++ /* TRSTRCY = 10 ms; plus some extra */ ++ msleep(10 + 40); ++ if (udev) { ++ struct usb_hcd *hcd = bus_to_hcd(udev->bus); ++ ++ update_devnum(udev, 0); ++ /* The xHC may think the device is already reset, ++ * so ignore the status. ++ */ ++ if (hcd->driver->reset_device) ++ hcd->driver->reset_device(hcd, udev); + } + /* FALL THROUGH */ + case -ENOTCONN: +@@ -2651,8 +2611,10 @@ static void hub_port_finish_reset(struct + USB_PORT_FEAT_C_BH_PORT_RESET); + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); ++ clear_port_feature(hub->hdev, port1, ++ USB_PORT_FEAT_C_CONNECTION); + } +- if (!warm && udev) ++ if (udev) + usb_set_device_state(udev, *status + ? USB_STATE_NOTATTACHED + : USB_STATE_DEFAULT); +@@ -2665,6 +2627,7 @@ static int hub_port_reset(struct usb_hub + struct usb_device *udev, unsigned int delay, bool warm) + { + int i, status; ++ u16 portchange, portstatus; + + if (!hub_is_superspeed(hub->hdev)) { + if (warm) { +@@ -2696,10 +2659,33 @@ static int hub_port_reset(struct usb_hub + status); + } + +- /* return on disconnect or reset */ ++ /* Check for disconnect or reset */ + if (status == 0 || status == -ENOTCONN || status == -ENODEV) { +- hub_port_finish_reset(hub, port1, udev, &status, warm); +- goto done; ++ hub_port_finish_reset(hub, port1, udev, &status); ++ ++ if (!hub_is_superspeed(hub->hdev)) ++ goto done; ++ ++ /* ++ * If a USB 3.0 device migrates from reset to an error ++ * state, re-issue the warm reset. ++ */ ++ if (hub_port_status(hub, port1, ++ &portstatus, &portchange) < 0) ++ goto done; ++ ++ if (!hub_port_warm_reset_required(hub, portstatus)) ++ goto done; ++ ++ /* ++ * If the port is in SS.Inactive or Compliance Mode, the ++ * hot or warm reset failed. Try another warm reset. ++ */ ++ if (!warm) { ++ dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n", ++ port1); ++ warm = true; ++ } + } + + dev_dbg (hub->intfdev, -- 2.47.3