--- /dev/null
+From 68d929862e29a8b52a7f2f2f86a0600423b093cd Mon Sep 17 00:00:00 2001
+From: Matthew Garrett <matthew.garrett@nebula.com>
+Date: Sat, 2 Mar 2013 19:40:17 -0500
+Subject: efi: be more paranoid about available space when creating variables
+
+From: Matthew Garrett <matthew.garrett@nebula.com>
+
+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 <matthew.garrett@nebula.com>
+Cc: Josh Boyer <jwboyer@redhat.com>
+Signed-off-by: Matt Fleming <matt.fleming@intel.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ 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,
--- /dev/null
+From 81fa4e581d9283f7992a0d8c534bb141eb840a14 Mon Sep 17 00:00:00 2001
+From: Seiji Aguchi <seiji.aguchi@hds.com>
+Date: Tue, 12 Feb 2013 12:59:07 -0800
+Subject: efivars: Disable external interrupt while holding efivars->lock
+
+From: Seiji Aguchi <seiji.aguchi@hds.com>
+
+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 <seiji.aguchi@hds.com>
+Acked-by: Mike Waychison <mikew@google.com>
+Acked-by: Matt Fleming <matt.fleming@intel.com>
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Cc: Josh Boyer <jwboyer@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+
+---
+ 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)
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
--- /dev/null
+From sarah.a.sharp@linux.intel.com Tue Mar 12 14:57:57 2013
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+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 <gregkh@linuxfoundation.org>
+Cc: stable@vger.kernel.org
+Message-ID: <20130308002330.GA13281@xanatos>
+
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+
+[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 <sarah.a.sharp@linux.intel.com>
+Acked-by: Alan Stern <stern@rowland.harvard.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ 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;
--- /dev/null
+From sarah.a.sharp@linux.intel.com Tue Mar 12 14:59:14 2013
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+Date: Thu, 7 Mar 2013 16:23:43 -0800
+Subject: USB: Fix connected device switch to Inactive state.
+To: Greg KH <gregkh@linuxfoundation.org>
+Cc: stable@vger.kernel.org
+Message-ID: <20130308002343.GA13329@xanatos>
+
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+
+[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 <sarah.a.sharp@linux.intel.com>
+Acked-by: Alan Stern <stern@rowland.harvard.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ 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;
+ }
+
--- /dev/null
+From sarah.a.sharp@linux.intel.com Tue Mar 12 14:58:36 2013
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+Date: Thu, 7 Mar 2013 16:23:34 -0800
+Subject: USB: Prepare for refactoring by adding extra udev checks.
+To: Greg KH <gregkh@linuxfoundation.org>
+Cc: stable@vger.kernel.org
+Message-ID: <20130308002334.GA13297@xanatos>
+
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+
+[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 <sarah.a.sharp@linux.intel.com>
+Acked-by: Alan Stern <stern@rowland.harvard.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ 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);
--- /dev/null
+From sarah.a.sharp@linux.intel.com Tue Mar 12 14:58:54 2013
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+Date: Thu, 7 Mar 2013 16:23:39 -0800
+Subject: USB: Rip out recursive call on warm port reset.
+To: Greg KH <gregkh@linuxfoundation.org>
+Cc: stable@vger.kernel.org
+Message-ID: <20130308002339.GA13313@xanatos>
+
+From: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+
+[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 <sarah.a.sharp@linux.intel.com>
+Acked-by: Alan Stern <stern@rowland.harvard.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ 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,