xenocara/xserver/hw/xfree86/doc/devel/RAC.Notes
2006-11-26 18:13:41 +00:00

697 lines
29 KiB
Plaintext

I. Abstract
===========
Graphics devices are accessed thru ranges in I/O or memory space. While
most modern graphics devices allow relocation of such ranges many of
them still require the use of well established interfaces such as VGA
memory and IO ranges or 8514/A IO ranges. Up to version 3.3 of
XFree86 only a single graphics device could be driven. Therfore there
was no need to address the issue of sharing such memory or I/O ranges
among several devices. Starting with version 4.0 XFree86 is capable of
driving more than one graphics interface in a multi-head environment.
Therefore a mechanism needed to be designed which was capable of
controlling the sharing the access to memory and I/O ranges. In this
document we describe to use of the RAC (Resource Access Control)
system in the XFree86 server which provides the service of controlling
access to interface resources.
II. Introduction
================
Terms and definitions:
II.1. Bus
---------
'Bus' is ambiguous as it is used for different things: It may refer to
physical incompatible extension connectors in a computer system. The
RAC system knows two such systems: The ISA bus and the PCI bus. (On
the software level EISA, MC and VL buses are currently treated like
ISA buses). 'Bus' may always refer to logically different entities on
a single bus system which are connected via bridges. A PCI system may
have several distinct PCI buses connecting each other by PCI-PCI
bridges or to the host CPU by HOST-PCI bridges.
Systems that host more than one bus system link these together using
bridges. Bridges are a concern to RAC as they might block or pass
specific resources. PCI-PCI bridges may be set up to pass VGA
resources to the secondary bus. PCI-ISA buses pass any resources not
decoded on the primary PCI bus to the ISA bus. This way VGA resources
(although exclusive on the ISA bus) can be shared by ISA and PCI
cards. Currently HOST-PCI bridges are not yet handled by RACY as they
require specific drivers.
II.2. Entity
------------
The smallest independently addressable unit on a system bus is
referred to as an entity. So far we know ISA and PCI entities. PCI
entities can be located on the PCI bus by an unique ID consisting of
the bus, card and function number.
II.3. Resource
--------------
'Resource' refers to a range of memory or I/O addresses an entity
can decode.
If a device is capable of disabling this decoding the resource is
called sharable. For PCI devices a generic method is provided to
control resource decoding. Other devices will have to provide a device
specific function to control decoding.
If the entity is capable of decoding this range at a different
location this resource is considered relocatable. Resource which start
at a specific address and occupy a single continuous range are called
block resources.
Alternatively resource addresses can be decoded in a way that they
satisfy the condition:
address & mask == base
with base & mask == base. Resources addressed in such a way are
considered sparse resources.
II.4. Server States
------------------
The resource access control system knows two server states: the SETUP
and the OPERATING state. The setup state is entered whenever a mode
change takes place or the server exits or does VT switching. During
this state any entity resource is under resource access control.
During OPERATING state only those entities are controlled which
actually have shared resources that conflict with others. The
determination which entity is to be placed under RAC during OPERATING
state takes place after ScreenInit() during the first server
generation. This doesn't apply if only one screen is active: in this
case no RAC is needed and the screen is simply left enabled while the
server is active.
III. Theory of operation
========================
III.1. General
--------------
The common level has knowledge of generic access control mechanisms
for devices on certain bus systems (currently the PCI bus) as well as
of methods to enable or disable access to the buses
itself. Furthermore it can access information on resources decoded by
these devices and if necessary modify it.
When first starting the Xserver collects all this information, saves
it for restoration checks it for consistency and if necessary corrects
it. Finally it disables all resources on a generic level prior to
calling any driver function.
The user should provide a device section in XF86Config for each
graphics device installed in his system. Each such entity which is
never to be used as X display device might be marked as inactive by
adding the keyword "Inactive" to the device section.
When the Probe() function of each driver is called the device sections
are matched against the devices found in the system. The driver may
probe devices at this stage that cannot be identified by using device
independent methods. Access to all resources that can be controlled in
a device independent way is disabled. The Probe() function should
register all non-relocatable resources at this stage. If a resource
conflict is found between exclusive resources the driver will fail
immediately. Optionally the driver might specify an EntityInit(),
EntityLeave() and EntityEnter() function.
EntityInit() can be used to disable any shared resources that are not
controlled by the generic access control functions. It is called prior
to the PreInit phase regardless if an entity is active or not. When
calling the EntityInit(), EntityEnter() and EntityLeave() functions
the common level will disable access to all other entities on a
generic level. Since the common level has no knowledge of device
specific methods to disable access to resources it cannot be
guaranteed that certain resources are not decoded by any other entity
until the EntityInit() or EntityEnter() phase is finished. Device
drivers should therefore register all those resources which they are
going to disable. If these resources are never to be used by any
driver function they may be flagged 'ResInit' so that they can be
removed from the resource list after processing all EntityInit()
functions. EntityEnter() should disable decoding of all resources
which are not registered as exclusive and which are not handled by the
generic access control in the common level. The difference to
EntityInit() is that the latter one is only called once during
lifetime of the server. It can therefore be used to set up variables
prior to disabling resources. EntityLeave() should restore the
original state when exiting the server or switching to a different vt.
It also needs to disable device specific access functions if they need
to be disabled on server exit or VT switch. The default state is to
enable them before giving up the VT.
In PreInit() phase each driver should check if any sharable resources
it has registered during Probe() has been denied and take appropriate
action which could simply be to fail. If it needs to access resources
it has disabled during EntitySetup() it can do so provided it has
registered these and will disable them before returning from
PreInit(). This also applies to all other driver functions. Several
functions are provided to request resource ranges, register these,
correct PCI config space and add replacements for the generic access
functions. Resources may be marked 'disabled' or 'unused' during
OPERATING stage. Although these steps could also be performed in
ScreenInit(), this is not desirable.
Following PreInit() phase the common level determines if resource
access control is needed. This is the case if more than one screen is
used. If necessary the RAC wrapper module is loaded. In ScreenInit()
the drivers can decide which operations need to be placed under
RAC. Available are the frame buffer operations, the pointer operations
and the colormap operations. Any operation that requires resources
which might be disabled during OPERATING state should be set to use
RAC. This can be specified separately for memory and IO resources.
When ScreenInit() phase is done the common level will determine which
shared resources are requested by more than one driver and set the
access functions accordingly. This is done following these rules:
a. The sharable resources registered by each entity are compared. if
a resource is registered by more than one entity the entity will be
marked to need to share this resources type (IO or MEM).
b. A resource marked 'disabled' during OPERATING state will be ignored
entirely.
c. A resource marked 'unused' will only conflicts with an overlapping
resource of an other entity if the second is actually in use during
OPERATING state.
d. If an 'unused' resource was found to conflict however the entity
does not use any other resource of this type the entire resource
type will be disabled for that entity.
The driver has the choice among different ways to control access to
certain resources:
a. It can relay on the generic access functions. This is probably the
most common case. Here the driver only needs to register any
resource it is going to use.
b. It can replace the generic access functions by driver specific
ones. This will mostly be used in cases where no generic access
functions are available. In this case the driver has to make sure
these resources are disabled when entering the PreInit() stage.
Since the replacement functions are registered in PreInit() the
driver will have to enable these resources itself if it needs to
access them during this state. The driver can specify if the
replacement functions can control memory and/or I/O resources
separately.
c. The driver can enable resources itself when it needs them. Each
driver function enabling them needs to disable them before it will
return. This should be used if a resource which can be controlled
in a device dependent way is only required during SETUP state. This
way it can be marked 'unused' during OPERATING state.
A resource which is decoded during OPERATING state however never
accessed by the driver should be marked unused.
Since access switching latencies are an issue during Xserver
operation, the common level attempts to minimize the number of
entities that need to be placed under RAC control. When a wrapped
operation is called, the EnableAccess() function is called before
control is passed on. EnableAccess() checks if a screen is under
access control. If not it just establishes bus routing and returns. If
the screen needs to be under access control, EnableAccess() determines
which resource types (MEM,IO) are required. Then it tests if this
access is already established. If so it simply returns. If not it
disables the currently established access, fixes bus routing and
enables access to all entities registered for this screen.
Whenever a mode switch or a vt-switch is performed the common level
will return to SETUP state.
III.3. Resource Types
---------------------
Resource have certain properties. When registering resources each
range is accompanied by a flag consisting of the or'ed flags of the
different properties the resource has. Each resource range may be
classified according to
- its physical properties ie. if it addresses
memory (ResMem) or
I/O space (ResIo),
- if it addresses a
block (ResBlock) or
sparse (ResSparse)
range,
- its access properties.
There are two known access properties:
- ResExclusive
for resources which may not be shared with any other device and
- ResShared
for resources which can be disabled and therefore can be shared.
If it is desirable to test a resource against any type a generic
access type 'ResAny' is provided. If this is set the resource will
conflict with any resource of a different entity intersecting its
range. Further it can be specified that a resource is decoded however
never used during any stage (ResUnused) or during OPERATING state
(ResUnusedOpr). A resource only visible during the init functions (ie.
EntityInit(), EntityEnter() and EntityLeave() should be registered
with the flag 'ResInit'. A resource that might conflict with
background resource ranges may be flagged with 'ResBios'. This might
be useful when registering resources ranges that were assigned by the
system Bios.
Several predefined resource lists are available for VGA and 8514/A
resources in common/sf86Resources.h.
IV. Available Functions
=======================
The functions provided for resource management will be listed in order
of use in the driver.
IV.1. Probe phase
-----------------
In this stage each driver detects those resources it is able to drive,
creates an entity record for each of them, registers non-relocatable
resources and allocates screens and adds the resources to screens.
Two helper functions are provided for matching device sections in the
XF86Config file to the devices:
int xf86MatchPciInstances(const char *driverName, int vendorID,
SymTabPtr chipsets, PciChipsets *PCIchipsets,
GDevPtr *devList, int numDevs, DriverPtr drvp,
int **foundEntities);
int xf86MatchIsaInstances(const char *driverName, SymTabPtr chipsets,
IsaChipsets *ISAchipsets, DriverPtr drvp,
FindIsaDevProc FindIsaDevice, GDevPtr *devList,
int numDevs, int **foundEntities);
Both functions return the number of matched entities and their indices
in foundEntities list.
They make use of several sub functions which are also available on the
driver level:
Bool xf86ComparePciBusString(const char *busID, int bus,
int device, int func);
and
Bool xf86ParseIsaBusString(const char *busID);
are called to interpret the busID in the device section. The functions:
int xf86ClaimPciSlot(int bus, int device, int func, DriverPtr drvp,
int chipset, GDevPtr dev, Bool active);
int xf86ClaimIsaSlot(DriverPtr drvp, int chipset, GDevPtr dev, Bool
active);
are used to allocate the entities and initialize their data
structures. Both functions return the index of the newly allocated
entity record or (-1) should the function fail. Before probing an ISA
card
Bool xf86IsPrimaryIsa();
gets called to determine if the primary card was not detected on the
PCI bus.
Two helper functions are provided to aid configuring entities:
Bool xf86ConfigActivePciEntity(ScrnInfoPtr pScrn, int entityIndex,
PciChipsets *p_chip, resList res,
EntityProc init, EntityProc enter,
EntityProc leave, pointer private);
Bool xf86ConfigActiveIsaEntity(ScrnInfoPtr pScrn, int entityIndex,
IsaChipsets *i_chip, resList res,
EntityProc init, EntityProc enter,
EntityProc leave, pointer private);
They are used to register the init/enter/leave functions described
above as well as the non-relocatable resources. Generally the list of
fixed resources is obtained from the Isa/PciChipsets lists. However
an additional list of resources may be passed. Generally this is not
required. The init/enter/leave functions have to be of type
typedef void (*EntityProc)(int entityIndex,pointer private);
They are passed the entity index and a pointer to a private scratch
area. This are can be set up during Probe() and its address can be
passed to xf86ConfigActiveIsaEntity() xf86ConfigActivePciEntity() as
the last argument.
These helper functions use:
void xf86ClaimFixedResources(resList list, int entityIndex);
To register the non relocatable resources which cannot be disabled
and which therefore would cause the server to fail immediately if
they were found to conflict. It also records non-relocatable but
sharable resources for processing after the Probe() phase.
Bool xf86SetEntityFuncs(int entityIndex, EntityProc init,
EntityProc enter, EntityProc leave, pointer);
This function registers the init/enter/leave() functions along with
the pointer to their private area to the entity.
void xf86AddEntityToScreen(ScrnInfoPtr pScrn, int entityIndex);
adds the entity to the screen.
These functions are also available on the driver level. A detailed
Probe() function is listed below. For most drivers this can be used
with little change.
Please note that VGA resources have to be claimed in Probe()
phase. Otherwise they are not routed to the bus.
IV.2. PreInit() phase
---------------------
During this phase the remaining resource should be registered.
PreInit() should call
EntityInfoPtr xf86GetEntityInfo(int entityIndex);
To obtain a pointer to an EntityInfoRec for each entity it is able to
drive and check if any resource are listed in 'resources'. These have
been rejected in the post-Probe() phase. The driver should decide if
it can continue without using these or if it should fail. The pointer
to the EntityInfoRec should be freed if not needed any more.
Several functions are provided to simplify resource registration:
Bool xf86IsEntityPrimary(int entityIndex);
is used to determine if the entity is the display device that is used
during boot-up and text mode.
Bool xf86IsScreenPrimary(int scrnIndex);
finds out if the primary entity is registered for the screen with
specified index.
pciVideoPtr xf86GetPciInfoForEntity(int entityIndex);
returns a pointer to the pciVideoRec of the specified entity. If the
entity is not a PCI device NULL is returned.
The primary function for registration of resources is
resPtr xf86RegisterResources(int entityIndex, resList list, int access);
it tries to register the resources in 'list'. If list is NULL it tries
to determine the resources automatically. This only works for entities
that provide a generic way to read out the resource ranges they
decode. So far this is only the case for PCI devices. By default the
PCI resources are registered as shared (ResShared) if the driver wants
to set a different access type it can do so by specifying the access
flags in the third argument. A value of 0 means to use the default
settings. If for any reason the resource broker is not able to
register some of the requested resources the function will return a
pointer to a list of the failed ones. In this case the driver may move
the resource to different locations. In case of PCI bus entities this
is done by passing the list of failed resources to
resPtr xf86ReallocatePciResources(int entityIndex, resPtr pRes);
this function returns a list of reallocated resource. This list needs
to be passed to xf86RegisterResources() again to be registered with
the broker.
Two functions are provided to obtain a resource range of a given type:
resRange xf86GetBlock(long type, memType size,
memType window_start, memType window_end,
memType align_mask, resPtr avoid);
resRange xf86GetSparse(long type, unsigned long fixed_bits,
unsigned long decode_mask, unsigned long address_mask,
resPtr avoid);
The first one tries to find a block range of size 'size' and type
'type' in a window bound by window_start and window_end with the
alignment specified in alignment mask. Optionally a list of resource
ranges which should be avoided inside this window can be passed. On
failure it will return a zero range of type 'ResEnd'.
The latter function does the same for sparse resources. A spares range
is determined by to parameters: the mask and the base value. An
address satisfying
mask & address == base
belongs to the specific spares range. 'mask' and 'base' themselves
have to satisfy:
mask & base == base.
Here three values have to be specified: the address mask which marks
all bits of the mask part of the address, the decode_mask which masks
out the bits which are hard coded and are therefore not available for
relocation and the values of the fixed bits. The function tries to
find a base that satisfies the given condition. If the function fails
it will return a zero range of type 'ResEnd'. Optionally it might be
passed a list of resource ranges to avoid.
Certain PCI devices are broken in the sense that they return invalid
size information for a certain resource. In this case the driver can
supply the correct size and make sure that the resource range
allocated for the card is large enough to hold the address range
decoded by the card. The function:
Bool xf86FixPciResource(int entityIndex, unsigned int prt, CARD32 alignment,
long type);
is used for that. The parameter prt contains the number of the PCI
base register that needs to be modified. A value of 6 refers to the
BIOS base register. The size is specified in the alignment
register. Since PCI resources need to span an integral range of the
size 2^n the alignment also specifies the number of addresses that
will be decoded. If the driver specifies a type mask it can override
the default type for PCI resources which is 'ResShared'. The resource
broker needs to know that to find a matching resource range. This
function should be called before calling xf86RegisterResources().
Bool xf86CheckPciMemBase(pciVideoPtr pPci, unsigned long base);
checks that the memory base value specified in base matches one of the
PCI base address register values for the given PCI device.
The driver may replace the generic access control functions for an
entity by it's own ones.
void xf86SetAccessFuncs(EntityInfoPtr pEnt, xf86SetAccessFuncPtr funcs,
xf86SetAccessFuncPtr oldFuncs);
with:
typedef struct {
xf86AccessPtr mem;
xf86AccessPtr io;
xf86AccessPtr io_mem;
} xf86SetAccessFuncRec, *xf86SetAccessFuncPtr;
is used for that. The driver can pass three functions: one for I/O
access, one for memory access and one for combined memory and I/O
access. If the memory access and combined access functions are
identical the common level assumes that the memory access cannot be
controlled independently of I/O access, if the I/O access function and
the combined access functions are the same it is assumed that I/O can
not be controlled independently. If memory and I/O have to be
controlled together all three values should be the same. If a non
NULL value is passed as third argument it is interpreted as an address
where to store the old access records. If the third argument is NULL
it will be assumed that the generic access should be enabled before
replacing the access functions. Otherwise it will be disabled. The
driver may enable them itself using the returned values. It should do
this from his replacement access functions as the generic access may
be disabled by the common level on certain occasions. If replacement
functions are specified they must control all resources of the
specific type registered for the entity.
To find out if specific resource range is conflicting with another
resource
memType xf86ChkConflict(resRange *rgp, int entityIndex);
may be called. If a non-zero value is returned a conflict is found.
resPtr xf86SetOperatingState(resList list, int entityIndex, int mask);
is used to set the state of a resource during OPERATING state. 'list'
holds a list to which 'mask' is to be applied. The parameter 'mask'
may have the value 'ResUnusedOpr' and 'ResDisableOpr'. The first one
should be used if a resource isn't used during OPERATING state however
decoded by the device while the latter one indicates that the resource
is not decoded during OPERATING state. Note that the resource ranges
have to match those specified during registration. If a range has been
specified starting at A and ending at B and suppose C us a value
satisfying A < C < B one may not specify the resource range (A,B) by
splitting it into two ranges (A,C) and (C,B).
Two functions are provided for special cases:
void xf86RemoveEntityFromScreen(ScrnInfoPtr pScrn, int entityIndex);
may be used to remove an entity from a screen. This only makes sense
if a screen has more than one entity assigned or the screen is to be
deleted. No test is made if the screen has any entities left.
void xf86DeallocateResourcesForEntity(int entityIndex, long type);
deallocates all resources of a given type registered for a certain
entity from the resource broker list.
IV.3. ScreenInit() phase
------------------------
Setting up the rac flags is all that remains to do in ScreenInit()
phase (Note that these flags might also be set up in PreInit() phase).
The ScrnInfoRec has separate flags for memory and PIO access:
racIoFlags and racMemFlags. They specifies which graphics operations
might require the use of resources which might be disabled for some
reason. Note that even exclusive resources might be disabled if they
are disabled along with shared resources. For example if a driver has
registered the VGA PIO resources and lets the common level disable
these by disabling PIO access in PCI config space (the standard way),
exclusive PCI PIO ranges will also be disabled. Therefore the driver
has to flag any operations requiring PCI PIO resources in racIoFlags.
The avaliable flags are defined in rac/xf86RAC.h. Available are:
RAC_FB for framebuffer operations (including hw acceleration)
RAC_CURSOR for Cursor operations
(??? I'm not sure if we need this for SW cursor it depends
on which level the sw cursor is drawn)
RAC_COLORMAP for colormap operations
RAC_VIEWPORT for the call to RACAdjustFrame()
The flags are or'ed.
V. Appendix
===========
A. Sample Probe() Function
--------------------------
static Bool
XXXProbe(DriverPtr drv, int flags)
{
Bool foundScreen = FALSE;
int numDevSections, numUsed;
GDevPtr *devSections;
int *usedChips;
int i;
/*
* Find the config file Device sections that match this
* driver, and return if there are none.
*/
if ((numDevSections = xf86MatchDevice(CHIPS_DRIVER_NAME,
&devSections)) <= 0) {
return FALSE;
}
/* PCI BUS */
/* test if PCI bus present */
if (xf86GetPciVideoInfo() ) {
/* match PCI instances with ones supported by the driver */
numUsed = xf86MatchPciInstances(XXX_NAME, PCI_VENDOR_XXX,
XXXChipsets, XXXPCIchipsets,
devSections,numDevSections, drv,
&usedChips);
if (numUsed > 0) {
for (i = 0; i < numUsed; i++) {
/* Allocate a ScrnInfoRec */
ScrnInfoPtr pScrn = xf86AllocateScreen(drv,0);
pScrn->driverVersion = VERSION;
pScrn->driverName = XXX_DRIVER_NAME;
pScrn->name = XXX_NAME;
pScrn->Probe = XXXProbe;
pScrn->PreInit = XXXPreInit;
pScrn->ScreenInit = XXXScreenInit;
pScrn->SwitchMode = XXXSwitchMode;
pScrn->AdjustFrame = XXXAdjustFrame;
pScrn->EnterVT = XXXEnterVT;
pScrn->LeaveVT = XXXLeaveVT;
pScrn->FreeScreen = XXXFreeScreen;
pScrn->ValidMode = XXXValidMode;
foundScreen = TRUE;
/* add screen to entity */
xf86ConfigActivePciEntity(pScrn,usedChips[i],XXXPCIchipsets,
NULL,NULL,NULL,NULL,NULL);
}
}
}
/* Isa Bus */
numUsed = xf86MatchIsaInstances(XXX_NAME,XXXChipsets,XXXISAchipsets,
drv,chipsFindIsaDevice,devSections,
numDevSections,&usedChips);
if(numUsed >= 0)
for (i = 0; i < numUsed; i++) {
ScrnInfoPtr pScrn = xf86AllocateScreen(drv,0);
pScrn->driverVersion = VERSION;
pScrn->driverName = XXX_DRIVER_NAME;
pScrn->name = XXX_NAME;
pScrn->Probe = XXXProbe;
pScrn->PreInit = XXXPreInit;
pScrn->ScreenInit = XXXScreenInit;
pScrn->SwitchMode = XXXSwitchMode;
pScrn->AdjustFrame = XXXAdjustFrame;
pScrn->EnterVT = XXXEnterVT;
pScrn->LeaveVT = XXXLeaveVT;
pScrn->FreeScreen = XXXFreeScreen;
pScrn->ValidMode = XXXValidMode;
foundScreen = TRUE;
xf86ConfigActiveIsaEntity(pScrn,usedChips[i],XXXISAchipsets,
NULL,NULL,NULL,NULL,NULL);
}
xfree(devSections);
return foundScreen;
}
B. Porting Issues
-----------------
Here are some hints on porting code developed for RAC 1 to RAC 2.
1. a. Initialization of RAC is now entirely done on the common level.
Therefore the call to xf86RACInit() can be removed.
b. Also there is no need for the racSymbols list.
c. LoadSubModule(..,rac) should be removed.
d. racSymbols should be removed from LoaderRequestSymList(racSymbols,..)
2. a. if the driver uses the predefined resource lists xf86Resources.h
needs to be included.
b. RES_VGA should be changed to RES_EXCLUSIVE_VGA
3. The device list now belongs to the EntityInfoRec.
Change pScrn->device to xxx->pEnt->device.
4. Rewrite the Probe() function. The example given above should work
as a guideline.
5. Register all necessary resources in PreInit() by calling
xf86RegisterResources().
6. If applicable set the operating state of the registered resources
by calling xf86SetOperatingState(). This should be done during
PreInit(). If necessary it might still be done in ScreenInit()
7. Set up the racIoFlags and racMemFlags.
LocalWords: ISA