Re: Accessing MMIO PCI space - crossplatform (fwd)

Jon M. Taylor (taylorj@ecs.csus.edu)
Fri, 13 Nov 1998 13:50:36 -0800 (PST)


Ignore the last version.

Jon

---
'Cloning and the reprogramming of DNA is the first serious step in 
becoming one with God.'
	- Scientist G. Richard Seed

---------- Forwarded message ---------- Date: Fri, 13 Nov 1998 12:32:07 +0100 (MET) From: Steffen Seeger <s.seeger@physik.tu-chemnitz.de> To: ggi-develop@eskimo.com Subject: 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 ======================================================================

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/