
                       PCI Error Recovery
                       ------------------
                         May 31, 2005

               Current document maintainer:
           Linas Vepstas <linas@austin.ibm.com>


Some PCI bus controllers are able to detect certain "hard" PCI errors
on the bus, such as parity errors on the data and address busses, as
well as SERR and PERR errors.  These chipsets are then able to disable
I/O to/from the affected device, so that, for example, a bad DMA
address doesn't end up corrupting system memory.  These same chipsets
are also able to reset the affected PCI device, and return it to
working condition.  This document describes a generic API form
performing error recovery.

The core idea is that after a PCI error has been detected, there must
be a way for the kernel to coordinate with all affected device drivers
so that the pci card can be made operational again, possibly after
performing a full electrical #RST of the PCI card.  The API below
provides a generic API for device drivers to be notified of PCI
errors, and to be notified of, and respond to, a reset sequence.

Preliminary sketch of API, cut-n-pasted-n-modified email from
Ben Herrenschmidt, circa 5 april 2005

The error recovery API support is exposed to the driver in the form of
a structure of function pointers pointed to by a new field in struct
pci_driver. The absence of this pointer in pci_driver denotes an
"non-aware" driver, behaviour on these is platform dependant.
Platforms like ppc64 can try to simulate pci hotplug remove/add.

The definition of "pci_error_token" is not covered here. It is based on
Seto's work on the synchronous error detection. We still need to define
functions for extracting infos out of an opaque error token. This is
separate from this API.

This structure has the form:

struct pci_error_handlers
{
	int (*error_detected)(struct pci_dev *dev, pci_error_token error);
	int (*mmio_enabled)(struct pci_dev *dev);
	int (*resume)(struct pci_dev *dev);
	int (*link_reset)(struct pci_dev *dev);
	int (*slot_reset)(struct pci_dev *dev);
};

A driver doesn't have to implement all of these callbacks. The
only mandatory one is error_detected(). If a callback is not
implemented, the corresponding feature is considered unsupported.
For example, if mmio_enabled() and resume() aren't there, then the
driver is assumed as not doing any direct recovery and requires
a reset. If link_reset() is not implemented, the card is assumed as
not caring about link resets, in which case, if recover is supported,
the core can try recover (but not slot_reset() unless it really did
reset the slot). If slot_reset() is not supported, link_reset() can
be called instead on a slot reset.

At first, the call will always be :

	1) error_detected()

	Error detected. This is sent once after an error has been detected. At
this point, the device might not be accessible anymore depending on the
platform (the slot will be isolated on ppc64). The driver may already
have "noticed" the error because of a failing IO, but this is the proper
"synchronisation point", that is, it gives a chance to the driver to
cleanup, waiting for pending stuff (timers, whatever, etc...) to
complete; it can take semaphores, schedule, etc... everything but touch
the device. Within this function and after it returns, the driver
shouldn't do any new IOs. Called in task context. This is sort of a
"quiesce" point. See note about interrupts at the end of this doc.

	Result codes:
		- PCIERR_RESULT_CAN_RECOVER:
		  Driever returns this if it thinks it might be able to recover
		  the HW by just banging IOs or if it wants to be given
		  a chance to extract some diagnostic informations (see
		  below).
		- PCIERR_RESULT_NEED_RESET:
		  Driver returns this if it thinks it can't recover unless the
		  slot is reset.
		- PCIERR_RESULT_DISCONNECT:
		  Return this if driver thinks it won't recover at all,
		  (this will detach the driver ? or just leave it
		  dangling ? to be decided)

So at this point, we have called error_detected() for all drivers
on the segment that had the error. On ppc64, the slot is isolated. What
happens now typically depends on the result from the drivers. If all
drivers on the segment/slot return PCIERR_RESULT_CAN_RECOVER, we would
re-enable IOs on the slot (or do nothing special if the platform doesn't
isolate slots) and call 2). If not and we can reset slots, we go to 4),
if neither, we have a dead slot. If it's an hotplug slot, we might
"simulate" reset by triggering HW unplug/replug though.

>>> Current ppc64 implementation assumes that a device driver will
>>> *not* schedule or semaphore in this routine; the current ppc64
>>> implementation uses one kernel thread to notify all devices;
>>> thus, of one device sleeps/schedules, all devices are affected.
>>> Doing better requires complex multi-threaded logic in the error
>>> recovery implementation (e.g. waiting for all notification threads
>>> to "join" before proceeding with recovery.)  This seems excessively
>>> complex and not worth implementing.

>>> The current ppc64 implementation doesn't much care if the device
>>> attempts i/o at this point, or not.  I/O's will fail, returning
>>> a value of 0xff on read, and writes will be dropped. If the device
>>> driver attempts more than 10K I/O's to a frozen adapter, it will
>>> assume that the device driver has gone into an infinite loop, and
>>> it will panic the the kernel.

	2) mmio_enabled()

	This is the "early recovery" call. IOs are allowed again, but DMA is
not (hrm... to be discussed, I prefer not), with some restrictions. This
is NOT a callback for the driver to start operations again, only to
peek/poke at the device, extract diagnostic information, if any, and
eventually do things like trigger a device local reset or some such,
but not restart operations. This is sent if all drivers on a segment
agree that they can try to recover and no automatic link reset was
performed by the HW. If the platform can't just re-enable IOs without
a slot reset or a link reset, it doesn't call this callback and goes
directly to 3) or 4). All IOs should be done _synchronously_ from
within this callback, errors triggered by them will be returned via
the normal pci_check_whatever() api, no new error_detected() callback
will be issued due to an error happening here. However, such an error
might cause IOs to be re-blocked for the whole segment, and thus
invalidate the recovery that other devices on the same segment might
have done, forcing the whole segment into one of the next states,
that is link reset or slot reset.

	Result codes:
		- PCIERR_RESULT_RECOVERED
		  Driver returns this if it thinks the device is fully
		  functionnal and thinks it is ready to start
		  normal driver operations again. There is no
		  guarantee that the driver will actually be
		  allowed to proceed, as another driver on the
		  same segment might have failed and thus triggered a
		  slot reset on platforms that support it.

		- PCIERR_RESULT_NEED_RESET
		  Driver returns this if it thinks the device is not
		  recoverable in it's current state and it needs a slot
		  reset to proceed.

		- PCIERR_RESULT_DISCONNECT
		  Same as above. Total failure, no recovery even after
		  reset driver dead. (To be defined more precisely)

>>> The current ppc64 implementation does not implement this callback.

	3) link_reset()

	This is called after the link has been reset. This is typically
a PCI Express specific state at this point and is done whenever a
non-fatal error has been detected that can be "solved" by resetting
the link. This call informs the driver of the reset and the driver
should check if the device appears to be in working condition.
This function acts a bit like 2) mmio_enabled(), in that the driver
is not supposed to restart normal driver I/O operations right away.
Instead, it should just "probe" the device to check it's recoverability
status. If all is right, then the core will call resume() once all
drivers have ack'd link_reset().

	Result codes:
		(identical to mmio_enabled)

>>> The current ppc64 implementation does not implement this callback.

	4) slot_reset()

	This is called after the slot has been soft or hard reset by the
platform.  A soft reset consists of asserting the adapter #RST line
and then restoring the PCI BARs and PCI configuration header. If the
platform supports PCI hotplug, then it might instead perform a hard
reset by toggling power on the slot off/on. This call gives drivers
the chance to re-initialize the hardware (re-download firmware, etc.),
but drivers shouldn't restart normal I/O processing operations at
this point.  (See note about interrupts; interrupts aren't guaranteed
to be delivered until the resume() callback has been called). If all
device drivers report success on this callback, the patform will call
resume() to complete the error handling and let the driver restart
normal I/O processing.

A driver can still return a critical failure for this function if
it can't get the device operational after reset.  If the platform
previously tried a soft reset, it migh now try a hard reset (power
cycle) and then call slot_reset() again.  It the device still can't
be recovered, there is nothing more that can be done;  the platform
will typically report a "permanent failure" in such a case.  The
device will be considered "dead" in this case.

	Result codes:
		- PCIERR_RESULT_DISCONNECT
		Same as above.

>>> The current ppc64 implementation does not try a power-cycle reset
>>> if the driver returned PCIERR_RESULT_DISCONNECT. However, it should.

	5) resume()

	This is called if all drivers on the segment have returned
PCIERR_RESULT_RECOVERED from one of the 3 prevous callbacks.
That basically tells the driver to restart activity, tht everything
is back and running. No result code is taken into account here. If
a new error happens, it will restart a new error handling process.

That's it. I think this covers all the possibilities. The way those
callbacks are called is platform policy. A platform with no slot reset
capability for example may want to just "ignore" drivers that can't
recover (disconnect them) and try to let other cards on the same segment
recover. Keep in mind that in most real life cases, though, there will
be only one driver per segment.

Now, there is a note about interrupts. If you get an interrupt and your
device is dead or has been isolated, there is a problem :)

After much thinking, I decided to leave that to the platform. That is,
the recovery API only precies that:

 - There is no guarantee that interrupt delivery can proceed from any
device on the segment starting from the error detection and until the
restart callback is sent, at which point interrupts are expected to be
fully operational.

 - There is no guarantee that interrupt delivery is stopped, that is, ad
river that gets an interrupts after detecting an error, or that detects
and error within the interrupt handler such that it prevents proper
ack'ing of the interrupt (and thus removal of the source) should just
return IRQ_NOTHANDLED. It's up to the platform to deal with taht
condition, typically by masking the irq source during the duration of
the error handling. It is expected that the platform "knows" which
interrupts are routed to error-management capable slots and can deal
with temporarily disabling that irq number during error processing (this
isn't terribly complex). That means some IRQ latency for other devices
sharing the interrupt, but there is simply no other way. High end
platforms aren't supposed to share interrupts between many devices
anyway :)


Revised: 31 May 2005 Linas Vepstas <linas@austin.ibm.com>
