Index: [thread] [date] [subject] [author]
  From: Steffen Seeger <s.seeger@physik.tu-chemnitz.de>
  To  : ggi-develop@eskimo.com
  Date: Fri, 13 Nov 1998 12:32:07 +0100 (MET)

Re: Accessing MMIO PCI space - crossplatform (fwd)

Oopss... Please ignore my other mail. This is the real one.

> 	This came from linux-kernel just now.  We have been discussing 
> some of the same issues....
> 
> Jon

Please forward the following draft as a __proposal__ of a scheme that is 
an easy to use (proven!!), extensible, and orthogonal solution to the
problem addressed in the mail you forwarded. [be kind, be nice, 
be the good guy - don't start another KGI/not KGI war :-))]
I am not subscribed to linux-kernel, and we agreed on one 'offical'
KGI evangelist on linuux-kernel ;-)).

This is the scheme Degas/KGI is going to use. Please do also forward 
any followups of this thread to my address or the list. Of course any
comments on portability/inconsistencies are welcome.

Thank you very much in advance,

			Steffen

PS: This is not KGI specific at all! So if they start flaming "we don't
    want graphics drivers in the kernel" point out that a) this is not
    specific to graphics cards, and b) they already have drivers in there,
    and now are coming to the same problems we had some time ago. This
    is our (working) solution, and we offer it to improve Linux.

    KINDLY!!! You know what I mean ;))

----------------- e-mail: seeger@physik.tu-chemnitz.de -----------------
------------- The GGI Project: http://www.ggi-project.org --------------


KGI/GGI System Layer Guide
Steffen Seeger (seeger@physik.tu-chemnitz.de)

I/O spaces
==========

	Drivers that have to access hardware have to use I/O regions to 
	access the hardware. There must not be any OS specific I/O routines
	used in the drivers to ensure that binary drivers are 'portable'
	across platforms with the same CPU.

	I/O regions are continous subsets of I/O spaces, which are the set
	of all addressable locations plus some operations defined to 
	read and write these locations. Currently the following I/O spaces
	are defined:


	Identifier	I/O space

	pcicfg		PCI configuration space
			(accessed using PCI BIOS on IA32)
	io		ISA style I/O space 
			(accessed using IA32 IN/OUT instructions)
	mem		Memory I/O space
			(accessed using IA32 MOV instructions)


	Each I/O space location has four addresses associated with it:

	virtual		The address to be used with the operations 
			defined for this IO space.
	io		The address the device's address decoder responds to.
	bus		The address at which other devices on the same bus
			see this device.
	phys		The address at which the main CPU will find this
			device in physical address space.

	In order to access a certain location within a I/O region, e.g. at
	byte offset <offset>, using the CPU, the virtual address 
	<base_virt>+<offset> should be passed to the I/O operations.

	In order to access the same location from another device on the same
	bus, the address <base_bus>+<offset> should be passed to this
	device.

	In order to export this location to userspace (e.g. via memory 
	mapping), the mapping to be established should map the application's
	virtual address <app_virt>+<offset> to the physical address 
	<base_phys>+<offset>.

	Applications and drivers must not make any assuptions about the
	mappings between the virtual, io, bus and physical addresses.
	As the regions are defined to be continous, locations
	with the same offset from virtual, io, bus and physical base address
	will refer to the same I/O space location.

	I/O region management
	---------------------

	In order to obtain the base addresses of a particular region, 
	the I/O region has to be registered with the operating system.
	As long as a I/O region is registered, the mappings are guarantueed
	to be constant and will not be changed by the operating system.

	The functions to do region management take (except for PCICFG space,
	see below) an 'struct <iospace>_region' as an argument that has the
	following members:

	member		description

	device		(PCI) device this belongs to
        base_virt	virtual base address
        base_io		base address the device's address decoder responds to.
        base_bus	bus base address
        base_phys	physical base address
        size		size of region
        decode		lines used to compare address in address decoder
        *name		(symbolic) name of the region


	Region management functions defined so far are:

	check	Test if a particular I/O region is registered already.
		struct members assumed to be valid:
			device, base_io, size, decode, name	

	claim	Register a particular I/O region.
		struct members assumed to be valid:
			device, base_io, size, decode, name
		If successful, all other struct members will be 
		valid and the I/O operations are useable.

	alloc	Allocate a particular region of given size, avoiding
		address conflict with other (registered) regions and
		claim this region.
		struct members assumed to be valid:
			device, size, decode, name
		If successfull all other struct members will be valid.

	free	Unregister a previously registered region. This assumes
		all members of the struct passed are valid. After freeing
		a region, only the device, size, decode, name and base_io
		members are valid.

	The only exception to the above management procedure are PCICFG 
	functions used to obtain information about installed PCI devices.
	The difference is, that there is a defined mapping from bus, device
	and function IDs to a PCICFG virtual address. This address can 
	directly be used with the PCICFG I/O space operations.
	In order to support PCI device autodetection, the pcicfg_find()
	function is provided. The following example code will scan the whole
	PCICFG space for supported cards and call do_something() for the
	devices found.

		__ggi_sys_u32 signatures =
		{
			PCICFG_SIGNATURE(PCI_VENDOR_ID_ABC, PCI_DEVICE_ID_123),
			PCICFG_SIGNATURE(PCI_VENDOR_ID_DEF, PCI_DEVICE_ID_456),
			...
			PCICFG_SIGNATURE(0,0);
		};
		pcicfg_vaddr device = PCICFG_NULL;
		while (! pcicfg_find(&device, signatures)) {

			do_something(device);
		}

	For KGI drivers, policy is to have one driver (module) loaded per
	installed device, which may have to be specified explicitly. Thus
	it is recommended to autodetect only the first device if (and only
	if) the given device specifier is invalid. The appropriate code
	will then be:

		... signatures definition as above
		struct kgim_options_pci *pci = KGIM_OPTIONS(dpy, pci);
		...
		if (pci->dev == PCICFG_NULL) {

			if (pcicfg_find(&pci->dev, signatures)) {

				... error! no supported device found
			}
		}
		... verify/analyze detected device



	I/O region access
	-----------------

	For access with the main CPU, the following I/O operations are defined:

	operation	description

	in/out		data transfer device -> CPU memory/CPU memory -> device
			from/to one particular I/O space location.

	ins/outs	'in/out string': repeated transfer device -> CPU memory
			buffer/CPU memory buffer -> device from/to one 
			particular I/O space location
			C/C++ equivalent:
			ins:	while (i < cnt) { buf[i++] = *io_addr; }
			outs:	while (i < cnt) { *io_addr = buf[i++]; }

	incu/outcu	'in/out copy upward': repeated transfer from device
			to CPU/CPU to device from/to ascending I/O space 
			locations to ascending CPU memory locations.
			C/C++ equivalent:
			incu:	while (i < cnt) { buf[i] = io_addr[i]; i++; }
			outco:	while (i < cnt) { io_addr[i] = buf[i]; i++; }
			
	incd/outcd	'in/out copy downward':	repeated transfer from device
			to CPU/CPU to device from/to descending I/O space
			locations to descending CPU memory locations.
			incd:	i = cnt; while (i--) { buf[i] = io_addr[i]; }
			outcd:	i = cnt; while (i--) { io_addr[i] = buf[i]; }

	All these operations are defined to (1) preserve read/write order,
	(2) preserve byte order (no endian swap) and (3) are assumed to 
	be completed before the caller resumes execution. Each operation may
	be available for different access widths (8,16,32,64 bit) indicated 
	by a suffix.

	Any of the operations may be missing, either for a particular width
	only or for a whole operation. (E.g. incd/outcd, ins/outs don't make
	sense with PCICFG I/O space). Also, particular I/O spaces may not be
	available on some architectures (e.g. on early PC's there might not be
	a PCICFG space).

=== io.h ======================================================================
/* ----------------------------------------------------------------------------
**	hardware I/O layer definiton
** ----------------------------------------------------------------------------
**
**	Copyright (C) 1997-1998	by Steffen Seeger
**
** ----------------------------------------------------------------------------
**	MAINTAINER	Steffen_Seeger
**
**	$Log: io.h,v $
**	Revision 1.2  1998/08/14 20:05:16  seeger_s
**	- redefined IRQ interface.
**	
**	Revision 1.1  1998/07/19 22:54:17  seeger_s
**	- implemented I/O functions
**	
*/
#ifndef	__ggi_io_h
#define	__ggi_io_h

/*	Explanations on I/O regions can be found in the system layer 
**	documentation.
*/

/* ----------------------------------------------------------------------------
**	PCI configuration space
** ----------------------------------------------------------------------------
**	These functions should be used to access the PCI configuration space.
**	As the address is given by the hardware wiring, a registration
**	scheme doesn't make sense. But I/O is pretty useful :) For the same
**	reason, and as it is used for configuration (bootstrap) purposes,
**	a physical->virtual mapping is not possible. All addresses are
**	virtual and no mapping takes place.
*/
#ifdef __GGI_SYS_IO_HAS_PCICFG

#define	PCICFG_NULL ((pcicfg_vaddr) -1)	/* an invalid virtual address	*/

typedef __ggi_sys_u32	pcicfg_vaddr;	/* the virtual address type	*/

#define	PCICFG_VADDR(bus, slot, fn)	\
	((pcicfg_vaddr)((((bus) & 0xFF) << 24) | (PCI_DEVFN(slot,fn) << 16)))
#define	PCICFG_BUS(vaddr)	(((vaddr) >> 24) & 0xFF)
#define	PCICFG_DEV(vaddr)	PCI_SLOT(((vaddr) >> 16) & 0xFF)
#define	PCICFG_FN(vaddr)	PCI_FUNC(((vaddr) >> 16) & 0xFF)
#define	PCICFG_REG(vaddr)	((vaddr) & 0xFFFF)
#define	PCICFG_SIGNATURE(vendor, device) ((vendor << 16) | device)

extern int pcicfg_find(pcicfg_vaddr *addr, const __ggi_sys_u32 *signatures);

extern __ggi_sys_u8  pcicfg_in8 (const pcicfg_vaddr vaddr);
extern __ggi_sys_u16 pcicfg_in16(const pcicfg_vaddr vaddr);
extern __ggi_sys_u32 pcicfg_in32(const pcicfg_vaddr vaddr);

extern void pcicfg_out8 (const __ggi_sys_u8  val, const pcicfg_vaddr vaddr);
extern void pcicfg_out16(const __ggi_sys_u16 val, const pcicfg_vaddr vaddr);
extern void pcicfg_out32(const __ggi_sys_u32 val, const pcicfg_vaddr vaddr);

#endif	/* #ifdef __GGI_SYS_IO_HAS_PCICFG	*/


/* ----------------------------------------------------------------------------
**	Intel style in/out I/O space
** ----------------------------------------------------------------------------
**	The io_in.. / io_out.. functions generate the neccesary hardware
**	actions to do a read/write cycle in a cards I/O space. (Registers
**	accessed via the in.. / out.. instructions on i386 architecture.)
*/
#ifdef __GGI_SYS_IO_HAS_IO

#define	IO_NULL	((io_vaddr) 0)	/* an invalid virtual address	*/
#define	IO_DECODE_ALL	((io_addr) -1)	/* all lines being decoded	*/

typedef unsigned int	io_addr;	/* the physical address type	*/
typedef unsigned int	io_vaddr;	/* the virtual address type	*/

struct io_region
{
	pcicfg_vaddr	device;		/* (PCI) device this belongs to	*/
	io_vaddr	base_virt;	/* virtual base address		*/
	io_addr		base_io;	/* I/O base address		*/
	io_addr		base_bus;	/* bus address			*/
	io_addr		base_phys;	/* physical address		*/
	io_addr		size;		/* size of region		*/
	io_addr		decode;		/* decoded I/O address lines	*/
	char		*name;		/* name of the region		*/
};

extern int	io_check_region(struct io_region *r);
extern io_vaddr io_claim_region(struct io_region *r);
extern io_vaddr io_free_region(struct io_region *r);
extern io_vaddr io_alloc_region(struct io_region *r);

extern __ggi_sys_u8  io_in8 (const io_vaddr vaddr);
extern __ggi_sys_u16 io_in16(const io_vaddr vaddr);
extern __ggi_sys_u32 io_in32(const io_vaddr vaddr);

extern void io_out8 (const __ggi_sys_u8  val, const io_vaddr vaddr);
extern void io_out16(const __ggi_sys_u16 val, const io_vaddr vaddr);
extern void io_out32(const __ggi_sys_u32 val, const io_vaddr vaddr);

extern void io_ins8 (const io_vaddr addr, void *buf, unsigned long cnt);
extern void io_ins16(const io_vaddr addr, void *buf, unsigned long cnt);
extern void io_ins32(const io_vaddr addr, void *buf, unsigned long cnt);

extern void io_outs8 (const io_vaddr addr, const void *buf, unsigned long cnt);
extern void io_outs16(const io_vaddr addr, const void *buf, unsigned long cnt);
extern void io_outs32(const io_vaddr addr, const void *buf, unsigned long cnt);

#endif	/* #ifdef __GGI_SYS_IO_HAS_IO	*/

/* ----------------------------------------------------------------------------
**	memory I/O space
** ----------------------------------------------------------------------------
**	This is the 'normal' shared memory I/O space accessed using the
**	mov instructions on i386 architecture. The difference between
**	*vaddr = val and mem_out32(val, vaddr) is that the latter will not
**	be optimized away when compiling with maximum optimization.
*/
#ifdef __GGI_SYS_IO_HAS_MEM

#define	MEM_NULL	((mem_vaddr) 0)	/* an invalid virtual address	*/
#define	MEM_DECODE_ALL	((mem_addr) -1)	/* all lines being decoded	*/

typedef unsigned int	mem_addr;	/* the physical address type	*/
typedef void *		mem_vaddr;	/* the virtual address type	*/

struct  mem_region
{
	pcicfg_vaddr	device;		/* (PCI) device this belongs to	*/
	mem_vaddr	base_virt;	/* virtual base address		*/
	mem_addr	base_io;	/* the I/O base address		*/
	mem_addr	base_bus;	/* bus address			*/
	mem_addr	base_phys;	/* physical address		*/
	mem_addr	size;		/* size of region		*/
	mem_addr	decode;		/* decoded io address lines	*/
	char		*name;		/* name of the region		*/
};

extern int	 mem_check_region(struct mem_region *r);
extern mem_vaddr mem_claim_region(struct mem_region *r);
extern mem_vaddr mem_free_region(struct mem_region *r);
extern mem_vaddr mem_alloc_region(struct mem_region *r);

extern __ggi_sys_u8  mem_in8 (const mem_vaddr vaddr);
extern __ggi_sys_u16 mem_in16(const mem_vaddr vaddr);
extern __ggi_sys_u32 mem_in32(const mem_vaddr vaddr);

extern void mem_out8 (const __ggi_sys_u8  val, const mem_vaddr vaddr);
extern void mem_out16(const __ggi_sys_u16 val, const mem_vaddr vaddr);
extern void mem_out32(const __ggi_sys_u32 val, const mem_vaddr vaddr);

extern void mem_ins8 (const mem_vaddr addr, void *buf, unsigned long cnt);
extern void mem_ins16(const mem_vaddr addr, void *buf, unsigned long cnt);
extern void mem_ins32(const mem_vaddr addr, void *buf, unsigned long cnt);

extern void mem_outs8 (const mem_vaddr addr, const void *buf,unsigned long cnt);
extern void mem_outs16(const mem_vaddr addr, const void *buf,unsigned long cnt);
extern void mem_outs32(const mem_vaddr addr, const void *buf,unsigned long cnt);

#endif	/* #ifdef GGI_SYS_IO_HAS_MEM	*/

/* ----------------------------------------------------------------------------
**	irq handling
** ----------------------------------------------------------------------------
**	use similar to io regions.
*/
#ifdef __GGI_SYS_IO_HAS_IRQ

#define	IRQ_NULL	((unsigned int) -1)	/* an invalid irq line	*/
#define	IRQ_DECODE_ALL	((irq_mask) -1)	/* all lines being decoded	*/

typedef unsigned int	irq_mask;

enum irq_flags
{
	IF_SHARED_IRQ	= 0x00000001
};

struct  irq_line
{
	enum irq_flags	flags;		/* properties			*/
	char		*name;		/* name of the line		*/
	unsigned int	line;		/* requested IRQ line		*/

	void *high_priv;		/* high priority handler priv.	*/
	int (*High)(void *priv, void *regs); /* high priority handler	*/

	void *low_priv;			/* low priority handler private	*/
	void (*Low)(void *priv);	/* low priority handler, improve!!! */
};

/*	These may be OS dependent, and have to be supplied by the OS/kernel
**	layer. As they aren't performance critical either, a call doesn't hurt.
*/
extern int  irq_claim_line(struct irq_line *irq);
extern void irq_free_line(struct irq_line *irq);

#endif	/* #ifdef __GGI_SYS_IO_HAS_IRQ	*/

#undef	__GGI_SYS_IO_HAS_PCICFG
#undef	__GGI_SYS_IO_HAS_IO
#undef	__GGI_SYS_IO_HAS_MEM
#undef	__GGI_SYS_IO_HAS_IRQ

#endif	/* #ifdef __ggi_io_h */
=== io.h ======================================================================

Index: [thread] [date] [subject] [author]