diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2011-10-15 12:14:39 -0400 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2011-10-18 06:32:39 -0400 |
commit | 32eaeae177bf77fbc224c35262add45bd5e6abb3 (patch) | |
tree | f20496f4be3f7e164bb995a88191e91fa902e9cc /drivers/firewire | |
parent | a74477db9171e677b7a37b89e6e0ac8a15ba1f26 (diff) |
firewire: ohci: work around selfID junk due to wrong gap count
If a device's firmware initiates a bus reset by setting the IBR bit in
PHY register 1 without resetting the gap count field to 63 (and without
having sent a PHY configuration packet beforehand), the gap count of
this node will remain at the old value after the bus reset and thus be
inconsistent with the gap count on all other nodes.
The bus manager is supposed to detect the inconsistent gap count values
in the self ID packets and correct them by issuing another bus reset.
However, if the buggy device happens to be the cycle master, and if it
sends a cycle start packet immediately after the bus reset (which is
likely after a long bus reset), then the time between the end of the
selfID phase and the start of the cycle start packet will be based on
the too-small gap count value, so this gap will be too short to be
detected as a subaction gap by the other nodes. This means that the
cycle start packet will be assumed to be self ID data, and will be
stored after the actual self ID quadlets in the self ID buffer.
This garbage in the self ID buffer made firewire-core ignore all of the
self ID data, and thus prevented the Linux bus manager from correcting
the problem. Furthermore, because the bus reset handling was aborted
completely, asynchronous transfers would be no longer handled correctly,
and fw_run_transaction() would hang until the next bus reset.
To fix this, make the detection of inconsistent self IDs more
discriminating: If the invalid data in the self ID buffer looks like
a cycle start packet, we can assume that the previous data in the buffer
is correctly received self ID information, and process it normally.
(We inspect only the first quadlet of the cycle start packet, because
this value is different enough from any valid self ID quadlet, and many
controllers do not store the cycle start packet in five quadlets because
they expect self ID data to have an even number of quadlets.)
This bug has been observed when a bus-powered DesktopKonnekt6 is
switched off with its power button.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire')
-rw-r--r-- | drivers/firewire/ohci.c | 18 |
1 files changed, 16 insertions, 2 deletions
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index bffc2ad7ecab..b6977149394e 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c | |||
@@ -1860,8 +1860,22 @@ static void bus_reset_work(struct work_struct *work) | |||
1860 | 1860 | ||
1861 | for (i = 1, j = 0; j < self_id_count; i += 2, j++) { | 1861 | for (i = 1, j = 0; j < self_id_count; i += 2, j++) { |
1862 | if (ohci->self_id_cpu[i] != ~ohci->self_id_cpu[i + 1]) { | 1862 | if (ohci->self_id_cpu[i] != ~ohci->self_id_cpu[i + 1]) { |
1863 | fw_notify("inconsistent self IDs\n"); | 1863 | /* |
1864 | return; | 1864 | * If the invalid data looks like a cycle start packet, |
1865 | * it's likely to be the result of the cycle master | ||
1866 | * having a wrong gap count. In this case, the self IDs | ||
1867 | * so far are valid and should be processed so that the | ||
1868 | * bus manager can then correct the gap count. | ||
1869 | */ | ||
1870 | if (cond_le32_to_cpu(ohci->self_id_cpu[i]) | ||
1871 | == 0xffff008f) { | ||
1872 | fw_notify("ignoring spurious self IDs\n"); | ||
1873 | self_id_count = j; | ||
1874 | break; | ||
1875 | } else { | ||
1876 | fw_notify("inconsistent self IDs\n"); | ||
1877 | return; | ||
1878 | } | ||
1865 | } | 1879 | } |
1866 | ohci->self_id_buffer[j] = | 1880 | ohci->self_id_buffer[j] = |
1867 | cond_le32_to_cpu(ohci->self_id_cpu[i]); | 1881 | cond_le32_to_cpu(ohci->self_id_cpu[i]); |