XFree86® server 4.x Design (DRAFT) : Control Flow in the Server and Mandatory Driver Functions
Previous: Resource Access Control Introduction
Next: Optional Driver Functions

5. Control Flow in the Server and Mandatory Driver Functions

At the start of each server generation, main() (dix/main.c) calls the DDX function InitOutput(). This is the first place that the DDX gets control. InitOutput() is expected to fill in the global screenInfo struct, and one screenInfo.screen[] entry for each screen present. Here is what InitOutput() does:

5.1. Parse the XF86Config file

This is done at the start of the first server generation only.

The XF86Config file is read in full, and the resulting information stored in data structures. None of the parsed information is processed at this point. The parser data structures are opaque to the video drivers and to most of the common layer code.

The entire file is parsed first to remove any section ordering requirements.

5.2. Initial processing of parsed information and command line options

This is done at the start of the first server generation only.

The initial processing is to determine paths like the ModulePath, etc, and to determine which ServerLayout, Screen and Device sections are active.

5.3. Enable port I/O access

Port I/O access is controlled from the XFree86 common layer, and is ``all or nothing''. It is enabled prior to calling driver probes, at the start of subsequent server generations, and when VT switching back to the Xserver. It is disabled at the end of server generations, and when VT switching away from the Xserver.

The implementation details of this may vary on different platforms.

5.4. General bus probe

This is done at the start of the first server generation only.

In the case of ix86 machines, this will be a general PCI probe. The full information obtained here will be available to the drivers. This information persists for the life of the Xserver. In the PCI case, the PCI information for all video cards found is available by calling xf86GetPciVideoInfo().

pciVideoPtr *xf86GetPciVideoInfo(void)

returns a pointer to a list of pointers to pciVideoRec entries, of which there is one for each detected PCI video card. The list is terminated with a NULL pointer. If no PCI video cards were detected, the return value is NULL.

After the bus probe, the resource broker is initialised.

5.5. Load initial set of modules

This is done at the start of the first server generation only.

The core server contains a list of mandatory modules. These are loaded first. Currently the only module on this list is the bitmap font module.

The next set of modules loaded are those specified explicitly in the Module section of the config file.

The final set of initial modules are the driver modules referenced by the active Device and InputDevice sections in the config file. Each of these modules is loaded exactly once.

5.6. Register Video and Input Drivers

This is done at the start of the first server generation only.

When a driver module is loaded, the loader calls its Setup function. For video drivers, this function calls xf86AddDriver() to register the driver's DriverRec, which contains a small set of essential details and driver entry points required during the early phase of InitOutput(). xf86AddDriver() adds it to the global xf86DriverList[] array.

The DriverRec contains the driver canonical name, the Identify(), Probe() and AvailableOptions() function entry points as well as a pointer to the driver's module (as returned from the loader when the driver was loaded) and a reference count which keeps track of how many screens are using the driver. The entry driver entry points are those required prior to the driver allocating and filling in its ScrnInfoRec.

For a static server, the xf86DriverList[] array is initialised at build time, and the loading of modules is not done.

A similar procedure is used for input drivers. The input driver's Setup function calls xf86AddInputDriver() to register the driver's InputDriverRec, which contains a small set of essential details and driver entry points required during the early phase of InitInput(). xf86AddInputDriver() adds it to the global xf86InputDriverList[] array. For a static server, the xf86InputDriverList[] array is initialised at build time.

Both the xf86DriverList[] and xf86InputDriverList[] arrays have been initialised by the end of this stage.

Once all the drivers are registered, their ChipIdentify() functions are called.

void ChipIdentify(int flags)
This is expected to print a message indicating the driver name, a short summary of what it supports, and a list of the chipset names that it supports. It may use the xf86PrintChipsets() helper to do this.

void xf86PrintChipsets(const char *drvname, const char *drvmsg,
          SymTabPtr chips)
This function provides an easy way for a driver's ChipIdentify function to format the identification message.

5.7. Initialise Access Control

This is done at the start of the first server generation only.

The Resource Access Control (RAC) subsystem is initialised before calling any driver functions that may access hardware. All generic bus information is probed and saved (for restoration later). All (shared resource) video devices are disabled at the generic bus level, and a probe is done to find the ``primary'' video device. These devices remain disabled for the next step.

5.8. Video Driver Probe

This is done at the start of the first server generation only. The ChipProbe() function of each registered video driver is called.

Bool ChipProbe(DriverPtr drv, int flags)

The purpose of this is to identify all instances of hardware supported by the driver. The flags value is currently either 0, PROBE_DEFAULT or PROBE_DETECT. PROBE_DETECT is used if "-configure" or "-probe" command line arguments are given and indicates to the Probe() function that it should not configure the bus entities and that no XF86Config information is available.

The probe must find the active device sections that match the driver by calling xf86MatchDevice(). The number of matches found limits the maximum number of instances for this driver. If no matches are found, the function should return FALSE immediately.

Devices that cannot be identified by using device-independent methods should be probed at this stage (keeping in mind that access to all resources that can be disabled in a device-independent way are disabled during this phase). The probe must be a minimal probe. It should just determine if there is a card present that the driver can drive. It should use the least intrusive probe methods possible. It must not do anything that is not essential, like probing for other details such as the amount of memory installed, etc. It is recommended that the xf86MatchPciInstances() helper function be used for identifying matching PCI devices, and similarly the xf86MatchIsaInstances() for ISA (non-PCI) devices (see the RAC section). These helpers also checks and claims the appropriate entity. When not using the helper, that should be done with xf86CheckPciSlot() and xf86ClaimPciSlot() for PCI devices and xf86ClaimIsaSlot() for ISA devices (see the RAC section).

The probe must register all non-relocatable resources at this stage. If a resource conflict is found between exclusive resources the driver will fail immediately. This is usually best done with the xf86ConfigPciEntity() helper function for PCI and xf86ConfigIsaEntity() for ISA (see the RAC section). It is possible to register some entity specific functions with those helpers. When not using the helpers, the xf86AddEntityToScreen() xf86ClaimFixedResources() and xf86SetEntityFuncs() should be used instead (see the RAC section).

If a chipset is specified in an active device section which the driver considers relevant (ie it has no driver specified, or the driver specified matches the driver doing the probe), the Probe must return FALSE if the chipset doesn't match one supported by the driver.

If there are no active device sections that the driver considers relevant, it must return FALSE.

Allocate a ScrnInfoRec for each active instance of the hardware found, and fill in the basic information, including the other driver entry points. This is best done with the xf86ConfigIsaEntity() helper function for ISA instances or xf86ConfigPciEntity() for PCI instances. These functions allocate a ScrnInfoRec for active entities. Optionally xf86AllocateScreen() function may also be used to allocate the ScrnInfoRec. Any of these functions take care of initialising fields to defined ``unused'' values.

Claim the entities for each instance of the hardware found. This prevents other drivers from claiming the same hardware.

Must leave hardware in the same state it found it in, and must not do any hardware initialisation.

All detection can be overridden via the config file, and that parsed information is available to the driver at this stage.

Returns TRUE if one or more instances are found, and FALSE otherwise.

int xf86MatchDevice(const char *drivername,
          GDevPtr **driversectlist)

This function takes the name of the driver and returns via driversectlist a list of device sections that match the driver name. The function return value is the number of matches found. If a fatal error is encountered the return value is -1.

The caller should use xfree() to free *driversectlist when it is no longer needed.

ScrnInfoPtr xf86AllocateScreen(DriverPtr drv, int flags)

This function allocates a new ScrnInfoRec in the xf86Screens[] array. This function is normally called by the video driver ChipProbe() functions. The return value is a pointer to the newly allocated ScrnInfoRec. The scrnIndex, origIndex, module and drv fields are initialised. The reference count in drv is incremented. The storage for any currently allocated ``privates'' pointers is also allocated and the privates field initialised (the privates data is of course not allocated or initialised). This function never returns on failure. If the allocation fails, the server exits with a fatal error. The flags value is not currently used, and should be set to zero.

At the completion of this, a list of ScrnInfoRecs have been allocated in the xf86Screens[] array, and the associated entities and fixed resources have been claimed. The following ScrnInfoRec fields must be initialised at this point:

          driverVersion
          driverName
          scrnIndex(*)
          origIndex(*)
          drv(*)
          module(*)
          name
          Probe
          PreInit
          ScreenInit
          EnterVT
          LeaveVT
          numEntities
          entityList
          access
    

(*) These are initialised when the ScrnInfoRec is allocated, and not explicitly by the driver.

The following ScrnInfoRec fields must be initialised if the driver is going to use them:

          SwitchMode
          AdjustFrame
          FreeScreen
          ValidMode
    

5.9. Matching Screens

This is done at the start of the first server generation only.

After the Probe phase is finished, there will be some number of ScrnInfoRecs. These are then matched with the active Screen sections in the XF86Config, and those not having an active Screen section are deleted. If the number of remaining screens is 0, InitOutput() sets screenInfo.numScreens to 0 and returns.

At this point the following fields of the ScrnInfoRecs must be initialised:

          confScreen
    

5.10. Allocate non-conflicting resources

This is done at the start of the first server generation only.

Before calling the drivers again, the resource information collected from the Probe phase is processed. This includes checking the extent of PCI resources for the probed devices, and resolving any conflicts in the relocatable PCI resources. It also reports conflicts, checks bus routing issues, and anything else that is needed to enable the entities for the next phase.

If any drivers registered an EntityInit() function during the Probe phase, then they are called here.

5.11. Sort the Screens and pre-check Monitor Information

This is done at the start of the first server generation only.

The list of screens is sorted to match the ordering requested in the config file.

The list of modes for each active monitor is checked against the monitor's parameters. Invalid modes are pruned.

5.12. PreInit

This is done at the start of the first server generation only.

For each ScrnInfoRec, enable access to the screens entities and call the ChipPreInit() function.

Bool ChipPreInit(ScrnInfoRec screen, int flags)

The purpose of this function is to find out all the information required to determine if the configuration is usable, and to initialise those parts of the ScrnInfoRec that can be set once at the beginning of the first server generation.

The number of entities registered for the screen should be checked against the expected number (most drivers expect only one). The entity information for each of them should be retrieved (with xf86GetEntityInfo()) and checked for the correct bus type and that none of the sharable resources registered during the Probe phase was rejected.

Access to resources for the entities that can be controlled in a device-independent way are enabled before this function is called. If the driver needs to access any resources that it has disabled in an EntityInit() function that it registered, then it may enable them here providing that it disables them before this function returns.

This includes probing for video memory, clocks, ramdac, and all other HW info that is needed. It includes determining the depth/bpp/visual and related info. It includes validating and determining the set of video modes that will be used (and anything that is required to determine that).

This information should be determined in the least intrusive way possible. The state of the HW must remain unchanged by this function. Although video memory (including MMIO) may be mapped within this function, it must be unmapped before returning. Driver specific information should be stored in a structure hooked into the ScrnInfoRec's driverPrivate field. Any other modules which require persistent data (ie data that persists across server generations) should be initialised in this function, and they should allocate a ``privates'' index to hook their data into by calling xf86AllocateScrnInfoPrivateIndex(). The ``privates'' data is persistent.

Helper functions for some of these things are provided at the XFree86 common level, and the driver can choose to make use of them.

All additional resources that the screen needs must be registered here. This should be done with xf86RegisterResources(). If some of the fixed resources registered in the Probe phase are not needed or not decoded by the hardware when in the OPERATING server state, their status should be updated with xf86SetOperatingState().

Modules may be loaded at any point in this function, and all modules that the driver will need must be loaded before the end of this function. Either the xf86LoadSubModule() or the xf86LoadDrvSubModule() function should be used to load modules depending on whether a ScrnInfoRec has been set up. A driver may unload a module within this function if it was only needed temporarily, and the xf86UnloadSubModule() function should be used to do that. Otherwise there is no need to explicitly unload modules because the loader takes care of module dependencies and will unload submodules automatically if/when the driver module is unloaded.

The bulk of the ScrnInfoRec fields should be filled out in this function.

ChipPreInit() returns FALSE when the configuration is unusable in some way (unsupported depth, no valid modes, not enough video memory, etc), and TRUE if it is usable.

It is expected that if the ChipPreInit() function returns TRUE, then the only reasons that subsequent stages in the driver might fail are lack or resources (like xalloc failures). All other possible reasons for failure should be determined by the ChipPreInit() function.

The ScrnInfoRecs for screens where the ChipPreInit() fails are removed. If none remain, InitOutput() sets screenInfo.numScreens to 0 and returns.

At this point, further fields of the ScrnInfoRecs would normally be filled in. Most are not strictly mandatory, but many are required by other layers and/or helper functions that the driver may choose to use. The documentation for those layers and helper functions indicates which they require.

The following fields of the ScrnInfoRecs should be filled in if the driver is going to use them:

          monitor
          display
          depth
          pixmapBPP
          bitsPerPixel
          weight                (>8bpp only)
          mask                  (>8bpp only)
          offset                (>8bpp only)
          rgbBits               (8bpp only)
          gamma
          defaultVisual
          maxHValue
          maxVValue
          virtualX
          virtualY
          displayWidth
          frameX0
          frameY0
          frameX1
          frameY1
          zoomLocked
          modePool
          modes
          currentMode
          progClock             (TRUE if clock is programmable)
          chipset
          ramdac
          clockchip
          numClocks             (if not programmable)
          clock[]               (if not programmable)
          videoRam
          biosBase
          memBase
          memClk
          driverPrivate
          chipID
          chipRev
    

pointer xf86LoadSubModule(ScrnInfoPtr pScrn, const char *name): and pointer xf86LoadDrvSubModule(DriverPtr drv, const char *name):

Load a module that a driver depends on. This function loads the module name as a sub module of the driver. The return value is a handle identifying the new module. If the load fails, the return value will be NULL. If a driver needs to explicitly unload a module it has loaded in this way, the return value must be saved and passed to xf86UnloadSubModule() when unloading.

void xf86UnloadSubModule(pointer module)

Unloads the module referenced by module. module should be a pointer returned previously by xf86LoadSubModule() or xf86LoadDrvSubModule() .

5.13. Cleaning up Unused Drivers

At this point it is known which screens will be in use, and which drivers are being used. Unreferenced drivers (and modules they may have loaded) are unloaded here.

5.14. Consistency Checks

The parameters that must be global to the server, like pixmap formats, bitmap bit order, bitmap scanline unit and image byte order are compared for each of the screens. If a mismatch is found, the server exits with an appropriate message.

5.15. Check if Resource Control is Needed

Determine 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.

5.16. AddScreen (ScreenInit)

At this point, the valid screens are known. AddScreen() is called for each of them, passing ChipScreenInit() as the argument. AddScreen() is a DIX function that allocates a new screenInfo.screen[] entry (aka pScreen), and does some basic initialisation of it. It then calls the ChipScreenInit() function, with pScreen as one of its arguments. If ChipScreenInit() returns FALSE, AddScreen() returns -1. Otherwise it returns the index of the screen. AddScreen() should only fail because of programming errors or failure to allocate resources (like memory). All configuration problems should be detected BEFORE this point.

Bool ChipScreenInit(int index, ScreenPtr pScreen,
          int argc, char **argv)

This is called at the start of each server generation.

Fill in all of pScreen, possibly doing some of this by calling ScreenInit functions from other layers like mi, framebuffers (cfb, etc), and extensions.

Decide which operations need to be placed under resource access control. The classes of operations are the frame buffer operations (RAC_FB), the pointer operations (RAC_CURSOR), the viewport change operations (RAC_VIEWPORT) and the colormap operations (RAC_COLORMAP). 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 (the racMemFlags and racIoFlags fields of the ScrnInfoRec respectively).

Map any video memory or other memory regions.

Save the video card state. Enough state must be saved so that the original state can later be restored.

Initialise the initial video mode. The ScrnInfoRec's vtSema field should be set to TRUE just prior to changing the video hardware's state.

The ChipScreenInit() function (or functions from other layers that it calls) should allocate entries in the ScreenRec's devPrivates area by calling AllocateScreenPrivateIndex() if it needs per-generation storage. Since the ScreenRec's devPrivates information is cleared for each server generation, this is the correct place to initialise it.

After AddScreen() has successfully returned, the following ScrnInfoRec fields are initialised:

          pScreen
          racMemFlags
          racIoFlags
    

The ChipScreenInit() function should initialise the CloseScreen and SaveScreen fields of pScreen. The old value of pScreen->CloseScreen should be saved as part of the driver's per-screen private data, allowing it to be called from ChipCloseScreen(). This means that the existing CloseScreen() function is wrapped.

5.17. Finalising RAC Initialisation

After all the ChipScreenInit() functions have been called, each screen has registered its RAC requirements. This information is used to determine which shared resources are requested by more than one driver and set the access functions accordingly. This is done following these rules:

  1. 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 indicate that it needs to share this resources type (IO or MEM).
  2. A resource marked ``disabled'' during OPERATING state will be ignored entirely.
  3. A resource marked ``unused'' will only conflict with an overlapping resource of an other entity if the second is actually in use during OPERATING state.
  4. If an ``unused'' resource was found to conflict but the entity does not use any other resource of this type the entire resource type will be disabled for that entity.

5.18. Finishing InitOutput()

At this point InitOutput() is finished, and all the screens have been setup in their initial video mode.

5.19. Mode Switching

When a SwitchMode event is received, ChipSwitchMode() is called (when it exists):

Bool ChipSwitchMode(int index, DisplayModePtr mode, int flags)

Initialises the new mode for the screen identified by index;. The viewport may need to be adjusted also.

5.20. Changing Viewport

When a Change Viewport event is received, ChipAdjustFrame() is called (when it exists):

void ChipAdjustFrame(int index, int x, int y, int flags)

Changes the viewport for the screen identified by index.

It should be noted that many chipsets impose restrictions on where the viewport may be placed in the virtual resolution, either for alignment reasons, or to prevent the start of the viewport from being positioned within a pixel (as can happen in a 24bpp mode). After calculating the value the chipset's panning registers need to be set to for non-DGA modes, this function should recalculate the ScrnInfoRec's frameX0, frameY0, frameX1 and frameY1 fields to correspond to that value. If this is not done, switching to another mode might cause the position of a hardware cursor to change.

5.21. VT Switching

When a VT switch event is received, xf86VTSwitch() is called. xf86VTSwitch() does the following:

On ENTER:

On LEAVE:

Bool ChipEnterVT(int index, int flags)

This function should initialise the current video mode and initialise the viewport, turn on the HW cursor if appropriate, etc.

Should it re-save the video state before initialising the video mode?

void ChipLeaveVT(int index, int flags)

This function should restore the saved video state. If appropriate it should also turn off the HW cursor, and invalidate any pixmap/font caches.

Optionally, ChipLeaveVT() may also unmap memory regions. If so, ChipEnterVT() will need to remap them. Additionally, if an aperture used to access video memory is unmapped and remapped in this fashion, ChipEnterVT() will also need to notify the framebuffer layers of the aperture's new location in virtual memory. This is done with a call to the screen's ModifyPixmapHeader() function, as follows

(*pScreen->ModifyPixmapHeader)(pScrn->ppix,
          -1, -1, -1, -1, -1, NewApertureAddress);

where the ``ppix'' field in a ScrnInfoRec points to the pixmap used by the screen's SaveRestoreImage() function to hold the screen's contents while switched out.

Currently, aperture remapping, as described here, should not be attempted if the driver uses the xf8_16bpp or xf8_32bpp framebuffer layers. A pending restructuring of VT switching will address this restriction in the near future.

Other layers may wrap the ChipEnterVT() and ChipLeaveVT() functions if they need to take some action when these events are received.

5.22. End of server generation

At the end of each server generation, the DIX layer calls ChipCloseScreen() for each screen:

Bool ChipCloseScreen(int index, ScreenPtr pScreen)

This function should restore the saved video state and unmap the memory regions.

It should also free per-screen data structures allocated by the driver. Note that the persistent data held in the ScrnInfoRec's driverPrivate field should not be freed here because it is needed by subsequent server generations.

The ScrnInfoRec's vtSema field should be set to FALSE once the video HW state has been restored.

Before freeing the per-screen driver data the saved CloseScreen value should be restored to pScreen->CloseScreen, and that function should be called after freeing the data.


XFree86® server 4.x Design (DRAFT) : Control Flow in the Server and Mandatory Driver Functions
Previous: Resource Access Control Introduction
Next: Optional Driver Functions