diff options
Diffstat (limited to 'arch/sparc/kernel/ioport.c')
-rw-r--r-- | arch/sparc/kernel/ioport.c | 572 |
1 files changed, 486 insertions, 86 deletions
diff --git a/arch/sparc/kernel/ioport.c b/arch/sparc/kernel/ioport.c index 74706e79a..e2b6b1ae4 100644 --- a/arch/sparc/kernel/ioport.c +++ b/arch/sparc/kernel/ioport.c @@ -1,139 +1,539 @@ -/* $Id: ioport.c,v 1.24 1997/04/10 03:02:32 davem Exp $ +/* $Id: ioport.c,v 1.28 1999/12/27 06:08:28 anton Exp $ * ioport.c: Simple io mapping allocator. * * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) * - * The routines in this file should be changed for a memory allocator - * that would be setup just like NetBSD does : you create regions that - * are administered by a general purpose allocator, and then you call - * that allocator with your handle and the block size instead of this - * weak stuff. + * 1996: sparc_free_io, 1999: ioremap()/iounmap() by Pete Zaitcev. */ +#include <linux/config.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/ioport.h> #include <linux/mm.h> +#include <linux/malloc.h> #include <asm/io.h> #include <asm/vaddrs.h> #include <asm/oplib.h> #include <asm/page.h> +#include <asm/pgalloc.h> #include <asm/pgtable.h> -/* This points to the next to use virtual memory for io mappings */ -static unsigned long dvma_next_free = DVMA_VADDR; -unsigned long sparc_iobase_vaddr = IOBASE_VADDR; +struct resource *sparc_find_resource_bystart(struct resource *, unsigned long); +struct resource *sparc_find_resource_by_hit(struct resource *, unsigned long); + +static void *_sparc_ioremap(struct resource *res, u32 bus, u32 pa, int sz); +static void *_sparc_alloc_io(unsigned int busno, unsigned long phys, + unsigned long size, char *name); +static void _sparc_free_io(struct resource *res); + +/* This points to the next to use virtual memory for DVMA mappings */ +static struct resource sparc_dvma = { + "sparc_dvma", DVMA_VADDR, DVMA_VADDR + DVMA_LEN - 1 +}; +/* This points to the start of I/O mappings, cluable from outside. */ + struct resource sparc_iomap = { + "sparc_iomap", IOBASE_VADDR, IOBASE_END-1 +}; /* - * sparc_alloc_io: - * Map and allocates an obio device. - * Implements a simple linear allocator, you can force the function - * to use your own mapping, but in practice this should not be used. - * - * Input: - * address: the obio address to map - * virtual: if non zero, specifies a fixed virtual address where - * the mapping should take place. - * len: the length of the mapping - * bus_type: The bus on which this io area sits. + * Our mini-allocator... + * Boy this is gross! We need it because we must map I/O for + * timers and interrupt controller before the kmalloc is available. + */ + +#define XNMLN 15 +#define XNRES 10 /* SS-10 uses 8 */ + +struct xresource { + struct resource xres; /* Must be first */ + int xflag; /* 1 == used */ + char xname[XNMLN+1]; +}; + +static struct xresource xresv[XNRES]; + +static struct xresource *xres_alloc(void) { + struct xresource *xrp; + int n; + + xrp = xresv; + for (n = 0; n < XNRES; n++) { + if (xrp->xflag == 0) { + xrp->xflag = 1; + return xrp; + } + xrp++; + } + return NULL; +} + +static void xres_free(struct xresource *xrp) { + xrp->xflag = 0; +} + +/* + */ +extern void sun4c_mapioaddr(unsigned long, unsigned long, int bus_type, int rdonly); +extern void srmmu_mapioaddr(unsigned long, unsigned long, int bus_type, int rdonly); + +static void mapioaddr(unsigned long physaddr, unsigned long virt_addr, + int bus, int rdonly) +{ + switch(sparc_cpu_model) { + case sun4c: + case sun4: + sun4c_mapioaddr(physaddr, virt_addr, bus, rdonly); + break; + case sun4m: + case sun4d: + case sun4e: + srmmu_mapioaddr(physaddr, virt_addr, bus, rdonly); + break; + default: + printk("mapioaddr: Trying to map IO space for unsupported machine.\n"); + printk("mapioaddr: sparc_cpu_model = %d\n", sparc_cpu_model); + printk("mapioaddr: Halting...\n"); + halt(); + }; + return; +} + +extern void srmmu_unmapioaddr(unsigned long virt); +extern void sun4c_unmapioaddr(unsigned long virt); + +static void unmapioaddr(unsigned long virt_addr) +{ + switch(sparc_cpu_model) { + case sun4c: + case sun4: + sun4c_unmapioaddr(virt_addr); + break; + case sun4m: + case sun4d: + case sun4e: + srmmu_unmapioaddr(virt_addr); + break; + default: + printk("unmapioaddr: sparc_cpu_model = %d, halt...\n", sparc_cpu_model); + halt(); + }; + return; +} + +/* + * These are typically used in PCI drivers + * which are trying to be cross-platform. * - * Returns: - * The virtual address where the mapping actually took place. + * Bus type is always zero on IIep. */ +void *ioremap(unsigned long offset, unsigned long size) +{ + char name[14]; + + sprintf(name, "phys_%08x", (u32)offset); + return _sparc_alloc_io(0, offset, size, name); +} -void *sparc_alloc_io (u32 address, void *virtual, int len, char *name, - u32 bus_type, int rdonly) +/* + * Comlimentary to ioremap(). + */ +void iounmap(void *virtual) { - unsigned long vaddr, base_address; - unsigned long addr = (unsigned long) address; - unsigned long offset = (addr & (~PAGE_MASK)); + unsigned long vaddr = (unsigned long) virtual & PAGE_MASK; + struct resource *res; - if (virtual) { - vaddr = (unsigned long) virtual; + if ((res = sparc_find_resource_bystart(&sparc_iomap, vaddr)) == NULL) { + printk("free_io/iounmap: cannot free %lx\n", vaddr); + return; + } + _sparc_free_io(res); - len += offset; - if(((unsigned long) virtual + len) > (IOBASE_VADDR + IOBASE_LEN)) { - prom_printf("alloc_io: Mapping outside IOBASE area\n"); - prom_halt(); - } - if(check_region ((vaddr | offset), len)) { - prom_printf("alloc_io: 0x%lx is already in use\n", vaddr); - prom_halt(); - } + if ((char *)res >= (char*)xresv && (char *)res < (char *)&xresv[XNRES]) { + xres_free((struct xresource *)res); + } else { + kfree(res); + } +} + +/* + * Davem's version of sbus_ioremap. + */ +unsigned long sbus_ioremap(struct resource *phyres, unsigned long offset, + unsigned long size, char *name) +{ + return (unsigned long) _sparc_alloc_io(phyres->flags & 0xF, + phyres->start + offset, size, name); +} - /* Tell Linux resource manager about the mapping */ - request_region ((vaddr | offset), len, name); +/* + */ +void sbus_iounmap(unsigned long addr, unsigned long size) +{ + iounmap((void *)addr); +} + +/* + * Meat of mapping + */ +static void *_sparc_alloc_io(unsigned int busno, unsigned long phys, + unsigned long size, char *name) +{ + static int printed_full = 0; + struct xresource *xres; + struct resource *res; + char *tack; + int tlen; + void *va; /* P3 diag */ + + if (name == NULL) name = "???"; + + if ((xres = xres_alloc()) != 0) { + tack = xres->xname; + res = &xres->xres; } else { - vaddr = occupy_region(sparc_iobase_vaddr, IOBASE_END, - (offset + len + PAGE_SIZE-1) & PAGE_MASK, PAGE_SIZE, name); - if (vaddr == 0) { - /* Usually we cannot see printks in this case. */ - prom_printf("alloc_io: cannot occupy %d region\n", len); - prom_halt(); + if (!printed_full) { + printk("ioremap: done with statics, switching to malloc\n"); + printed_full = 1; } + tlen = strlen(name); + tack = kmalloc(sizeof (struct resource) + tlen + 1, GFP_KERNEL); + if (tack == NULL) return NULL; + res = (struct resource *) tack; + tack += sizeof (struct resource); } - base_address = vaddr; - /* Do the actual mapping */ - for (; len > 0; len -= PAGE_SIZE) { - mapioaddr(addr, vaddr, bus_type, rdonly); - vaddr += PAGE_SIZE; - addr += PAGE_SIZE; - } + strncpy(tack, name, XNMLN); + tack[XNMLN] = 0; + res->name = tack; - return (void *) (base_address | offset); + va = _sparc_ioremap(res, busno, phys, size); + /* printk("ioremap(0x%x:%08lx[0x%lx])=%p\n", busno, phys, size, va); */ /* P3 diag */ + return va; } -void sparc_free_io (void *virtual, int len) +/* + * This is called from _sparc_alloc_io only, we left it separate + * in case Davem changes his mind about interface to sbus_ioremap(). + */ +static void * +_sparc_ioremap(struct resource *res, u32 bus, u32 pa, int sz) { - unsigned long vaddr = (unsigned long) virtual & PAGE_MASK; - unsigned long plen = (((unsigned long)virtual & ~PAGE_MASK) + len + PAGE_SIZE-1) & PAGE_MASK; + unsigned long offset = ((unsigned long) pa) & (~PAGE_MASK); + unsigned long va; + unsigned int psz; + + if (allocate_resource(&sparc_iomap, res, + (offset + sz + PAGE_SIZE-1) & PAGE_MASK, + sparc_iomap.start, sparc_iomap.end, PAGE_SIZE, NULL, NULL) != 0) { + /* Usually we cannot see printks in this case. */ + prom_printf("alloc_io_res(%s): cannot occupy\n", + (res->name != NULL)? res->name: "???"); + prom_halt(); + } + + va = res->start; + pa &= PAGE_MASK; + for (psz = res->end - res->start + 1; psz != 0; psz -= PAGE_SIZE) { + mapioaddr(pa, va, bus, 0); + va += PAGE_SIZE; + pa += PAGE_SIZE; + } + + /* + * XXX Playing with implementation details here. + * On sparc64 Ebus has resources with precise boundaries. + * We share drivers with sparc64. Too clever drivers use + * start of a resource instead of a base adress. + * + * XXX-2 This may be not valid anymore, clean when + * interface to sbus_ioremap() is resolved. + */ + res->start += offset; + res->end = res->start + sz - 1; /* not strictly necessary.. */ + + return (void *) res->start; +} - release_region(vaddr, plen); +/* + * Comlimentary to _sparc_ioremap(). + */ +static void _sparc_free_io(struct resource *res) +{ + unsigned long plen; - for (; plen != 0;) { + plen = res->end - res->start + 1; + while (plen != 0) { plen -= PAGE_SIZE; - unmapioaddr(vaddr + plen); + unmapioaddr(res->start + plen); + } + + release_resource(res); +} + +#ifdef CONFIG_SBUS + +void sbus_set_sbus64(struct sbus_dev *sdev, int x) { + printk("sbus_set_sbus64: unsupported\n"); +} + +/* + * Allocate a chunk of memory suitable for DMA. + * Typically devices use them for control blocks. + * CPU may access them without any explicit flushing. + */ +void *sbus_alloc_consistant(struct sbus_dev *sdev, long len, u32 *dma_addrp) +{ + unsigned long len_total = (len + PAGE_SIZE-1) & PAGE_MASK; + unsigned long va; + struct resource *res; + int order; + + /* XXX why are some lenghts signed, others unsigned? */ + if (len <= 0) { + return NULL; + } + /* XXX So what is maxphys for us and how do drivers know it? */ + if (len > 256*1024) { /* __get_free_pages() limit */ + return NULL; } + + for (order = 0; order < 6; order++) /* 2^6 pages == 256K */ + if ((1 << (order + PAGE_SHIFT)) >= len_total) + break; + va = __get_free_pages(GFP_KERNEL, order); + if (va == 0) { + /* + * printk here may be flooding... Consider removal XXX. + */ + printk("sbus_alloc_consistant: no %ld pages\n", len_total>>PAGE_SHIFT); + return NULL; + } + + if ((res = kmalloc(sizeof(struct resource), GFP_KERNEL)) == NULL) { + free_pages(va, order); + printk("sbus_alloc_consistant: no core\n"); + return NULL; + } + + if (allocate_resource(&sparc_dvma, res, len_total, + sparc_dvma.start, sparc_dvma.end, PAGE_SIZE, NULL, NULL) != 0) { + printk("sbus_alloc_consistant: cannot occupy 0x%lx", len); + free_pages(va, order); + kfree(res); + return NULL; + } + + *dma_addrp = res->start; + mmu_map_dma_area(va, res->start, len); + + /* + * "Official" or "natural" address of pages we got is va. + * We want to return uncached range. We could make va[len] + * uncached but it's difficult to make cached back [P3: hmm] + * We use the artefact of sun4c, replicated everywhere else, + * that CPU can use bus addresses to access the same memory. + */ + res->name = (void *)va; /* XXX Ouch.. we got to hide it somewhere */ + return (void *)res->start; } -/* Does DVMA allocations with PAGE_SIZE granularity. How this basically - * works is that the ESP chip can do DVMA transfers at ANY address with - * certain size and boundary restrictions. But other devices that are - * attached to it and would like to do DVMA have to set things up in - * a special way, if the DVMA sees a device attached to it transfer data - * at addresses above DVMA_VADDR it will grab them, this way it does not - * now have to know the peculiarities of where to read the Lance data - * from. (for example) +void sbus_free_consistant(struct sbus_dev *sdev, long n, void *p, u32 ba) +{ + struct resource *res; + unsigned long pgp; + int order; + + if ((res = sparc_find_resource_bystart(&sparc_dvma, + (unsigned long)p)) == NULL) { + printk("sbus_free_consistant: cannot free %p\n", p); + return; + } + + if (((unsigned long)p & (PAGE_MASK-1)) != 0) { + printk("sbus_free_consistant: unaligned va %p\n", p); + return; + } + + n = (n + PAGE_SIZE-1) & PAGE_MASK; + if ((res->end-res->start)+1 != n) { + printk("sbus_free_consistant: region 0x%lx asked 0x%lx\n", + (long)((res->end-res->start)+1), n); + return; + } + + mmu_inval_dma_area((unsigned long)res->name, n); /* XXX Ouch */ + mmu_unmap_dma_area(ba, n); + release_resource(res); + + pgp = (unsigned long) res->name; /* XXX Ouch */ + for (order = 0; order < 6; order++) + if ((1 << (order + PAGE_SHIFT)) >= n) + break; + free_pages(pgp, order); + + kfree(res); +} + +/* + * Map a chunk of memory so that devices can see it. + * CPU view of this memory may be inconsistent with + * a device view and explicit flushing is necessary. */ -void *_sparc_dvma_malloc (int len, char *name) +u32 sbus_map_single(struct sbus_dev *sdev, void *va, long len) { - unsigned long vaddr, base_address; +#if 0 /* This is the version that abuses consistant space */ + unsigned long len_total = (len + PAGE_SIZE-1) & PAGE_MASK; + struct resource *res; - vaddr = dvma_next_free; - if(check_region (vaddr, len)) { - prom_printf("alloc_dma: 0x%lx is already in use\n", vaddr); - prom_halt(); + /* XXX why are some lenghts signed, others unsigned? */ + if (len <= 0) { + return 0; } - if(vaddr + len > (DVMA_VADDR + DVMA_LEN)) { - prom_printf("alloc_dvma: out of dvma memory\n"); - prom_halt(); + /* XXX So what is maxphys for us and how do drivers know it? */ + if (len > 256*1024) { /* __get_free_pages() limit */ + return 0; + } + + if ((res = kmalloc(sizeof(struct resource), GFP_KERNEL)) == NULL) { + printk("sbus_map_single: no core\n"); + return 0; + } + res->name = va; + + if (allocate_resource(&sparc_dvma, res, len_total, + sparc_dvma.start, sparc_dvma.end, PAGE_SIZE) != 0) { + printk("sbus_map_single: cannot occupy 0x%lx", len); + kfree(res); + return 0; + } + + mmu_map_dma_area(va, res->start, len_total); + mmu_flush_dma_area((unsigned long)va, len_total); /* in all contexts? */ + + return res->start; +#endif +#if 1 /* "trampoline" version */ + /* XXX why are some lenghts signed, others unsigned? */ + if (len <= 0) { + return 0; + } + /* XXX So what is maxphys for us and how do drivers know it? */ + if (len > 256*1024) { /* __get_free_pages() limit */ + return 0; + } +/* BTFIXUPDEF_CALL(__u32, mmu_get_scsi_one, char *, unsigned long, struct sbus_bus *sbus) */ + return mmu_get_scsi_one(va, len, sdev->bus); +#endif +} + +void sbus_unmap_single(struct sbus_dev *sdev, u32 ba, long n) +{ +#if 0 /* This is the version that abuses consistant space */ + struct resource *res; + unsigned long va; + + if ((res = sparc_find_resource_bystart(&sparc_dvma, ba)) == NULL) { + printk("sbus_unmap_single: cannot find %08x\n", (unsigned)ba); + return; } - /* Basically these can be mapped just like any old - * IO pages, cacheable bit off, etc. The physical - * pages are now mapped dynamically to save space. + n = (n + PAGE_SIZE-1) & PAGE_MASK; + if ((res->end-res->start)+1 != n) { + printk("sbus_unmap_single: region 0x%lx asked 0x%lx\n", + (long)((res->end-res->start)+1), n); + return; + } + + va = (unsigned long) res->name; /* XXX Ouch */ + mmu_inval_dma_area(va, n); /* in all contexts, mm's?... */ + mmu_unmap_dma_area(ba, n); /* iounit cache flush is here */ + release_resource(res); + kfree(res); +#endif +#if 1 /* "trampoline" version */ +/* BTFIXUPDEF_CALL(void, mmu_release_scsi_one, __u32, unsigned long, struct sbus_bus *sbus) */ + mmu_release_scsi_one(ba, n, sdev->bus); +#endif +} + +int sbus_map_sg(struct sbus_dev *sdev, struct scatterlist *sg, int n) +{ +/* BTFIXUPDEF_CALL(void, mmu_get_scsi_sgl, struct scatterlist *, int, struct sbus_bus *sbus) */ + mmu_get_scsi_sgl(sg, n, sdev->bus); + + /* + * XXX sparc64 can return a partial length here. sun4c should do this + * but it currently panics if it can't fulfill the request - Anton */ - base_address = vaddr; - mmu_map_dma_area(base_address, len); - /* Assign the memory area. */ - dvma_next_free = PAGE_ALIGN(dvma_next_free+len); + return n; +} - request_region(base_address, len, name); +void sbus_unmap_sg(struct sbus_dev *sdev, struct scatterlist *sg, int n) +{ +/* BTFIXUPDEF_CALL(void, mmu_release_scsi_sgl, struct scatterlist *, int, struct sbus_bus *sbus) */ + mmu_release_scsi_sgl(sg, n, sdev->bus); +} +#endif + +/* + * P3: I think a partial flush is permitted... + * We are not too efficient at doing it though. + * + * If only DaveM understood a concept of an allocation cookie, + * we could avoid find_resource_by_hit() here and a major + * performance hit. + */ +void sbus_dma_sync_single(struct sbus_dev *sdev, u32 ba, long size) +{ + unsigned long va; + struct resource *res; + + res = sparc_find_resource_by_hit(&sparc_dvma, ba); + if (res == NULL) + panic("sbus_dma_sync_single: 0x%x\n", ba); + + va = (unsigned long) res->name; + /* if (va == 0) */ + + mmu_inval_dma_area(va, (res->end - res->start) + 1); +} + +void sbus_dma_sync_sg(struct sbus_dev *sdev, struct scatterlist *sg, int n) +{ + printk("dma_sync_sg: not implemented yet\n"); +} + +/* + * This is a version of find_resource and it belongs to kernel/resource.c. + * Until we have agreement with Linus and Martin, it lingers here. + * + * "same start" is more strict than "hit into" + */ +struct resource * +sparc_find_resource_bystart(struct resource *root, unsigned long start) +{ + struct resource *tmp; + + for (tmp = root->child; tmp != 0; tmp = tmp->sibling) { + if (tmp->start == start) + return tmp; + } + return NULL; +} + +struct resource * +sparc_find_resource_by_hit(struct resource *root, unsigned long hit) +{ + struct resource *tmp; - return (void *) base_address; + for (tmp = root->child; tmp != 0; tmp = tmp->sibling) { + if (tmp->start <= hit && tmp->end >= hit) + return tmp; + } + return NULL; } |