From ccfe27d7000668b02d10fc3e06aa49e3e3603162 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 14 Jan 2010 11:21:02 +0100 Subject: microblaze: Support DMA Add DMA support for Microblaze. There are some part of this new feature: 1. Basic DMA support 2. Enable DMA debug option 3. Setup notifier Ad 1. dma-mapping come from powerpc and x86 version and it is based on generic dma-mapping-common.h Ad 2. DMA support debug features which is used in generic file. For more information please look at Documentation/DMA-API.txt Ad 3. notifier is very important to setup dma_ops. Without this part for example ll_temac driver failed because there are no setup dma operations. Signed-off-by: Michal Simek --- arch/microblaze/Kconfig | 5 +- arch/microblaze/include/asm/device.h | 4 + arch/microblaze/include/asm/dma-mapping.h | 155 +++++++++++++++++++++++++++++- arch/microblaze/include/asm/io.h | 1 + arch/microblaze/kernel/Makefile | 2 +- arch/microblaze/kernel/dma.c | 124 ++++++++++++++++++++++++ arch/microblaze/kernel/setup.c | 36 +++++++ 7 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 arch/microblaze/kernel/dma.c (limited to 'arch') diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index b008168ae94..71ec0413741 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -14,6 +14,8 @@ config MICROBLAZE select USB_ARCH_HAS_EHCI select ARCH_WANT_OPTIONAL_GPIOLIB select HAVE_OPROFILE + select HAVE_DMA_ATTRS + select HAVE_DMA_API_DEBUG select TRACING_SUPPORT config SWAP @@ -76,9 +78,6 @@ config HAVE_LATENCYTOP_SUPPORT config PCI def_bool n -config NO_DMA - def_bool y - config DTC def_bool y diff --git a/arch/microblaze/include/asm/device.h b/arch/microblaze/include/asm/device.h index 78a038452c0..402b46e630f 100644 --- a/arch/microblaze/include/asm/device.h +++ b/arch/microblaze/include/asm/device.h @@ -14,6 +14,10 @@ struct device_node; struct dev_archdata { /* Optional pointer to an OF device node */ struct device_node *of_node; + + /* DMA operations on that device */ + struct dma_map_ops *dma_ops; + void *dma_data; }; struct pdev_archdata { diff --git a/arch/microblaze/include/asm/dma-mapping.h b/arch/microblaze/include/asm/dma-mapping.h index d00e4009916..096fa96ee73 100644 --- a/arch/microblaze/include/asm/dma-mapping.h +++ b/arch/microblaze/include/asm/dma-mapping.h @@ -1 +1,154 @@ -#include +/* + * Implements the generic device dma API for microblaze and the pci + * + * Copyright (C) 2009-2010 Michal Simek + * Copyright (C) 2009-2010 PetaLogix + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + * + * This file is base on powerpc and x86 dma-mapping.h versions + * Copyright (C) 2004 IBM + */ + +#ifndef _ASM_MICROBLAZE_DMA_MAPPING_H +#define _ASM_MICROBLAZE_DMA_MAPPING_H + +/* + * See Documentation/PCI/PCI-DMA-mapping.txt and + * Documentation/DMA-API.txt for documentation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DMA_ERROR_CODE (~(dma_addr_t)0x0) + +#define __dma_alloc_coherent(dev, gfp, size, handle) NULL +#define __dma_free_coherent(size, addr) ((void)0) +#define __dma_sync(addr, size, rw) ((void)0) +#define __dma_sync_page(pg, off, sz, rw) ((void)0) + +static inline unsigned long device_to_mask(struct device *dev) +{ + if (dev->dma_mask && *dev->dma_mask) + return *dev->dma_mask; + /* Assume devices without mask can take 32 bit addresses */ + return 0xfffffffful; +} + +extern struct dma_map_ops *dma_ops; + +/* + * Available generic sets of operations + */ +extern struct dma_map_ops dma_direct_ops; + +static inline struct dma_map_ops *get_dma_ops(struct device *dev) +{ + /* We don't handle the NULL dev case for ISA for now. We could + * do it via an out of line call but it is not needed for now. The + * only ISA DMA device we support is the floppy and we have a hack + * in the floppy driver directly to get a device for us. + */ + if (unlikely(!dev) || !dev->archdata.dma_ops) + return NULL; + + return dev->archdata.dma_ops; +} + +static inline void set_dma_ops(struct device *dev, struct dma_map_ops *ops) +{ + dev->archdata.dma_ops = ops; +} + +static inline int dma_supported(struct device *dev, u64 mask) +{ + struct dma_map_ops *ops = get_dma_ops(dev); + + if (unlikely(!ops)) + return 0; + if (!ops->dma_supported) + return 1; + return ops->dma_supported(dev, mask); +} + +#ifdef CONFIG_PCI +/* We have our own implementation of pci_set_dma_mask() */ +#define HAVE_ARCH_PCI_SET_DMA_MASK + +#endif + +static inline int dma_set_mask(struct device *dev, u64 dma_mask) +{ + struct dma_map_ops *ops = get_dma_ops(dev); + + if (unlikely(ops == NULL)) + return -EIO; + if (ops->set_dma_mask) + return ops->set_dma_mask(dev, dma_mask); + if (!dev->dma_mask || !dma_supported(dev, dma_mask)) + return -EIO; + *dev->dma_mask = dma_mask; + return 0; +} + +#include + +static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr) +{ + struct dma_map_ops *ops = get_dma_ops(dev); + if (ops->mapping_error) + return ops->mapping_error(dev, dma_addr); + + return (dma_addr == DMA_ERROR_CODE); +} + +#define dma_alloc_noncoherent(d, s, h, f) dma_alloc_coherent(d, s, h, f) +#define dma_free_noncoherent(d, s, v, h) dma_free_coherent(d, s, v, h) +#define dma_is_consistent(d, h) (1) + +static inline void *dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flag) +{ + struct dma_map_ops *ops = get_dma_ops(dev); + void *memory; + + BUG_ON(!ops); + + memory = ops->alloc_coherent(dev, size, dma_handle, flag); + + debug_dma_alloc_coherent(dev, size, *dma_handle, memory); + return memory; +} + +static inline void dma_free_coherent(struct device *dev, size_t size, + void *cpu_addr, dma_addr_t dma_handle) +{ + struct dma_map_ops *ops = get_dma_ops(dev); + + BUG_ON(!ops); + debug_dma_free_coherent(dev, size, cpu_addr, dma_handle); + ops->free_coherent(dev, size, cpu_addr, dma_handle); +} + +static inline int dma_get_cache_alignment(void) +{ + return L1_CACHE_BYTES; +} + +static inline void dma_cache_sync(struct device *dev, void *vaddr, size_t size, + enum dma_data_direction direction) +{ + BUG_ON(direction == DMA_NONE); + __dma_sync(vaddr, size, (int)direction); +} + +#endif /* _ASM_MICROBLAZE_DMA_MAPPING_H */ diff --git a/arch/microblaze/include/asm/io.h b/arch/microblaze/include/asm/io.h index 267c7c779e5..9ac409ad906 100644 --- a/arch/microblaze/include/asm/io.h +++ b/arch/microblaze/include/asm/io.h @@ -16,6 +16,7 @@ #include #include /* Get struct page {...} */ +#define PCI_DRAM_OFFSET 0 #define IO_SPACE_LIMIT (0xFFFFFFFF) diff --git a/arch/microblaze/kernel/Makefile b/arch/microblaze/kernel/Makefile index b07594eccf9..e51bc152082 100644 --- a/arch/microblaze/kernel/Makefile +++ b/arch/microblaze/kernel/Makefile @@ -14,7 +14,7 @@ endif extra-y := head.o vmlinux.lds -obj-y += exceptions.o \ +obj-y += dma.o exceptions.o \ hw_exception_handler.o init_task.o intc.o irq.o of_device.o \ of_platform.o process.o prom.o prom_parse.o ptrace.o \ setup.o signal.o sys_microblaze.o timer.o traps.o reset.o diff --git a/arch/microblaze/kernel/dma.c b/arch/microblaze/kernel/dma.c new file mode 100644 index 00000000000..300fea46737 --- /dev/null +++ b/arch/microblaze/kernel/dma.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2009-2010 PetaLogix + * Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corporation + * + * Provide default implementations of the DMA mapping callbacks for + * directly mapped busses. + */ + +#include +#include +#include +#include + +/* + * Generic direct DMA implementation + * + * This implementation supports a per-device offset that can be applied if + * the address at which memory is visible to devices is not 0. Platform code + * can set archdata.dma_data to an unsigned long holding the offset. By + * default the offset is PCI_DRAM_OFFSET. + */ + +static unsigned long get_dma_direct_offset(struct device *dev) +{ + if (dev) + return (unsigned long)dev->archdata.dma_data; + + return PCI_DRAM_OFFSET; /* FIXME Not sure if is correct */ +} + +void *dma_direct_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flag) +{ + void *ret; + struct page *page; + int node = dev_to_node(dev); + + /* ignore region specifiers */ + flag &= ~(__GFP_HIGHMEM); + + page = alloc_pages_node(node, flag, get_order(size)); + if (page == NULL) + return NULL; + ret = page_address(page); + memset(ret, 0, size); + *dma_handle = virt_to_phys(ret) + get_dma_direct_offset(dev); + + return ret; +} + +void dma_direct_free_coherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + free_pages((unsigned long)vaddr, get_order(size)); +} + +static int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, + int nents, enum dma_data_direction direction, + struct dma_attrs *attrs) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sgl, sg, nents, i) { + sg->dma_address = sg_phys(sg) + get_dma_direct_offset(dev); + sg->dma_length = sg->length; + __dma_sync_page(sg_page(sg), sg->offset, sg->length, direction); + } + + return nents; +} + +static void dma_direct_unmap_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction direction, + struct dma_attrs *attrs) +{ +} + +static int dma_direct_dma_supported(struct device *dev, u64 mask) +{ + return 1; +} + +static inline dma_addr_t dma_direct_map_page(struct device *dev, + struct page *page, + unsigned long offset, + size_t size, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + BUG_ON(dir == DMA_NONE); + __dma_sync_page(page, offset, size, dir); + return page_to_phys(page) + offset + get_dma_direct_offset(dev); +} + +static inline void dma_direct_unmap_page(struct device *dev, + dma_addr_t dma_address, + size_t size, + enum dma_data_direction direction, + struct dma_attrs *attrs) +{ +} + +struct dma_map_ops dma_direct_ops = { + .alloc_coherent = dma_direct_alloc_coherent, + .free_coherent = dma_direct_free_coherent, + .map_sg = dma_direct_map_sg, + .unmap_sg = dma_direct_unmap_sg, + .dma_supported = dma_direct_dma_supported, + .map_page = dma_direct_map_page, + .unmap_page = dma_direct_unmap_page, +}; +EXPORT_SYMBOL(dma_direct_ops); + +/* Number of entries preallocated for DMA-API debugging */ +#define PREALLOC_DMA_DEBUG_ENTRIES (1 << 16) + +static int __init dma_init(void) +{ + dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES); + + return 0; +} +fs_initcall(dma_init); diff --git a/arch/microblaze/kernel/setup.c b/arch/microblaze/kernel/setup.c index bb8c4b9ccb8..bc325ac4efd 100644 --- a/arch/microblaze/kernel/setup.c +++ b/arch/microblaze/kernel/setup.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -188,3 +190,37 @@ static int microblaze_debugfs_init(void) } arch_initcall(microblaze_debugfs_init); #endif + +static int dflt_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + + /* We are only intereted in device addition */ + if (action != BUS_NOTIFY_ADD_DEVICE) + return 0; + + set_dma_ops(dev, &dma_direct_ops); + + return NOTIFY_DONE; +} + +static struct notifier_block dflt_plat_bus_notifier = { + .notifier_call = dflt_bus_notify, + .priority = INT_MAX, +}; + +static struct notifier_block dflt_of_bus_notifier = { + .notifier_call = dflt_bus_notify, + .priority = INT_MAX, +}; + +static int __init setup_bus_notifier(void) +{ + bus_register_notifier(&platform_bus_type, &dflt_plat_bus_notifier); + bus_register_notifier(&of_platform_bus_type, &dflt_of_bus_notifier); + + return 0; +} + +arch_initcall(setup_bus_notifier); -- cgit v1.2.3 From 2549edd353196d7de9c18e08146d7a8836f97235 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 20 Jan 2010 14:36:24 +0100 Subject: microblaze: Implement __dma_sync_page There is necessary to do some cache handling for dma operations. Signed-off-by: Michal Simek --- arch/microblaze/include/asm/dma-mapping.h | 1 - arch/microblaze/kernel/dma.c | 26 +++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/dma-mapping.h b/arch/microblaze/include/asm/dma-mapping.h index 096fa96ee73..18b3731c850 100644 --- a/arch/microblaze/include/asm/dma-mapping.h +++ b/arch/microblaze/include/asm/dma-mapping.h @@ -34,7 +34,6 @@ #define __dma_alloc_coherent(dev, gfp, size, handle) NULL #define __dma_free_coherent(size, addr) ((void)0) #define __dma_sync(addr, size, rw) ((void)0) -#define __dma_sync_page(pg, off, sz, rw) ((void)0) static inline unsigned long device_to_mask(struct device *dev) { diff --git a/arch/microblaze/kernel/dma.c b/arch/microblaze/kernel/dma.c index 300fea46737..64bc39f40ba 100644 --- a/arch/microblaze/kernel/dma.c +++ b/arch/microblaze/kernel/dma.c @@ -10,6 +10,7 @@ #include #include #include +#include /* * Generic direct DMA implementation @@ -20,6 +21,23 @@ * default the offset is PCI_DRAM_OFFSET. */ +static inline void __dma_sync_page(void *vaddr, unsigned long offset, + size_t size, enum dma_data_direction direction) +{ + unsigned long start = virt_to_phys(vaddr); + + switch (direction) { + case DMA_TO_DEVICE: + flush_dcache_range(start + offset, start + offset + size); + break; + case DMA_FROM_DEVICE: + invalidate_dcache_range(start + offset, start + offset + size); + break; + default: + BUG(); + } +} + static unsigned long get_dma_direct_offset(struct device *dev) { if (dev) @@ -85,11 +103,11 @@ static inline dma_addr_t dma_direct_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, - enum dma_data_direction dir, + enum dma_data_direction direction, struct dma_attrs *attrs) { - BUG_ON(dir == DMA_NONE); - __dma_sync_page(page, offset, size, dir); + BUG_ON(direction == DMA_NONE); + __dma_sync_page(page, offset, size, direction); return page_to_phys(page) + offset + get_dma_direct_offset(dev); } @@ -99,6 +117,8 @@ static inline void dma_direct_unmap_page(struct device *dev, enum dma_data_direction direction, struct dma_attrs *attrs) { +/* There is not necessary to do cache cleanup */ + /* __dma_sync_page(dma_address, 0 , size, direction); */ } struct dma_map_ops dma_direct_ops = { -- cgit v1.2.3 From a84642a339235020e6dccc022de27055f1fa9340 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 14 Jan 2010 17:03:49 +0100 Subject: microblaze: Add {z,}alloc_maybe_bootmem functions I will need {z,}alloc_maybe_bootmem functions for pci patches Signed-off-by: Michal Simek --- arch/microblaze/include/asm/system.h | 3 +++ arch/microblaze/mm/init.c | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) (limited to 'arch') diff --git a/arch/microblaze/include/asm/system.h b/arch/microblaze/include/asm/system.h index 157970688b2..59efb3fef95 100644 --- a/arch/microblaze/include/asm/system.h +++ b/arch/microblaze/include/asm/system.h @@ -87,6 +87,9 @@ void free_initmem(void); extern char *klimit; extern void ret_from_fork(void); +extern void *alloc_maybe_bootmem(size_t size, gfp_t mask); +extern void *zalloc_maybe_bootmem(size_t size, gfp_t mask); + #ifdef CONFIG_DEBUG_FS extern struct dentry *of_debugfs_root; #endif diff --git a/arch/microblaze/mm/init.c b/arch/microblaze/mm/init.c index a57cedf3671..6eea5544ad8 100644 --- a/arch/microblaze/mm/init.c +++ b/arch/microblaze/mm/init.c @@ -349,4 +349,27 @@ void __init *early_get_page(void) } return p; } + +void * __init_refok alloc_maybe_bootmem(size_t size, gfp_t mask) +{ + if (mem_init_done) + return kmalloc(size, mask); + else + return alloc_bootmem(size); +} + +void * __init_refok zalloc_maybe_bootmem(size_t size, gfp_t mask) +{ + void *p; + + if (mem_init_done) + p = kzalloc(size, mask); + else { + p = alloc_bootmem(size); + if (p) + memset(p, 0, size); + } + return p; +} + #endif /* CONFIG_MMU */ -- cgit v1.2.3 From c6ba01a4c7806d134c8d483525997559071d0990 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 14 Jan 2010 15:16:31 +0100 Subject: microblaze: Add irq_create_{of_,}mapping functions Support function for PCI. We don't use any advance mapping mechanism that's why implementation is simple. Signed-off-by: Michal Simek --- arch/microblaze/include/asm/irq.h | 37 ++++++++++++++++++++++++++++++++++++- arch/microblaze/kernel/irq.c | 15 +++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/irq.h b/arch/microblaze/include/asm/irq.h index 90f050535eb..31a35c33df6 100644 --- a/arch/microblaze/include/asm/irq.h +++ b/arch/microblaze/include/asm/irq.h @@ -14,6 +14,12 @@ #include +/* This type is the placeholder for a hardware interrupt number. It has to + * be big enough to enclose whatever representation is used by a given + * platform. + */ +typedef unsigned long irq_hw_number_t; + extern unsigned int nr_irq; #define NO_IRQ (-1) @@ -21,7 +27,8 @@ extern unsigned int nr_irq; struct pt_regs; extern void do_IRQ(struct pt_regs *regs); -/* irq_of_parse_and_map - Parse and Map an interrupt into linux virq space +/** + * irq_of_parse_and_map - Parse and Map an interrupt into linux virq space * @device: Device node of the device whose interrupt is to be mapped * @index: Index of the interrupt to map * @@ -40,4 +47,32 @@ static inline void irq_dispose_mapping(unsigned int virq) return; } +struct irq_host; + +/** + * irq_create_mapping - Map a hardware interrupt into linux virq space + * @host: host owning this hardware interrupt or NULL for default host + * @hwirq: hardware irq number in that host space + * + * Only one mapping per hardware interrupt is permitted. Returns a linux + * virq number. + * If the sense/trigger is to be specified, set_irq_type() should be called + * on the number returned from that call. + */ +extern unsigned int irq_create_mapping(struct irq_host *host, + irq_hw_number_t hwirq); + +/** + * irq_create_of_mapping - Map a hardware interrupt into linux virq space + * @controller: Device node of the interrupt controller + * @inspec: Interrupt specifier from the device-tree + * @intsize: Size of the interrupt specifier from the device-tree + * + * This function is identical to irq_create_mapping except that it takes + * as input informations straight from the device-tree (typically the results + * of the of_irq_map_*() functions. + */ +extern unsigned int irq_create_of_mapping(struct device_node *controller, + u32 *intspec, unsigned int intsize); + #endif /* _ASM_MICROBLAZE_IRQ_H */ diff --git a/arch/microblaze/kernel/irq.c b/arch/microblaze/kernel/irq.c index 0f06034d1fe..6f39e2c001f 100644 --- a/arch/microblaze/kernel/irq.c +++ b/arch/microblaze/kernel/irq.c @@ -93,3 +93,18 @@ skip: } return 0; } + +/* MS: There is no any advance mapping mechanism. We are using simple 32bit + intc without any cascades or any connection that's why mapping is 1:1 */ +unsigned int irq_create_mapping(struct irq_host *host, irq_hw_number_t hwirq) +{ + return hwirq; +} +EXPORT_SYMBOL_GPL(irq_create_mapping); + +unsigned int irq_create_of_mapping(struct device_node *controller, + u32 *intspec, unsigned int intsize) +{ + return intspec[0]; +} +EXPORT_SYMBOL_GPL(irq_create_of_mapping); -- cgit v1.2.3 From 2ddafeab6f159640299d17fb9b73b57f65011d85 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 14 Jan 2010 15:40:11 +0100 Subject: microblaze: io.h include asm-generic/iomap.h I need to use generic/iomap.h for PCI that's why is necessary to include it and fix ioport_{map,unmap} functions. Signed-off-by: Michal Simek --- arch/microblaze/include/asm/io.h | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/io.h b/arch/microblaze/include/asm/io.h index 9ac409ad906..f82df5d221a 100644 --- a/arch/microblaze/include/asm/io.h +++ b/arch/microblaze/include/asm/io.h @@ -15,6 +15,7 @@ #include #include #include /* Get struct page {...} */ +#include #define PCI_DRAM_OFFSET 0 @@ -228,15 +229,7 @@ static inline void __iomem *__ioremap(phys_addr_t address, unsigned long size, #define out_8(a, v) __raw_writeb((v), (a)) #define in_8(a) __raw_readb(a) -/* FIXME */ -static inline void __iomem *ioport_map(unsigned long port, unsigned int len) -{ - return (void __iomem *) (port); -} - -static inline void ioport_unmap(void __iomem *addr) -{ - /* Nothing to do */ -} +#define ioport_map(port, nr) ((void __iomem *)(port)) +#define ioport_unmap(addr) #endif /* _ASM_MICROBLAZE_IO_H */ -- cgit v1.2.3 From 830980a0a83e0b5af97c31f24dc7f1b57aa9ccea Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 18 Jan 2010 14:23:16 +0100 Subject: microblaze: Add pci.h Add pci.h for microblaze. It is based on powerpc pci.h Signed-off-by: Michal Simek --- arch/microblaze/include/asm/pci.h | 171 +++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/pci.h b/arch/microblaze/include/asm/pci.h index 9f0df5faf2c..fecc0448110 100644 --- a/arch/microblaze/include/asm/pci.h +++ b/arch/microblaze/include/asm/pci.h @@ -1 +1,170 @@ -#include +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Based on powerpc version + */ + +#ifndef __ASM_MICROBLAZE_PCI_H +#define __ASM_MICROBLAZE_PCI_H +#ifdef __KERNEL__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PCIBIOS_MIN_IO 0x1000 +#define PCIBIOS_MIN_MEM 0x10000000 + +struct pci_dev; + +/* Values for the `which' argument to sys_pciconfig_iobase syscall. */ +#define IOBASE_BRIDGE_NUMBER 0 +#define IOBASE_MEMORY 1 +#define IOBASE_IO 2 +#define IOBASE_ISA_IO 3 +#define IOBASE_ISA_MEM 4 + +#define pcibios_scan_all_fns(a, b) 0 + +/* + * Set this to 1 if you want the kernel to re-assign all PCI + * bus numbers (don't do that on ppc64 yet !) + */ +#define pcibios_assign_all_busses() \ + (pci_has_flag(PCI_REASSIGN_ALL_BUS)) + +static inline void pcibios_set_master(struct pci_dev *dev) +{ + /* No special bus mastering setup handling */ +} + +static inline void pcibios_penalize_isa_irq(int irq, int active) +{ + /* We don't do dynamic PCI IRQ allocation */ +} + +#ifdef CONFIG_PCI +extern void set_pci_dma_ops(struct dma_map_ops *dma_ops); +extern struct dma_map_ops *get_pci_dma_ops(void); +#else /* CONFIG_PCI */ +#define set_pci_dma_ops(d) +#define get_pci_dma_ops() NULL +#endif + +#ifdef CONFIG_PCI +static inline void pci_dma_burst_advice(struct pci_dev *pdev, + enum pci_dma_burst_strategy *strat, + unsigned long *strategy_parameter) +{ + *strat = PCI_DMA_BURST_INFINITY; + *strategy_parameter = ~0UL; +} +#endif + +extern int pci_domain_nr(struct pci_bus *bus); + +/* Decide whether to display the domain number in /proc */ +extern int pci_proc_domain(struct pci_bus *bus); + +struct vm_area_struct; +/* Map a range of PCI memory or I/O space for a device into user space */ +int pci_mmap_page_range(struct pci_dev *pdev, struct vm_area_struct *vma, + enum pci_mmap_state mmap_state, int write_combine); + +/* Tell drivers/pci/proc.c that we have pci_mmap_page_range() */ +#define HAVE_PCI_MMAP 1 + +extern int pci_legacy_read(struct pci_bus *bus, loff_t port, u32 *val, + size_t count); +extern int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, + size_t count); +extern int pci_mmap_legacy_page_range(struct pci_bus *bus, + struct vm_area_struct *vma, + enum pci_mmap_state mmap_state); + +#define HAVE_PCI_LEGACY 1 + +/* pci_unmap_{page,single} is a nop so... */ +#define DECLARE_PCI_UNMAP_ADDR(ADDR_NAME) +#define DECLARE_PCI_UNMAP_LEN(LEN_NAME) +#define pci_unmap_addr(PTR, ADDR_NAME) (0) +#define pci_unmap_addr_set(PTR, ADDR_NAME, VAL) do { } while (0) +#define pci_unmap_len(PTR, LEN_NAME) (0) +#define pci_unmap_len_set(PTR, LEN_NAME, VAL) do { } while (0) + +/* The PCI address space does equal the physical memory + * address space (no IOMMU). The IDE and SCSI device layers use + * this boolean for bounce buffer decisions. + */ +#define PCI_DMA_BUS_IS_PHYS (1) + +extern void pcibios_resource_to_bus(struct pci_dev *dev, + struct pci_bus_region *region, + struct resource *res); + +extern void pcibios_bus_to_resource(struct pci_dev *dev, + struct resource *res, + struct pci_bus_region *region); + +static inline struct resource *pcibios_select_root(struct pci_dev *pdev, + struct resource *res) +{ + struct resource *root = NULL; + + if (res->flags & IORESOURCE_IO) + root = &ioport_resource; + if (res->flags & IORESOURCE_MEM) + root = &iomem_resource; + + return root; +} + +extern void pcibios_claim_one_bus(struct pci_bus *b); + +extern void pcibios_finish_adding_to_bus(struct pci_bus *bus); + +extern void pcibios_resource_survey(void); + +extern struct pci_controller *init_phb_dynamic(struct device_node *dn); +extern int remove_phb_dynamic(struct pci_controller *phb); + +extern struct pci_dev *of_create_pci_dev(struct device_node *node, + struct pci_bus *bus, int devfn); + +extern void of_scan_pci_bridge(struct device_node *node, + struct pci_dev *dev); + +extern void of_scan_bus(struct device_node *node, struct pci_bus *bus); +extern void of_rescan_bus(struct device_node *node, struct pci_bus *bus); + +extern int pci_read_irq_line(struct pci_dev *dev); + +extern int pci_bus_find_capability(struct pci_bus *bus, + unsigned int devfn, int cap); + +struct file; +extern pgprot_t pci_phys_mem_access_prot(struct file *file, + unsigned long pfn, + unsigned long size, + pgprot_t prot); + +#define HAVE_ARCH_PCI_RESOURCE_TO_USER +extern void pci_resource_to_user(const struct pci_dev *dev, int bar, + const struct resource *rsrc, + resource_size_t *start, resource_size_t *end); + +extern void pcibios_setup_bus_devices(struct pci_bus *bus); +extern void pcibios_setup_bus_self(struct pci_bus *bus); + +#endif /* __KERNEL__ */ +#endif /* __ASM_MICROBLAZE_PCI_H */ -- cgit v1.2.3 From 2ed975b43c1bf09652795a355c4e820e21c4ec44 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 18 Jan 2010 14:25:08 +0100 Subject: microblaze: Add pci-bridge.h Add pci-bridge.h for Microblaze. It is based on powerpc header file. My changes: I removed PPC_ prefix from constants Removed ppc64 specifis parts Signed-off-by: Michal Simek --- arch/microblaze/include/asm/pci-bridge.h | 195 +++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) (limited to 'arch') diff --git a/arch/microblaze/include/asm/pci-bridge.h b/arch/microblaze/include/asm/pci-bridge.h index 7ad28f6f5f1..0c77cda9f5d 100644 --- a/arch/microblaze/include/asm/pci-bridge.h +++ b/arch/microblaze/include/asm/pci-bridge.h @@ -1 +1,196 @@ +#ifndef _ASM_MICROBLAZE_PCI_BRIDGE_H +#define _ASM_MICROBLAZE_PCI_BRIDGE_H +#ifdef __KERNEL__ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ #include +#include +#include + +struct device_node; + +enum { + /* Force re-assigning all resources (ignore firmware + * setup completely) + */ + PCI_REASSIGN_ALL_RSRC = 0x00000001, + + /* Re-assign all bus numbers */ + PCI_REASSIGN_ALL_BUS = 0x00000002, + + /* Do not try to assign, just use existing setup */ + PCI_PROBE_ONLY = 0x00000004, + + /* Don't bother with ISA alignment unless the bridge has + * ISA forwarding enabled + */ + PCI_CAN_SKIP_ISA_ALIGN = 0x00000008, + + /* Enable domain numbers in /proc */ + PCI_ENABLE_PROC_DOMAINS = 0x00000010, + /* ... except for domain 0 */ + PCI_COMPAT_DOMAIN_0 = 0x00000020, +}; + +/* + * Structure of a PCI controller (host bridge) + */ +struct pci_controller { + struct pci_bus *bus; + char is_dynamic; + struct device_node *dn; + struct list_head list_node; + struct device *parent; + + int first_busno; + int last_busno; + + int self_busno; + + void __iomem *io_base_virt; + resource_size_t io_base_phys; + + resource_size_t pci_io_size; + + /* Some machines (PReP) have a non 1:1 mapping of + * the PCI memory space in the CPU bus space + */ + resource_size_t pci_mem_offset; + + /* Some machines have a special region to forward the ISA + * "memory" cycles such as VGA memory regions. Left to 0 + * if unsupported + */ + resource_size_t isa_mem_phys; + resource_size_t isa_mem_size; + + struct pci_ops *ops; + unsigned int __iomem *cfg_addr; + void __iomem *cfg_data; + + /* + * Used for variants of PCI indirect handling and possible quirks: + * SET_CFG_TYPE - used on 4xx or any PHB that does explicit type0/1 + * EXT_REG - provides access to PCI-e extended registers + * SURPRESS_PRIMARY_BUS - we surpress the setting of PCI_PRIMARY_BUS + * on Freescale PCI-e controllers since they used the PCI_PRIMARY_BUS + * to determine which bus number to match on when generating type0 + * config cycles + * NO_PCIE_LINK - the Freescale PCI-e controllers have issues with + * hanging if we don't have link and try to do config cycles to + * anything but the PHB. Only allow talking to the PHB if this is + * set. + * BIG_ENDIAN - cfg_addr is a big endian register + * BROKEN_MRM - the 440EPx/GRx chips have an errata that causes hangs + * on the PLB4. Effectively disable MRM commands by setting this. + */ +#define INDIRECT_TYPE_SET_CFG_TYPE 0x00000001 +#define INDIRECT_TYPE_EXT_REG 0x00000002 +#define INDIRECT_TYPE_SURPRESS_PRIMARY_BUS 0x00000004 +#define INDIRECT_TYPE_NO_PCIE_LINK 0x00000008 +#define INDIRECT_TYPE_BIG_ENDIAN 0x00000010 +#define INDIRECT_TYPE_BROKEN_MRM 0x00000020 + u32 indirect_type; + + /* Currently, we limit ourselves to 1 IO range and 3 mem + * ranges since the common pci_bus structure can't handle more + */ + struct resource io_resource; + struct resource mem_resources[3]; + int global_number; /* PCI domain number */ +}; + +static inline struct pci_controller *pci_bus_to_host(const struct pci_bus *bus) +{ + return bus->sysdata; +} + +static inline int isa_vaddr_is_ioport(void __iomem *address) +{ + /* No specific ISA handling on ppc32 at this stage, it + * all goes through PCI + */ + return 0; +} + +/* These are used for config access before all the PCI probing + has been done. */ +extern int early_read_config_byte(struct pci_controller *hose, int bus, + int dev_fn, int where, u8 *val); +extern int early_read_config_word(struct pci_controller *hose, int bus, + int dev_fn, int where, u16 *val); +extern int early_read_config_dword(struct pci_controller *hose, int bus, + int dev_fn, int where, u32 *val); +extern int early_write_config_byte(struct pci_controller *hose, int bus, + int dev_fn, int where, u8 val); +extern int early_write_config_word(struct pci_controller *hose, int bus, + int dev_fn, int where, u16 val); +extern int early_write_config_dword(struct pci_controller *hose, int bus, + int dev_fn, int where, u32 val); + +extern int early_find_capability(struct pci_controller *hose, int bus, + int dev_fn, int cap); + +extern void setup_indirect_pci(struct pci_controller *hose, + resource_size_t cfg_addr, + resource_size_t cfg_data, u32 flags); + +/* Get the PCI host controller for an OF device */ +extern struct pci_controller *pci_find_hose_for_OF_device( + struct device_node *node); + +/* Fill up host controller resources from the OF node */ +extern void pci_process_bridge_OF_ranges(struct pci_controller *hose, + struct device_node *dev, int primary); + +/* Allocate & free a PCI host bridge structure */ +extern struct pci_controller *pcibios_alloc_controller(struct device_node *dev); +extern void pcibios_free_controller(struct pci_controller *phb); +extern void pcibios_setup_phb_resources(struct pci_controller *hose); + +#ifdef CONFIG_PCI +extern unsigned int pci_flags; + +static inline void pci_set_flags(int flags) +{ + pci_flags = flags; +} + +static inline void pci_add_flags(int flags) +{ + pci_flags |= flags; +} + +static inline int pci_has_flag(int flag) +{ + return pci_flags & flag; +} + +extern struct list_head hose_list; + +extern unsigned long pci_address_to_pio(phys_addr_t address); +extern int pcibios_vaddr_is_ioport(void __iomem *address); +#else +static inline unsigned long pci_address_to_pio(phys_addr_t address) +{ + return (unsigned long)-1; +} +static inline int pcibios_vaddr_is_ioport(void __iomem *address) +{ + return 0; +} + +static inline void pci_set_flags(int flags) { } +static inline void pci_add_flags(int flags) { } +static inline int pci_has_flag(int flag) +{ + return 0; +} +#endif /* CONFIG_PCI */ + +#endif /* __KERNEL__ */ +#endif /* _ASM_MICROBLAZE_PCI_BRIDGE_H */ -- cgit v1.2.3 From d3afa58c20b65155af9f0d5eaa59fe2d367ac432 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 18 Jan 2010 14:42:34 +0100 Subject: microblaze: Add core PCI files Add pci-common.h and pci32.c. Files are based on PPC version. There are removed ppc specific parts and the code was completely clean. Signed-off-by: Michal Simek --- arch/microblaze/pci/pci-common.c | 1640 ++++++++++++++++++++++++++++++++++++++ arch/microblaze/pci/pci_32.c | 430 ++++++++++ 2 files changed, 2070 insertions(+) create mode 100644 arch/microblaze/pci/pci-common.c create mode 100644 arch/microblaze/pci/pci_32.c (limited to 'arch') diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c new file mode 100644 index 00000000000..f03f8be2740 --- /dev/null +++ b/arch/microblaze/pci/pci-common.c @@ -0,0 +1,1640 @@ +/* + * Contains common pci routines for ALL ppc platform + * (based on pci_32.c and pci_64.c) + * + * Port for PPC64 David Engebretsen, IBM Corp. + * Contains common pci routines for ppc64 platform, pSeries and iSeries brands. + * + * Copyright (C) 2003 Anton Blanchard , IBM + * Rework, based on alpha PCI code. + * + * Common pmac/prep/chrp pci routines. -- Cort + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(hose_spinlock); +LIST_HEAD(hose_list); + +/* XXX kill that some day ... */ +static int global_phb_number; /* Global phb counter */ + +/* ISA Memory physical address */ +resource_size_t isa_mem_base; + +/* Default PCI flags is 0 on ppc32, modified at boot on ppc64 */ +unsigned int pci_flags; + +static struct dma_map_ops *pci_dma_ops = &dma_direct_ops; + +void set_pci_dma_ops(struct dma_map_ops *dma_ops) +{ + pci_dma_ops = dma_ops; +} + +struct dma_map_ops *get_pci_dma_ops(void) +{ + return pci_dma_ops; +} +EXPORT_SYMBOL(get_pci_dma_ops); + +int pci_set_dma_mask(struct pci_dev *dev, u64 mask) +{ + return dma_set_mask(&dev->dev, mask); +} + +int pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask) +{ + int rc; + + rc = dma_set_mask(&dev->dev, mask); + dev->dev.coherent_dma_mask = dev->dma_mask; + + return rc; +} + +struct pci_controller *pcibios_alloc_controller(struct device_node *dev) +{ + struct pci_controller *phb; + + phb = zalloc_maybe_bootmem(sizeof(struct pci_controller), GFP_KERNEL); + if (!phb) + return NULL; + spin_lock(&hose_spinlock); + phb->global_number = global_phb_number++; + list_add_tail(&phb->list_node, &hose_list); + spin_unlock(&hose_spinlock); + phb->dn = dev; + phb->is_dynamic = mem_init_done; + return phb; +} + +void pcibios_free_controller(struct pci_controller *phb) +{ + spin_lock(&hose_spinlock); + list_del(&phb->list_node); + spin_unlock(&hose_spinlock); + + if (phb->is_dynamic) + kfree(phb); +} + +static resource_size_t pcibios_io_size(const struct pci_controller *hose) +{ + return hose->io_resource.end - hose->io_resource.start + 1; +} + +int pcibios_vaddr_is_ioport(void __iomem *address) +{ + int ret = 0; + struct pci_controller *hose; + resource_size_t size; + + spin_lock(&hose_spinlock); + list_for_each_entry(hose, &hose_list, list_node) { + size = pcibios_io_size(hose); + if (address >= hose->io_base_virt && + address < (hose->io_base_virt + size)) { + ret = 1; + break; + } + } + spin_unlock(&hose_spinlock); + return ret; +} + +unsigned long pci_address_to_pio(phys_addr_t address) +{ + struct pci_controller *hose; + resource_size_t size; + unsigned long ret = ~0; + + spin_lock(&hose_spinlock); + list_for_each_entry(hose, &hose_list, list_node) { + size = pcibios_io_size(hose); + if (address >= hose->io_base_phys && + address < (hose->io_base_phys + size)) { + unsigned long base = + (unsigned long)hose->io_base_virt - _IO_BASE; + ret = base + (address - hose->io_base_phys); + break; + } + } + spin_unlock(&hose_spinlock); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_address_to_pio); + +/* + * Return the domain number for this bus. + */ +int pci_domain_nr(struct pci_bus *bus) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + + return hose->global_number; +} +EXPORT_SYMBOL(pci_domain_nr); + +/* This routine is meant to be used early during boot, when the + * PCI bus numbers have not yet been assigned, and you need to + * issue PCI config cycles to an OF device. + * It could also be used to "fix" RTAS config cycles if you want + * to set pci_assign_all_buses to 1 and still use RTAS for PCI + * config cycles. + */ +struct pci_controller *pci_find_hose_for_OF_device(struct device_node *node) +{ + while (node) { + struct pci_controller *hose, *tmp; + list_for_each_entry_safe(hose, tmp, &hose_list, list_node) + if (hose->dn == node) + return hose; + node = node->parent; + } + return NULL; +} + +static ssize_t pci_show_devspec(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev; + struct device_node *np; + + pdev = to_pci_dev(dev); + np = pci_device_to_OF_node(pdev); + if (np == NULL || np->full_name == NULL) + return 0; + return sprintf(buf, "%s", np->full_name); +} +static DEVICE_ATTR(devspec, S_IRUGO, pci_show_devspec, NULL); + +/* Add sysfs properties */ +int pcibios_add_platform_entries(struct pci_dev *pdev) +{ + return device_create_file(&pdev->dev, &dev_attr_devspec); +} + +char __devinit *pcibios_setup(char *str) +{ + return str; +} + +/* + * Reads the interrupt pin to determine if interrupt is use by card. + * If the interrupt is used, then gets the interrupt line from the + * openfirmware and sets it in the pci_dev and pci_config line. + */ +int pci_read_irq_line(struct pci_dev *pci_dev) +{ + struct of_irq oirq; + unsigned int virq; + + /* The current device-tree that iSeries generates from the HV + * PCI informations doesn't contain proper interrupt routing, + * and all the fallback would do is print out crap, so we + * don't attempt to resolve the interrupts here at all, some + * iSeries specific fixup does it. + * + * In the long run, we will hopefully fix the generated device-tree + * instead. + */ + pr_debug("PCI: Try to map irq for %s...\n", pci_name(pci_dev)); + +#ifdef DEBUG + memset(&oirq, 0xff, sizeof(oirq)); +#endif + /* Try to get a mapping from the device-tree */ + if (of_irq_map_pci(pci_dev, &oirq)) { + u8 line, pin; + + /* If that fails, lets fallback to what is in the config + * space and map that through the default controller. We + * also set the type to level low since that's what PCI + * interrupts are. If your platform does differently, then + * either provide a proper interrupt tree or don't use this + * function. + */ + if (pci_read_config_byte(pci_dev, PCI_INTERRUPT_PIN, &pin)) + return -1; + if (pin == 0) + return -1; + if (pci_read_config_byte(pci_dev, PCI_INTERRUPT_LINE, &line) || + line == 0xff || line == 0) { + return -1; + } + pr_debug(" No map ! Using line %d (pin %d) from PCI config\n", + line, pin); + + virq = irq_create_mapping(NULL, line); + if (virq != NO_IRQ) + set_irq_type(virq, IRQ_TYPE_LEVEL_LOW); + } else { + pr_debug(" Got one, spec %d cells (0x%08x 0x%08x...) on %s\n", + oirq.size, oirq.specifier[0], oirq.specifier[1], + oirq.controller ? oirq.controller->full_name : + ""); + + virq = irq_create_of_mapping(oirq.controller, oirq.specifier, + oirq.size); + } + if (virq == NO_IRQ) { + pr_debug(" Failed to map !\n"); + return -1; + } + + pr_debug(" Mapped to linux irq %d\n", virq); + + pci_dev->irq = virq; + + return 0; +} +EXPORT_SYMBOL(pci_read_irq_line); + +/* + * Platform support for /proc/bus/pci/X/Y mmap()s, + * modelled on the sparc64 implementation by Dave Miller. + * -- paulus. + */ + +/* + * Adjust vm_pgoff of VMA such that it is the physical page offset + * corresponding to the 32-bit pci bus offset for DEV requested by the user. + * + * Basically, the user finds the base address for his device which he wishes + * to mmap. They read the 32-bit value from the config space base register, + * add whatever PAGE_SIZE multiple offset they wish, and feed this into the + * offset parameter of mmap on /proc/bus/pci/XXX for that device. + * + * Returns negative error code on failure, zero on success. + */ +static struct resource *__pci_mmap_make_offset(struct pci_dev *dev, + resource_size_t *offset, + enum pci_mmap_state mmap_state) +{ + struct pci_controller *hose = pci_bus_to_host(dev->bus); + unsigned long io_offset = 0; + int i, res_bit; + + if (hose == 0) + return NULL; /* should never happen */ + + /* If memory, add on the PCI bridge address offset */ + if (mmap_state == pci_mmap_mem) { +#if 0 /* See comment in pci_resource_to_user() for why this is disabled */ + *offset += hose->pci_mem_offset; +#endif + res_bit = IORESOURCE_MEM; + } else { + io_offset = (unsigned long)hose->io_base_virt - _IO_BASE; + *offset += io_offset; + res_bit = IORESOURCE_IO; + } + + /* + * Check that the offset requested corresponds to one of the + * resources of the device. + */ + for (i = 0; i <= PCI_ROM_RESOURCE; i++) { + struct resource *rp = &dev->resource[i]; + int flags = rp->flags; + + /* treat ROM as memory (should be already) */ + if (i == PCI_ROM_RESOURCE) + flags |= IORESOURCE_MEM; + + /* Active and same type? */ + if ((flags & res_bit) == 0) + continue; + + /* In the range of this resource? */ + if (*offset < (rp->start & PAGE_MASK) || *offset > rp->end) + continue; + + /* found it! construct the final physical address */ + if (mmap_state == pci_mmap_io) + *offset += hose->io_base_phys - io_offset; + return rp; + } + + return NULL; +} + +/* + * Set vm_page_prot of VMA, as appropriate for this architecture, for a pci + * device mapping. + */ +static pgprot_t __pci_mmap_set_pgprot(struct pci_dev *dev, struct resource *rp, + pgprot_t protection, + enum pci_mmap_state mmap_state, + int write_combine) +{ + pgprot_t prot = protection; + + /* Write combine is always 0 on non-memory space mappings. On + * memory space, if the user didn't pass 1, we check for a + * "prefetchable" resource. This is a bit hackish, but we use + * this to workaround the inability of /sysfs to provide a write + * combine bit + */ + if (mmap_state != pci_mmap_mem) + write_combine = 0; + else if (write_combine == 0) { + if (rp->flags & IORESOURCE_PREFETCH) + write_combine = 1; + } + + return pgprot_noncached(prot); +} + +/* + * This one is used by /dev/mem and fbdev who have no clue about the + * PCI device, it tries to find the PCI device first and calls the + * above routine + */ +pgprot_t pci_phys_mem_access_prot(struct file *file, + unsigned long pfn, + unsigned long size, + pgprot_t prot) +{ + struct pci_dev *pdev = NULL; + struct resource *found = NULL; + resource_size_t offset = ((resource_size_t)pfn) << PAGE_SHIFT; + int i; + + if (page_is_ram(pfn)) + return prot; + + prot = pgprot_noncached(prot); + for_each_pci_dev(pdev) { + for (i = 0; i <= PCI_ROM_RESOURCE; i++) { + struct resource *rp = &pdev->resource[i]; + int flags = rp->flags; + + /* Active and same type? */ + if ((flags & IORESOURCE_MEM) == 0) + continue; + /* In the range of this resource? */ + if (offset < (rp->start & PAGE_MASK) || + offset > rp->end) + continue; + found = rp; + break; + } + if (found) + break; + } + if (found) { + if (found->flags & IORESOURCE_PREFETCH) + prot = pgprot_noncached_wc(prot); + pci_dev_put(pdev); + } + + pr_debug("PCI: Non-PCI map for %llx, prot: %lx\n", + (unsigned long long)offset, pgprot_val(prot)); + + return prot; +} + +/* + * Perform the actual remap of the pages for a PCI device mapping, as + * appropriate for this architecture. The region in the process to map + * is described by vm_start and vm_end members of VMA, the base physical + * address is found in vm_pgoff. + * The pci device structure is provided so that architectures may make mapping + * decisions on a per-device or per-bus basis. + * + * Returns a negative error code on failure, zero on success. + */ +int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma, + enum pci_mmap_state mmap_state, int write_combine) +{ + resource_size_t offset = + ((resource_size_t)vma->vm_pgoff) << PAGE_SHIFT; + struct resource *rp; + int ret; + + rp = __pci_mmap_make_offset(dev, &offset, mmap_state); + if (rp == NULL) + return -EINVAL; + + vma->vm_pgoff = offset >> PAGE_SHIFT; + vma->vm_page_prot = __pci_mmap_set_pgprot(dev, rp, + vma->vm_page_prot, + mmap_state, write_combine); + + ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot); + + return ret; +} + +/* This provides legacy IO read access on a bus */ +int pci_legacy_read(struct pci_bus *bus, loff_t port, u32 *val, size_t size) +{ + unsigned long offset; + struct pci_controller *hose = pci_bus_to_host(bus); + struct resource *rp = &hose->io_resource; + void __iomem *addr; + + /* Check if port can be supported by that bus. We only check + * the ranges of the PHB though, not the bus itself as the rules + * for forwarding legacy cycles down bridges are not our problem + * here. So if the host bridge supports it, we do it. + */ + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + offset += port; + + if (!(rp->flags & IORESOURCE_IO)) + return -ENXIO; + if (offset < rp->start || (offset + size) > rp->end) + return -ENXIO; + addr = hose->io_base_virt + port; + + switch (size) { + case 1: + *((u8 *)val) = in_8(addr); + return 1; + case 2: + if (port & 1) + return -EINVAL; + *((u16 *)val) = in_le16(addr); + return 2; + case 4: + if (port & 3) + return -EINVAL; + *((u32 *)val) = in_le32(addr); + return 4; + } + return -EINVAL; +} + +/* This provides legacy IO write access on a bus */ +int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, size_t size) +{ + unsigned long offset; + struct pci_controller *hose = pci_bus_to_host(bus); + struct resource *rp = &hose->io_resource; + void __iomem *addr; + + /* Check if port can be supported by that bus. We only check + * the ranges of the PHB though, not the bus itself as the rules + * for forwarding legacy cycles down bridges are not our problem + * here. So if the host bridge supports it, we do it. + */ + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + offset += port; + + if (!(rp->flags & IORESOURCE_IO)) + return -ENXIO; + if (offset < rp->start || (offset + size) > rp->end) + return -ENXIO; + addr = hose->io_base_virt + port; + + /* WARNING: The generic code is idiotic. It gets passed a pointer + * to what can be a 1, 2 or 4 byte quantity and always reads that + * as a u32, which means that we have to correct the location of + * the data read within those 32 bits for size 1 and 2 + */ + switch (size) { + case 1: + out_8(addr, val >> 24); + return 1; + case 2: + if (port & 1) + return -EINVAL; + out_le16(addr, val >> 16); + return 2; + case 4: + if (port & 3) + return -EINVAL; + out_le32(addr, val); + return 4; + } + return -EINVAL; +} + +/* This provides legacy IO or memory mmap access on a bus */ +int pci_mmap_legacy_page_range(struct pci_bus *bus, + struct vm_area_struct *vma, + enum pci_mmap_state mmap_state) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + resource_size_t offset = + ((resource_size_t)vma->vm_pgoff) << PAGE_SHIFT; + resource_size_t size = vma->vm_end - vma->vm_start; + struct resource *rp; + + pr_debug("pci_mmap_legacy_page_range(%04x:%02x, %s @%llx..%llx)\n", + pci_domain_nr(bus), bus->number, + mmap_state == pci_mmap_mem ? "MEM" : "IO", + (unsigned long long)offset, + (unsigned long long)(offset + size - 1)); + + if (mmap_state == pci_mmap_mem) { + /* Hack alert ! + * + * Because X is lame and can fail starting if it gets an error + * trying to mmap legacy_mem (instead of just moving on without + * legacy memory access) we fake it here by giving it anonymous + * memory, effectively behaving just like /dev/zero + */ + if ((offset + size) > hose->isa_mem_size) { + printk(KERN_DEBUG + "Process %s (pid:%d) mapped non-existing PCI" + "legacy memory for 0%04x:%02x\n", + current->comm, current->pid, pci_domain_nr(bus), + bus->number); + if (vma->vm_flags & VM_SHARED) + return shmem_zero_setup(vma); + return 0; + } + offset += hose->isa_mem_phys; + } else { + unsigned long io_offset = (unsigned long)hose->io_base_virt - \ + _IO_BASE; + unsigned long roffset = offset + io_offset; + rp = &hose->io_resource; + if (!(rp->flags & IORESOURCE_IO)) + return -ENXIO; + if (roffset < rp->start || (roffset + size) > rp->end) + return -ENXIO; + offset += hose->io_base_phys; + } + pr_debug(" -> mapping phys %llx\n", (unsigned long long)offset); + + vma->vm_pgoff = offset >> PAGE_SHIFT; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +void pci_resource_to_user(const struct pci_dev *dev, int bar, + const struct resource *rsrc, + resource_size_t *start, resource_size_t *end) +{ + struct pci_controller *hose = pci_bus_to_host(dev->bus); + resource_size_t offset = 0; + + if (hose == NULL) + return; + + if (rsrc->flags & IORESOURCE_IO) + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + + /* We pass a fully fixed up address to userland for MMIO instead of + * a BAR value because X is lame and expects to be able to use that + * to pass to /dev/mem ! + * + * That means that we'll have potentially 64 bits values where some + * userland apps only expect 32 (like X itself since it thinks only + * Sparc has 64 bits MMIO) but if we don't do that, we break it on + * 32 bits CHRPs :-( + * + * Hopefully, the sysfs insterface is immune to that gunk. Once X + * has been fixed (and the fix spread enough), we can re-enable the + * 2 lines below and pass down a BAR value to userland. In that case + * we'll also have to re-enable the matching code in + * __pci_mmap_make_offset(). + * + * BenH. + */ +#if 0 + else if (rsrc->flags & IORESOURCE_MEM) + offset = hose->pci_mem_offset; +#endif + + *start = rsrc->start - offset; + *end = rsrc->end - offset; +} + +/** + * pci_process_bridge_OF_ranges - Parse PCI bridge resources from device tree + * @hose: newly allocated pci_controller to be setup + * @dev: device node of the host bridge + * @primary: set if primary bus (32 bits only, soon to be deprecated) + * + * This function will parse the "ranges" property of a PCI host bridge device + * node and setup the resource mapping of a pci controller based on its + * content. + * + * Life would be boring if it wasn't for a few issues that we have to deal + * with here: + * + * - We can only cope with one IO space range and up to 3 Memory space + * ranges. However, some machines (thanks Apple !) tend to split their + * space into lots of small contiguous ranges. So we have to coalesce. + * + * - We can only cope with all memory ranges having the same offset + * between CPU addresses and PCI addresses. Unfortunately, some bridges + * are setup for a large 1:1 mapping along with a small "window" which + * maps PCI address 0 to some arbitrary high address of the CPU space in + * order to give access to the ISA memory hole. + * The way out of here that I've chosen for now is to always set the + * offset based on the first resource found, then override it if we + * have a different offset and the previous was set by an ISA hole. + * + * - Some busses have IO space not starting at 0, which causes trouble with + * the way we do our IO resource renumbering. The code somewhat deals with + * it for 64 bits but I would expect problems on 32 bits. + * + * - Some 32 bits platforms such as 4xx can have physical space larger than + * 32 bits so we need to use 64 bits values for the parsing + */ +void __devinit pci_process_bridge_OF_ranges(struct pci_controller *hose, + struct device_node *dev, + int primary) +{ + const u32 *ranges; + int rlen; + int pna = of_n_addr_cells(dev); + int np = pna + 5; + int memno = 0, isa_hole = -1; + u32 pci_space; + unsigned long long pci_addr, cpu_addr, pci_next, cpu_next, size; + unsigned long long isa_mb = 0; + struct resource *res; + + printk(KERN_INFO "PCI host bridge %s %s ranges:\n", + dev->full_name, primary ? "(primary)" : ""); + + /* Get ranges property */ + ranges = of_get_property(dev, "ranges", &rlen); + if (ranges == NULL) + return; + + /* Parse it */ + pr_debug("Parsing ranges property...\n"); + while ((rlen -= np * 4) >= 0) { + /* Read next ranges element */ + pci_space = ranges[0]; + pci_addr = of_read_number(ranges + 1, 2); + cpu_addr = of_translate_address(dev, ranges + 3); + size = of_read_number(ranges + pna + 3, 2); + + pr_debug("pci_space: 0x%08x pci_addr:0x%016llx " + "cpu_addr:0x%016llx size:0x%016llx\n", + pci_space, pci_addr, cpu_addr, size); + + ranges += np; + + /* If we failed translation or got a zero-sized region + * (some FW try to feed us with non sensical zero sized regions + * such as power3 which look like some kind of attempt + * at exposing the VGA memory hole) + */ + if (cpu_addr == OF_BAD_ADDR || size == 0) + continue; + + /* Now consume following elements while they are contiguous */ + for (; rlen >= np * sizeof(u32); + ranges += np, rlen -= np * 4) { + if (ranges[0] != pci_space) + break; + pci_next = of_read_number(ranges + 1, 2); + cpu_next = of_translate_address(dev, ranges + 3); + if (pci_next != pci_addr + size || + cpu_next != cpu_addr + size) + break; + size += of_read_number(ranges + pna + 3, 2); + } + + /* Act based on address space type */ + res = NULL; + switch ((pci_space >> 24) & 0x3) { + case 1: /* PCI IO space */ + printk(KERN_INFO + " IO 0x%016llx..0x%016llx -> 0x%016llx\n", + cpu_addr, cpu_addr + size - 1, pci_addr); + + /* We support only one IO range */ + if (hose->pci_io_size) { + printk(KERN_INFO + " \\--> Skipped (too many) !\n"); + continue; + } + /* On 32 bits, limit I/O space to 16MB */ + if (size > 0x01000000) + size = 0x01000000; + + /* 32 bits needs to map IOs here */ + hose->io_base_virt = ioremap(cpu_addr, size); + + /* Expect trouble if pci_addr is not 0 */ + if (primary) + isa_io_base = + (unsigned long)hose->io_base_virt; + /* pci_io_size and io_base_phys always represent IO + * space starting at 0 so we factor in pci_addr + */ + hose->pci_io_size = pci_addr + size; + hose->io_base_phys = cpu_addr - pci_addr; + + /* Build resource */ + res = &hose->io_resource; + res->flags = IORESOURCE_IO; + res->start = pci_addr; + break; + case 2: /* PCI Memory space */ + case 3: /* PCI 64 bits Memory space */ + printk(KERN_INFO + " MEM 0x%016llx..0x%016llx -> 0x%016llx %s\n", + cpu_addr, cpu_addr + size - 1, pci_addr, + (pci_space & 0x40000000) ? "Prefetch" : ""); + + /* We support only 3 memory ranges */ + if (memno >= 3) { + printk(KERN_INFO + " \\--> Skipped (too many) !\n"); + continue; + } + /* Handles ISA memory hole space here */ + if (pci_addr == 0) { + isa_mb = cpu_addr; + isa_hole = memno; + if (primary || isa_mem_base == 0) + isa_mem_base = cpu_addr; + hose->isa_mem_phys = cpu_addr; + hose->isa_mem_size = size; + } + + /* We get the PCI/Mem offset from the first range or + * the, current one if the offset came from an ISA + * hole. If they don't match, bugger. + */ + if (memno == 0 || + (isa_hole >= 0 && pci_addr != 0 && + hose->pci_mem_offset == isa_mb)) + hose->pci_mem_offset = cpu_addr - pci_addr; + else if (pci_addr != 0 && + hose->pci_mem_offset != cpu_addr - pci_addr) { + printk(KERN_INFO + " \\--> Skipped (offset mismatch) !\n"); + continue; + } + + /* Build resource */ + res = &hose->mem_resources[memno++]; + res->flags = IORESOURCE_MEM; + if (pci_space & 0x40000000) + res->flags |= IORESOURCE_PREFETCH; + res->start = cpu_addr; + break; + } + if (res != NULL) { + res->name = dev->full_name; + res->end = res->start + size - 1; + res->parent = NULL; + res->sibling = NULL; + res->child = NULL; + } + } + + /* If there's an ISA hole and the pci_mem_offset is -not- matching + * the ISA hole offset, then we need to remove the ISA hole from + * the resource list for that brige + */ + if (isa_hole >= 0 && hose->pci_mem_offset != isa_mb) { + unsigned int next = isa_hole + 1; + printk(KERN_INFO " Removing ISA hole at 0x%016llx\n", isa_mb); + if (next < memno) + memmove(&hose->mem_resources[isa_hole], + &hose->mem_resources[next], + sizeof(struct resource) * (memno - next)); + hose->mem_resources[--memno].flags = 0; + } +} + +/* Decide whether to display the domain number in /proc */ +int pci_proc_domain(struct pci_bus *bus) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + + if (!(pci_flags & PCI_ENABLE_PROC_DOMAINS)) + return 0; + if (pci_flags & PCI_COMPAT_DOMAIN_0) + return hose->global_number != 0; + return 1; +} + +void pcibios_resource_to_bus(struct pci_dev *dev, struct pci_bus_region *region, + struct resource *res) +{ + resource_size_t offset = 0, mask = (resource_size_t)-1; + struct pci_controller *hose = pci_bus_to_host(dev->bus); + + if (!hose) + return; + if (res->flags & IORESOURCE_IO) { + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + mask = 0xffffffffu; + } else if (res->flags & IORESOURCE_MEM) + offset = hose->pci_mem_offset; + + region->start = (res->start - offset) & mask; + region->end = (res->end - offset) & mask; +} +EXPORT_SYMBOL(pcibios_resource_to_bus); + +void pcibios_bus_to_resource(struct pci_dev *dev, struct resource *res, + struct pci_bus_region *region) +{ + resource_size_t offset = 0, mask = (resource_size_t)-1; + struct pci_controller *hose = pci_bus_to_host(dev->bus); + + if (!hose) + return; + if (res->flags & IORESOURCE_IO) { + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + mask = 0xffffffffu; + } else if (res->flags & IORESOURCE_MEM) + offset = hose->pci_mem_offset; + res->start = (region->start + offset) & mask; + res->end = (region->end + offset) & mask; +} +EXPORT_SYMBOL(pcibios_bus_to_resource); + +/* Fixup a bus resource into a linux resource */ +static void __devinit fixup_resource(struct resource *res, struct pci_dev *dev) +{ + struct pci_controller *hose = pci_bus_to_host(dev->bus); + resource_size_t offset = 0, mask = (resource_size_t)-1; + + if (res->flags & IORESOURCE_IO) { + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + mask = 0xffffffffu; + } else if (res->flags & IORESOURCE_MEM) + offset = hose->pci_mem_offset; + + res->start = (res->start + offset) & mask; + res->end = (res->end + offset) & mask; +} + +/* This header fixup will do the resource fixup for all devices as they are + * probed, but not for bridge ranges + */ +static void __devinit pcibios_fixup_resources(struct pci_dev *dev) +{ + struct pci_controller *hose = pci_bus_to_host(dev->bus); + int i; + + if (!hose) { + printk(KERN_ERR "No host bridge for PCI dev %s !\n", + pci_name(dev)); + return; + } + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + struct resource *res = dev->resource + i; + if (!res->flags) + continue; + /* On platforms that have PCI_PROBE_ONLY set, we don't + * consider 0 as an unassigned BAR value. It's technically + * a valid value, but linux doesn't like it... so when we can + * re-assign things, we do so, but if we can't, we keep it + * around and hope for the best... + */ + if (res->start == 0 && !(pci_flags & PCI_PROBE_ONLY)) { + pr_debug("PCI:%s Resource %d %016llx-%016llx [%x]" \ + "is unassigned\n", + pci_name(dev), i, + (unsigned long long)res->start, + (unsigned long long)res->end, + (unsigned int)res->flags); + res->end -= res->start; + res->start = 0; + res->flags |= IORESOURCE_UNSET; + continue; + } + + pr_debug("PCI:%s Resource %d %016llx-%016llx [%x] fixup...\n", + pci_name(dev), i, + (unsigned long long)res->start,\ + (unsigned long long)res->end, + (unsigned int)res->flags); + + fixup_resource(res, dev); + + pr_debug("PCI:%s %016llx-%016llx\n", + pci_name(dev), + (unsigned long long)res->start, + (unsigned long long)res->end); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, pcibios_fixup_resources); + +/* This function tries to figure out if a bridge resource has been initialized + * by the firmware or not. It doesn't have to be absolutely bullet proof, but + * things go more smoothly when it gets it right. It should covers cases such + * as Apple "closed" bridge resources and bare-metal pSeries unassigned bridges + */ +static int __devinit pcibios_uninitialized_bridge_resource(struct pci_bus *bus, + struct resource *res) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + struct pci_dev *dev = bus->self; + resource_size_t offset; + u16 command; + int i; + + /* We don't do anything if PCI_PROBE_ONLY is set */ + if (pci_flags & PCI_PROBE_ONLY) + return 0; + + /* Job is a bit different between memory and IO */ + if (res->flags & IORESOURCE_MEM) { + /* If the BAR is non-0 (res != pci_mem_offset) then it's + * probably been initialized by somebody + */ + if (res->start != hose->pci_mem_offset) + return 0; + + /* The BAR is 0, let's check if memory decoding is enabled on + * the bridge. If not, we consider it unassigned + */ + pci_read_config_word(dev, PCI_COMMAND, &command); + if ((command & PCI_COMMAND_MEMORY) == 0) + return 1; + + /* Memory decoding is enabled and the BAR is 0. If any of + * the bridge resources covers that starting address (0 then + * it's good enough for us for memory + */ + for (i = 0; i < 3; i++) { + if ((hose->mem_resources[i].flags & IORESOURCE_MEM) && + hose->mem_resources[i].start == hose->pci_mem_offset) + return 0; + } + + /* Well, it starts at 0 and we know it will collide so we may as + * well consider it as unassigned. That covers the Apple case. + */ + return 1; + } else { + /* If the BAR is non-0, then we consider it assigned */ + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + if (((res->start - offset) & 0xfffffffful) != 0) + return 0; + + /* Here, we are a bit different than memory as typically IO + * space starting at low addresses -is- valid. What we do + * instead if that we consider as unassigned anything that + * doesn't have IO enabled in the PCI command register, + * and that's it. + */ + pci_read_config_word(dev, PCI_COMMAND, &command); + if (command & PCI_COMMAND_IO) + return 0; + + /* It's starting at 0 and IO is disabled in the bridge, consider + * it unassigned + */ + return 1; + } +} + +/* Fixup resources of a PCI<->PCI bridge */ +static void __devinit pcibios_fixup_bridge(struct pci_bus *bus) +{ + struct resource *res; + int i; + + struct pci_dev *dev = bus->self; + + for (i = 0; i < PCI_BUS_NUM_RESOURCES; ++i) { + res = bus->resource[i]; + if (!res) + continue; + if (!res->flags) + continue; + if (i >= 3 && bus->self->transparent) + continue; + + pr_debug("PCI:%s Bus rsrc %d %016llx-%016llx [%x] fixup...\n", + pci_name(dev), i, + (unsigned long long)res->start,\ + (unsigned long long)res->end, + (unsigned int)res->flags); + + /* Perform fixup */ + fixup_resource(res, dev); + + /* Try to detect uninitialized P2P bridge resources, + * and clear them out so they get re-assigned later + */ + if (pcibios_uninitialized_bridge_resource(bus, res)) { + res->flags = 0; + pr_debug("PCI:%s (unassigned)\n", + pci_name(dev)); + } else { + pr_debug("PCI:%s %016llx-%016llx\n", + pci_name(dev), + (unsigned long long)res->start, + (unsigned long long)res->end); + } + } +} + +void __devinit pcibios_setup_bus_self(struct pci_bus *bus) +{ + /* Fix up the bus resources for P2P bridges */ + if (bus->self != NULL) + pcibios_fixup_bridge(bus); +} + +void __devinit pcibios_setup_bus_devices(struct pci_bus *bus) +{ + struct pci_dev *dev; + + pr_debug("PCI: Fixup bus devices %d (%s)\n", + bus->number, bus->self ? pci_name(bus->self) : "PHB"); + + list_for_each_entry(dev, &bus->devices, bus_list) { + struct dev_archdata *sd = &dev->dev.archdata; + + /* Setup OF node pointer in archdata */ + sd->of_node = pci_device_to_OF_node(dev); + + /* Fixup NUMA node as it may not be setup yet by the generic + * code and is needed by the DMA init + */ + set_dev_node(&dev->dev, pcibus_to_node(dev->bus)); + + /* Hook up default DMA ops */ + sd->dma_ops = pci_dma_ops; + sd->dma_data = (void *)PCI_DRAM_OFFSET; + + /* Read default IRQs and fixup if necessary */ + pci_read_irq_line(dev); + } +} + +void __devinit pcibios_fixup_bus(struct pci_bus *bus) +{ + /* When called from the generic PCI probe, read PCI<->PCI bridge + * bases. This is -not- called when generating the PCI tree from + * the OF device-tree. + */ + if (bus->self != NULL) + pci_read_bridge_bases(bus); + + /* Now fixup the bus bus */ + pcibios_setup_bus_self(bus); + + /* Now fixup devices on that bus */ + pcibios_setup_bus_devices(bus); +} +EXPORT_SYMBOL(pcibios_fixup_bus); + +static int skip_isa_ioresource_align(struct pci_dev *dev) +{ + if ((pci_flags & PCI_CAN_SKIP_ISA_ALIGN) && + !(dev->bus->bridge_ctl & PCI_BRIDGE_CTL_ISA)) + return 1; + return 0; +} + +/* + * We need to avoid collisions with `mirrored' VGA ports + * and other strange ISA hardware, so we always want the + * addresses to be allocated in the 0x000-0x0ff region + * modulo 0x400. + * + * Why? Because some silly external IO cards only decode + * the low 10 bits of the IO address. The 0x00-0xff region + * is reserved for motherboard devices that decode all 16 + * bits, so it's ok to allocate at, say, 0x2800-0x28ff, + * but we want to try to avoid allocating at 0x2900-0x2bff + * which might have be mirrored at 0x0100-0x03ff.. + */ +void pcibios_align_resource(void *data, struct resource *res, + resource_size_t size, resource_size_t align) +{ + struct pci_dev *dev = data; + + if (res->flags & IORESOURCE_IO) { + resource_size_t start = res->start; + + if (skip_isa_ioresource_align(dev)) + return; + if (start & 0x300) { + start = (start + 0x3ff) & ~0x3ff; + res->start = start; + } + } +} +EXPORT_SYMBOL(pcibios_align_resource); + +/* + * Reparent resource children of pr that conflict with res + * under res, and make res replace those children. + */ +static int __init reparent_resources(struct resource *parent, + struct resource *res) +{ + struct resource *p, **pp; + struct resource **firstpp = NULL; + + for (pp = &parent->child; (p = *pp) != NULL; pp = &p->sibling) { + if (p->end < res->start) + continue; + if (res->end < p->start) + break; + if (p->start < res->start || p->end > res->end) + return -1; /* not completely contained */ + if (firstpp == NULL) + firstpp = pp; + } + if (firstpp == NULL) + return -1; /* didn't find any conflicting entries? */ + res->parent = parent; + res->child = *firstpp; + res->sibling = *pp; + *firstpp = res; + *pp = NULL; + for (p = res->child; p != NULL; p = p->sibling) { + p->parent = res; + pr_debug("PCI: Reparented %s [%llx..%llx] under %s\n", + p->name, + (unsigned long long)p->start, + (unsigned long long)p->end, res->name); + } + return 0; +} + +/* + * Handle resources of PCI devices. If the world were perfect, we could + * just allocate all the resource regions and do nothing more. It isn't. + * On the other hand, we cannot just re-allocate all devices, as it would + * require us to know lots of host bridge internals. So we attempt to + * keep as much of the original configuration as possible, but tweak it + * when it's found to be wrong. + * + * Known BIOS problems we have to work around: + * - I/O or memory regions not configured + * - regions configured, but not enabled in the command register + * - bogus I/O addresses above 64K used + * - expansion ROMs left enabled (this may sound harmless, but given + * the fact the PCI specs explicitly allow address decoders to be + * shared between expansion ROMs and other resource regions, it's + * at least dangerous) + * + * Our solution: + * (1) Allocate resources for all buses behind PCI-to-PCI bridges. + * This gives us fixed barriers on where we can allocate. + * (2) Allocate resources for all enabled devices. If there is + * a collision, just mark the resource as unallocated. Also + * disable expansion ROMs during this step. + * (3) Try to allocate resources for disabled devices. If the + * resources were assigned correctly, everything goes well, + * if they weren't, they won't disturb allocation of other + * resources. + * (4) Assign new addresses to resources which were either + * not configured at all or misconfigured. If explicitly + * requested by the user, configure expansion ROM address + * as well. + */ + +void pcibios_allocate_bus_resources(struct pci_bus *bus) +{ + struct pci_bus *b; + int i; + struct resource *res, *pr; + + pr_debug("PCI: Allocating bus resources for %04x:%02x...\n", + pci_domain_nr(bus), bus->number); + + for (i = 0; i < PCI_BUS_NUM_RESOURCES; ++i) { + res = bus->resource[i]; + if (!res || !res->flags + || res->start > res->end || res->parent) + continue; + if (bus->parent == NULL) + pr = (res->flags & IORESOURCE_IO) ? + &ioport_resource : &iomem_resource; + else { + /* Don't bother with non-root busses when + * re-assigning all resources. We clear the + * resource flags as if they were colliding + * and as such ensure proper re-allocation + * later. + */ + if (pci_flags & PCI_REASSIGN_ALL_RSRC) + goto clear_resource; + pr = pci_find_parent_resource(bus->self, res); + if (pr == res) { + /* this happens when the generic PCI + * code (wrongly) decides that this + * bridge is transparent -- paulus + */ + continue; + } + } + + pr_debug("PCI: %s (bus %d) bridge rsrc %d: %016llx-%016llx " + "[0x%x], parent %p (%s)\n", + bus->self ? pci_name(bus->self) : "PHB", + bus->number, i, + (unsigned long long)res->start, + (unsigned long long)res->end, + (unsigned int)res->flags, + pr, (pr && pr->name) ? pr->name : "nil"); + + if (pr && !(pr->flags & IORESOURCE_UNSET)) { + if (request_resource(pr, res) == 0) + continue; + /* + * Must be a conflict with an existing entry. + * Move that entry (or entries) under the + * bridge resource and try again. + */ + if (reparent_resources(pr, res) == 0) + continue; + } + printk(KERN_WARNING "PCI: Cannot allocate resource region " + "%d of PCI bridge %d, will remap\n", i, bus->number); +clear_resource: + res->flags = 0; + } + + list_for_each_entry(b, &bus->children, node) + pcibios_allocate_bus_resources(b); +} + +static inline void __devinit alloc_resource(struct pci_dev *dev, int idx) +{ + struct resource *pr, *r = &dev->resource[idx]; + + pr_debug("PCI: Allocating %s: Resource %d: %016llx..%016llx [%x]\n", + pci_name(dev), idx, + (unsigned long long)r->start, + (unsigned long long)r->end, + (unsigned int)r->flags); + + pr = pci_find_parent_resource(dev, r); + if (!pr || (pr->flags & IORESOURCE_UNSET) || + request_resource(pr, r) < 0) { + printk(KERN_WARNING "PCI: Cannot allocate resource region %d" + " of device %s, will remap\n", idx, pci_name(dev)); + if (pr) + pr_debug("PCI: parent is %p: %016llx-%016llx [%x]\n", + pr, + (unsigned long long)pr->start, + (unsigned long long)pr->end, + (unsigned int)pr->flags); + /* We'll assign a new address later */ + r->flags |= IORESOURCE_UNSET; + r->end -= r->start; + r->start = 0; + } +} + +static void __init pcibios_allocate_resources(int pass) +{ + struct pci_dev *dev = NULL; + int idx, disabled; + u16 command; + struct resource *r; + + for_each_pci_dev(dev) { + pci_read_config_word(dev, PCI_COMMAND, &command); + for (idx = 0; idx <= PCI_ROM_RESOURCE; idx++) { + r = &dev->resource[idx]; + if (r->parent) /* Already allocated */ + continue; + if (!r->flags || (r->flags & IORESOURCE_UNSET)) + continue; /* Not assigned at all */ + /* We only allocate ROMs on pass 1 just in case they + * have been screwed up by firmware + */ + if (idx == PCI_ROM_RESOURCE) + disabled = 1; + if (r->flags & IORESOURCE_IO) + disabled = !(command & PCI_COMMAND_IO); + else + disabled = !(command & PCI_COMMAND_MEMORY); + if (pass == disabled) + alloc_resource(dev, idx); + } + if (pass) + continue; + r = &dev->resource[PCI_ROM_RESOURCE]; + if (r->flags) { + /* Turn the ROM off, leave the resource region, + * but keep it unregistered. + */ + u32 reg; + pci_read_config_dword(dev, dev->rom_base_reg, ®); + if (reg & PCI_ROM_ADDRESS_ENABLE) { + pr_debug("PCI: Switching off ROM of %s\n", + pci_name(dev)); + r->flags &= ~IORESOURCE_ROM_ENABLE; + pci_write_config_dword(dev, dev->rom_base_reg, + reg & ~PCI_ROM_ADDRESS_ENABLE); + } + } + } +} + +static void __init pcibios_reserve_legacy_regions(struct pci_bus *bus) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + resource_size_t offset; + struct resource *res, *pres; + int i; + + pr_debug("Reserving legacy ranges for domain %04x\n", + pci_domain_nr(bus)); + + /* Check for IO */ + if (!(hose->io_resource.flags & IORESOURCE_IO)) + goto no_io; + offset = (unsigned long)hose->io_base_virt - _IO_BASE; + res = kzalloc(sizeof(struct resource), GFP_KERNEL); + BUG_ON(res == NULL); + res->name = "Legacy IO"; + res->flags = IORESOURCE_IO; + res->start = offset; + res->end = (offset + 0xfff) & 0xfffffffful; + pr_debug("Candidate legacy IO: %pR\n", res); + if (request_resource(&hose->io_resource, res)) { + printk(KERN_DEBUG + "PCI %04x:%02x Cannot reserve Legacy IO %pR\n", + pci_domain_nr(bus), bus->number, res); + kfree(res); + } + + no_io: + /* Check for memory */ + offset = hose->pci_mem_offset; + pr_debug("hose mem offset: %016llx\n", (unsigned long long)offset); + for (i = 0; i < 3; i++) { + pres = &hose->mem_resources[i]; + if (!(pres->flags & IORESOURCE_MEM)) + continue; + pr_debug("hose mem res: %pR\n", pres); + if ((pres->start - offset) <= 0xa0000 && + (pres->end - offset) >= 0xbffff) + break; + } + if (i >= 3) + return; + res = kzalloc(sizeof(struct resource), GFP_KERNEL); + BUG_ON(res == NULL); + res->name = "Legacy VGA memory"; + res->flags = IORESOURCE_MEM; + res->start = 0xa0000 + offset; + res->end = 0xbffff + offset; + pr_debug("Candidate VGA memory: %pR\n", res); + if (request_resource(pres, res)) { + printk(KERN_DEBUG + "PCI %04x:%02x Cannot reserve VGA memory %pR\n", + pci_domain_nr(bus), bus->number, res); + kfree(res); + } +} + +void __init pcibios_resource_survey(void) +{ + struct pci_bus *b; + + /* Allocate and assign resources. If we re-assign everything, then + * we skip the allocate phase + */ + list_for_each_entry(b, &pci_root_buses, node) + pcibios_allocate_bus_resources(b); + + if (!(pci_flags & PCI_REASSIGN_ALL_RSRC)) { + pcibios_allocate_resources(0); + pcibios_allocate_resources(1); + } + + /* Before we start assigning unassigned resource, we try to reserve + * the low IO area and the VGA memory area if they intersect the + * bus available resources to avoid allocating things on top of them + */ + if (!(pci_flags & PCI_PROBE_ONLY)) { + list_for_each_entry(b, &pci_root_buses, node) + pcibios_reserve_legacy_regions(b); + } + + /* Now, if the platform didn't decide to blindly trust the firmware, + * we proceed to assigning things that were left unassigned + */ + if (!(pci_flags & PCI_PROBE_ONLY)) { + pr_debug("PCI: Assigning unassigned resources...\n"); + pci_assign_unassigned_resources(); + } +} + +#ifdef CONFIG_HOTPLUG + +/* This is used by the PCI hotplug driver to allocate resource + * of newly plugged busses. We can try to consolidate with the + * rest of the code later, for now, keep it as-is as our main + * resource allocation function doesn't deal with sub-trees yet. + */ +void __devinit pcibios_claim_one_bus(struct pci_bus *bus) +{ + struct pci_dev *dev; + struct pci_bus *child_bus; + + list_for_each_entry(dev, &bus->devices, bus_list) { + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r = &dev->resource[i]; + + if (r->parent || !r->start || !r->flags) + continue; + + pr_debug("PCI: Claiming %s: " + "Resource %d: %016llx..%016llx [%x]\n", + pci_name(dev), i, + (unsigned long long)r->start, + (unsigned long long)r->end, + (unsigned int)r->flags); + + pci_claim_resource(dev, i); + } + } + + list_for_each_entry(child_bus, &bus->children, node) + pcibios_claim_one_bus(child_bus); +} +EXPORT_SYMBOL_GPL(pcibios_claim_one_bus); + + +/* pcibios_finish_adding_to_bus + * + * This is to be called by the hotplug code after devices have been + * added to a bus, this include calling it for a PHB that is just + * being added + */ +void pcibios_finish_adding_to_bus(struct pci_bus *bus) +{ + pr_debug("PCI: Finishing adding to hotplug bus %04x:%02x\n", + pci_domain_nr(bus), bus->number); + + /* Allocate bus and devices resources */ + pcibios_allocate_bus_resources(bus); + pcibios_claim_one_bus(bus); + + /* Add new devices to global lists. Register in proc, sysfs. */ + pci_bus_add_devices(bus); + + /* Fixup EEH */ + eeh_add_device_tree_late(bus); +} +EXPORT_SYMBOL_GPL(pcibios_finish_adding_to_bus); + +#endif /* CONFIG_HOTPLUG */ + +int pcibios_enable_device(struct pci_dev *dev, int mask) +{ + return pci_enable_resources(dev, mask); +} + +void __devinit pcibios_setup_phb_resources(struct pci_controller *hose) +{ + struct pci_bus *bus = hose->bus; + struct resource *res; + int i; + + /* Hookup PHB IO resource */ + bus->resource[0] = res = &hose->io_resource; + + if (!res->flags) { + printk(KERN_WARNING "PCI: I/O resource not set for host" + " bridge %s (domain %d)\n", + hose->dn->full_name, hose->global_number); + /* Workaround for lack of IO resource only on 32-bit */ + res->start = (unsigned long)hose->io_base_virt - isa_io_base; + res->end = res->start + IO_SPACE_LIMIT; + res->flags = IORESOURCE_IO; + } + + pr_debug("PCI: PHB IO resource = %016llx-%016llx [%lx]\n", + (unsigned long long)res->start, + (unsigned long long)res->end, + (unsigned long)res->flags); + + /* Hookup PHB Memory resources */ + for (i = 0; i < 3; ++i) { + res = &hose->mem_resources[i]; + if (!res->flags) { + if (i > 0) + continue; + printk(KERN_ERR "PCI: Memory resource 0 not set for " + "host bridge %s (domain %d)\n", + hose->dn->full_name, hose->global_number); + + /* Workaround for lack of MEM resource only on 32-bit */ + res->start = hose->pci_mem_offset; + res->end = (resource_size_t)-1LL; + res->flags = IORESOURCE_MEM; + + } + bus->resource[i+1] = res; + + pr_debug("PCI: PHB MEM resource %d = %016llx-%016llx [%lx]\n", + i, (unsigned long long)res->start, + (unsigned long long)res->end, + (unsigned long)res->flags); + } + + pr_debug("PCI: PHB MEM offset = %016llx\n", + (unsigned long long)hose->pci_mem_offset); + pr_debug("PCI: PHB IO offset = %08lx\n", + (unsigned long)hose->io_base_virt - _IO_BASE); +} + +/* + * Null PCI config access functions, for the case when we can't + * find a hose. + */ +#define NULL_PCI_OP(rw, size, type) \ +static int \ +null_##rw##_config_##size(struct pci_dev *dev, int offset, type val) \ +{ \ + return PCIBIOS_DEVICE_NOT_FOUND; \ +} + +static int +null_read_config(struct pci_bus *bus, unsigned int devfn, int offset, + int len, u32 *val) +{ + return PCIBIOS_DEVICE_NOT_FOUND; +} + +static int +null_write_config(struct pci_bus *bus, unsigned int devfn, int offset, + int len, u32 val) +{ + return PCIBIOS_DEVICE_NOT_FOUND; +} + +static struct pci_ops null_pci_ops = { + .read = null_read_config, + .write = null_write_config, +}; + +/* + * These functions are used early on before PCI scanning is done + * and all of the pci_dev and pci_bus structures have been created. + */ +static struct pci_bus * +fake_pci_bus(struct pci_controller *hose, int busnr) +{ + static struct pci_bus bus; + + if (!hose) + printk(KERN_ERR "Can't find hose for PCI bus %d!\n", busnr); + + bus.number = busnr; + bus.sysdata = hose; + bus.ops = hose ? hose->ops : &null_pci_ops; + return &bus; +} + +#define EARLY_PCI_OP(rw, size, type) \ +int early_##rw##_config_##size(struct pci_controller *hose, int bus, \ + int devfn, int offset, type value) \ +{ \ + return pci_bus_##rw##_config_##size(fake_pci_bus(hose, bus), \ + devfn, offset, value); \ +} + +EARLY_PCI_OP(read, byte, u8 *) +EARLY_PCI_OP(read, word, u16 *) +EARLY_PCI_OP(read, dword, u32 *) +EARLY_PCI_OP(write, byte, u8) +EARLY_PCI_OP(write, word, u16) +EARLY_PCI_OP(write, dword, u32) + +int early_find_capability(struct pci_controller *hose, int bus, int devfn, + int cap) +{ + return pci_bus_find_capability(fake_pci_bus(hose, bus), devfn, cap); +} diff --git a/arch/microblaze/pci/pci_32.c b/arch/microblaze/pci/pci_32.c new file mode 100644 index 00000000000..7e0c94f501c --- /dev/null +++ b/arch/microblaze/pci/pci_32.c @@ -0,0 +1,430 @@ +/* + * Common pmac/prep/chrp pci routines. -- Cort + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#undef DEBUG + +unsigned long isa_io_base; +unsigned long pci_dram_offset; +int pcibios_assign_bus_offset = 1; + +static u8 *pci_to_OF_bus_map; + +/* By default, we don't re-assign bus numbers. We do this only on + * some pmacs + */ +static int pci_assign_all_buses; + +static int pci_bus_count; + +/* + * Functions below are used on OpenFirmware machines. + */ +static void +make_one_node_map(struct device_node *node, u8 pci_bus) +{ + const int *bus_range; + int len; + + if (pci_bus >= pci_bus_count) + return; + bus_range = of_get_property(node, "bus-range", &len); + if (bus_range == NULL || len < 2 * sizeof(int)) { + printk(KERN_WARNING "Can't get bus-range for %s, " + "assuming it starts at 0\n", node->full_name); + pci_to_OF_bus_map[pci_bus] = 0; + } else + pci_to_OF_bus_map[pci_bus] = bus_range[0]; + + for_each_child_of_node(node, node) { + struct pci_dev *dev; + const unsigned int *class_code, *reg; + + class_code = of_get_property(node, "class-code", NULL); + if (!class_code || + ((*class_code >> 8) != PCI_CLASS_BRIDGE_PCI && + (*class_code >> 8) != PCI_CLASS_BRIDGE_CARDBUS)) + continue; + reg = of_get_property(node, "reg", NULL); + if (!reg) + continue; + dev = pci_get_bus_and_slot(pci_bus, ((reg[0] >> 8) & 0xff)); + if (!dev || !dev->subordinate) { + pci_dev_put(dev); + continue; + } + make_one_node_map(node, dev->subordinate->number); + pci_dev_put(dev); + } +} + +void +pcibios_make_OF_bus_map(void) +{ + int i; + struct pci_controller *hose, *tmp; + struct property *map_prop; + struct device_node *dn; + + pci_to_OF_bus_map = kmalloc(pci_bus_count, GFP_KERNEL); + if (!pci_to_OF_bus_map) { + printk(KERN_ERR "Can't allocate OF bus map !\n"); + return; + } + + /* We fill the bus map with invalid values, that helps + * debugging. + */ + for (i = 0; i < pci_bus_count; i++) + pci_to_OF_bus_map[i] = 0xff; + + /* For each hose, we begin searching bridges */ + list_for_each_entry_safe(hose, tmp, &hose_list, list_node) { + struct device_node *node = hose->dn; + + if (!node) + continue; + make_one_node_map(node, hose->first_busno); + } + dn = of_find_node_by_path("/"); + map_prop = of_find_property(dn, "pci-OF-bus-map", NULL); + if (map_prop) { + BUG_ON(pci_bus_count > map_prop->length); + memcpy(map_prop->value, pci_to_OF_bus_map, pci_bus_count); + } + of_node_put(dn); +#ifdef DEBUG + printk(KERN_INFO "PCI->OF bus map:\n"); + for (i = 0; i < pci_bus_count; i++) { + if (pci_to_OF_bus_map[i] == 0xff) + continue; + printk(KERN_INFO "%d -> %d\n", i, pci_to_OF_bus_map[i]); + } +#endif +} + +typedef int (*pci_OF_scan_iterator)(struct device_node *node, void *data); + +static struct device_node *scan_OF_pci_childs(struct device_node *parent, + pci_OF_scan_iterator filter, void *data) +{ + struct device_node *node; + struct device_node *sub_node; + + for_each_child_of_node(parent, node) { + const unsigned int *class_code; + + if (filter(node, data)) { + of_node_put(node); + return node; + } + + /* For PCI<->PCI bridges or CardBus bridges, we go down + * Note: some OFs create a parent node "multifunc-device" as + * a fake root for all functions of a multi-function device, + * we go down them as well. + */ + class_code = of_get_property(node, "class-code", NULL); + if ((!class_code || + ((*class_code >> 8) != PCI_CLASS_BRIDGE_PCI && + (*class_code >> 8) != PCI_CLASS_BRIDGE_CARDBUS)) && + strcmp(node->name, "multifunc-device")) + continue; + sub_node = scan_OF_pci_childs(node, filter, data); + if (sub_node) { + of_node_put(node); + return sub_node; + } + } + return NULL; +} + +static struct device_node *scan_OF_for_pci_dev(struct device_node *parent, + unsigned int devfn) +{ + struct device_node *np, *cnp; + const u32 *reg; + unsigned int psize; + + for_each_child_of_node(parent, np) { + reg = of_get_property(np, "reg", &psize); + if (reg && psize >= 4 && ((reg[0] >> 8) & 0xff) == devfn) + return np; + + /* Note: some OFs create a parent node "multifunc-device" as + * a fake root for all functions of a multi-function device, + * we go down them as well. */ + if (!strcmp(np->name, "multifunc-device")) { + cnp = scan_OF_for_pci_dev(np, devfn); + if (cnp) + return cnp; + } + } + return NULL; +} + + +static struct device_node *scan_OF_for_pci_bus(struct pci_bus *bus) +{ + struct device_node *parent, *np; + + /* Are we a root bus ? */ + if (bus->self == NULL || bus->parent == NULL) { + struct pci_controller *hose = pci_bus_to_host(bus); + if (hose == NULL) + return NULL; + return of_node_get(hose->dn); + } + + /* not a root bus, we need to get our parent */ + parent = scan_OF_for_pci_bus(bus->parent); + if (parent == NULL) + return NULL; + + /* now iterate for children for a match */ + np = scan_OF_for_pci_dev(parent, bus->self->devfn); + of_node_put(parent); + + return np; +} + +/* + * Scans the OF tree for a device node matching a PCI device + */ +struct device_node * +pci_busdev_to_OF_node(struct pci_bus *bus, int devfn) +{ + struct device_node *parent, *np; + + pr_debug("pci_busdev_to_OF_node(%d,0x%x)\n", bus->number, devfn); + parent = scan_OF_for_pci_bus(bus); + if (parent == NULL) + return NULL; + pr_debug(" parent is %s\n", parent ? parent->full_name : ""); + np = scan_OF_for_pci_dev(parent, devfn); + of_node_put(parent); + pr_debug(" result is %s\n", np ? np->full_name : ""); + + /* XXX most callers don't release the returned node + * mostly because ppc64 doesn't increase the refcount, + * we need to fix that. + */ + return np; +} +EXPORT_SYMBOL(pci_busdev_to_OF_node); + +struct device_node* +pci_device_to_OF_node(struct pci_dev *dev) +{ + return pci_busdev_to_OF_node(dev->bus, dev->devfn); +} +EXPORT_SYMBOL(pci_device_to_OF_node); + +static int +find_OF_pci_device_filter(struct device_node *node, void *data) +{ + return ((void *)node == data); +} + +/* + * Returns the PCI device matching a given OF node + */ +int +pci_device_from_OF_node(struct device_node *node, u8 *bus, u8 *devfn) +{ + const unsigned int *reg; + struct pci_controller *hose; + struct pci_dev *dev = NULL; + + /* Make sure it's really a PCI device */ + hose = pci_find_hose_for_OF_device(node); + if (!hose || !hose->dn) + return -ENODEV; + if (!scan_OF_pci_childs(hose->dn, + find_OF_pci_device_filter, (void *)node)) + return -ENODEV; + reg = of_get_property(node, "reg", NULL); + if (!reg) + return -ENODEV; + *bus = (reg[0] >> 16) & 0xff; + *devfn = ((reg[0] >> 8) & 0xff); + + /* Ok, here we need some tweak. If we have already renumbered + * all busses, we can't rely on the OF bus number any more. + * the pci_to_OF_bus_map is not enough as several PCI busses + * may match the same OF bus number. + */ + if (!pci_to_OF_bus_map) + return 0; + + for_each_pci_dev(dev) + if (pci_to_OF_bus_map[dev->bus->number] == *bus && + dev->devfn == *devfn) { + *bus = dev->bus->number; + pci_dev_put(dev); + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL(pci_device_from_OF_node); + +/* We create the "pci-OF-bus-map" property now so it appears in the + * /proc device tree + */ +void __init +pci_create_OF_bus_map(void) +{ + struct property *of_prop; + struct device_node *dn; + + of_prop = (struct property *) alloc_bootmem(sizeof(struct property) + \ + 256); + if (!of_prop) + return; + dn = of_find_node_by_path("/"); + if (dn) { + memset(of_prop, -1, sizeof(struct property) + 256); + of_prop->name = "pci-OF-bus-map"; + of_prop->length = 256; + of_prop->value = &of_prop[1]; + prom_add_property(dn, of_prop); + of_node_put(dn); + } +} + +static void __devinit pcibios_scan_phb(struct pci_controller *hose) +{ + struct pci_bus *bus; + struct device_node *node = hose->dn; + unsigned long io_offset; + struct resource *res = &hose->io_resource; + + pr_debug("PCI: Scanning PHB %s\n", + node ? node->full_name : ""); + + /* Create an empty bus for the toplevel */ + bus = pci_create_bus(hose->parent, hose->first_busno, hose->ops, hose); + if (bus == NULL) { + printk(KERN_ERR "Failed to create bus for PCI domain %04x\n", + hose->global_number); + return; + } + bus->secondary = hose->first_busno; + hose->bus = bus; + + /* Fixup IO space offset */ + io_offset = (unsigned long)hose->io_base_virt - isa_io_base; + res->start = (res->start + io_offset) & 0xffffffffu; + res->end = (res->end + io_offset) & 0xffffffffu; + + /* Wire up PHB bus resources */ + pcibios_setup_phb_resources(hose); + + /* Scan children */ + hose->last_busno = bus->subordinate = pci_scan_child_bus(bus); +} + +static int __init pcibios_init(void) +{ + struct pci_controller *hose, *tmp; + int next_busno = 0; + + printk(KERN_INFO "PCI: Probing PCI hardware\n"); + + if (pci_flags & PCI_REASSIGN_ALL_BUS) { + printk(KERN_INFO "setting pci_asign_all_busses\n"); + pci_assign_all_buses = 1; + } + + /* Scan all of the recorded PCI controllers. */ + list_for_each_entry_safe(hose, tmp, &hose_list, list_node) { + if (pci_assign_all_buses) + hose->first_busno = next_busno; + hose->last_busno = 0xff; + pcibios_scan_phb(hose); + printk(KERN_INFO "calling pci_bus_add_devices()\n"); + pci_bus_add_devices(hose->bus); + if (pci_assign_all_buses || next_busno <= hose->last_busno) + next_busno = hose->last_busno + \ + pcibios_assign_bus_offset; + } + pci_bus_count = next_busno; + + /* OpenFirmware based machines need a map of OF bus + * numbers vs. kernel bus numbers since we may have to + * remap them. + */ + if (pci_assign_all_buses) + pcibios_make_OF_bus_map(); + + /* Call common code to handle resource allocation */ + pcibios_resource_survey(); + + return 0; +} + +subsys_initcall(pcibios_init); + +static struct pci_controller* +pci_bus_to_hose(int bus) +{ + struct pci_controller *hose, *tmp; + + list_for_each_entry_safe(hose, tmp, &hose_list, list_node) + if (bus >= hose->first_busno && bus <= hose->last_busno) + return hose; + return NULL; +} + +/* Provide information on locations of various I/O regions in physical + * memory. Do this on a per-card basis so that we choose the right + * root bridge. + * Note that the returned IO or memory base is a physical address + */ + +long sys_pciconfig_iobase(long which, unsigned long bus, unsigned long devfn) +{ + struct pci_controller *hose; + long result = -EOPNOTSUPP; + + hose = pci_bus_to_hose(bus); + if (!hose) + return -ENODEV; + + switch (which) { + case IOBASE_BRIDGE_NUMBER: + return (long)hose->first_busno; + case IOBASE_MEMORY: + return (long)hose->pci_mem_offset; + case IOBASE_IO: + return (long)hose->io_base_phys; + case IOBASE_ISA_IO: + return (long)isa_io_base; + case IOBASE_ISA_MEM: + return (long)isa_mem_base; + } + + return result; +} -- cgit v1.2.3 From a6475c132278c1be158a13872c233aeab8a00176 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 18 Jan 2010 15:27:10 +0100 Subject: microblaze: Enable PCI, missing files There are two parts of changes. The first is just enable PCI in Makefiles and in Kconfig. The second is the rest of missing files. I didn't want to add it with previous patch because that patch is too big. Current Microblaze toolchain has problem with weak symbols that's why is necessary to apply this changes to be possible to compile pci support. Xilinx knows about this problem. Signed-off-by: Michal Simek --- arch/microblaze/Kconfig | 15 ++++ arch/microblaze/Makefile | 1 + arch/microblaze/include/asm/io.h | 16 +++- arch/microblaze/include/asm/pgtable.h | 15 ++++ arch/microblaze/include/asm/prom.h | 15 ++++ arch/microblaze/pci/Makefile | 5 ++ arch/microblaze/pci/indirect_pci.c | 163 ++++++++++++++++++++++++++++++++++ arch/microblaze/pci/iomap.c | 39 ++++++++ 8 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 arch/microblaze/pci/Makefile create mode 100644 arch/microblaze/pci/indirect_pci.c create mode 100644 arch/microblaze/pci/iomap.c (limited to 'arch') diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index 71ec0413741..e1fa0844ba4 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -256,6 +256,21 @@ source "fs/Kconfig.binfmt" endmenu +menu "Bus Options" + +config PCI + bool "PCI support" + +config PCI_DOMAINS + def_bool PCI + +config PCI_SYSCALL + def_bool PCI + +source "drivers/pci/Kconfig" + +endmenu + source "net/Kconfig" source "drivers/Kconfig" diff --git a/arch/microblaze/Makefile b/arch/microblaze/Makefile index d2d6cfcb1a3..836832dd9b2 100644 --- a/arch/microblaze/Makefile +++ b/arch/microblaze/Makefile @@ -50,6 +50,7 @@ libs-y += $(LIBGCC) core-y += arch/microblaze/kernel/ core-y += arch/microblaze/mm/ core-y += arch/microblaze/platform/ +core-$(CONFIG_PCI) += arch/microblaze/pci/ drivers-$(CONFIG_OPROFILE) += arch/microblaze/oprofile/ diff --git a/arch/microblaze/include/asm/io.h b/arch/microblaze/include/asm/io.h index f82df5d221a..06d804b15a5 100644 --- a/arch/microblaze/include/asm/io.h +++ b/arch/microblaze/include/asm/io.h @@ -17,7 +17,21 @@ #include /* Get struct page {...} */ #include -#define PCI_DRAM_OFFSET 0 +#ifndef CONFIG_PCI +#define _IO_BASE 0 +#define _ISA_MEM_BASE 0 +#define PCI_DRAM_OFFSET 0 +#else +#define _IO_BASE isa_io_base +#define _ISA_MEM_BASE isa_mem_base +#define PCI_DRAM_OFFSET pci_dram_offset +#endif + +extern unsigned long isa_io_base; +extern unsigned long pci_io_base; +extern unsigned long pci_dram_offset; + +extern resource_size_t isa_mem_base; #define IO_SPACE_LIMIT (0xFFFFFFFF) diff --git a/arch/microblaze/include/asm/pgtable.h b/arch/microblaze/include/asm/pgtable.h index cc3a4dfc3ea..1c47f6f8bfb 100644 --- a/arch/microblaze/include/asm/pgtable.h +++ b/arch/microblaze/include/asm/pgtable.h @@ -89,6 +89,21 @@ static inline pte_t pte_mkspecial(pte_t pte) { return pte; } #endif /* __ASSEMBLY__ */ +/* + * Macro to mark a page protection value as "uncacheable". + */ + +#define _PAGE_CACHE_CTL (_PAGE_GUARDED | _PAGE_NO_CACHE | \ + _PAGE_WRITETHRU) + +#define pgprot_noncached(prot) \ + (__pgprot((pgprot_val(prot) & ~_PAGE_CACHE_CTL) | \ + _PAGE_NO_CACHE | _PAGE_GUARDED)) + +#define pgprot_noncached_wc(prot) \ + (__pgprot((pgprot_val(prot) & ~_PAGE_CACHE_CTL) | \ + _PAGE_NO_CACHE)) + /* * The MicroBlaze MMU is identical to the PPC-40x MMU, and uses a hash * table containing PTEs, together with a set of 16 segment registers, to diff --git a/arch/microblaze/include/asm/prom.h b/arch/microblaze/include/asm/prom.h index 03f45a96320..e7d67a329bd 100644 --- a/arch/microblaze/include/asm/prom.h +++ b/arch/microblaze/include/asm/prom.h @@ -31,6 +31,21 @@ /* Other Prototypes */ extern int early_uartlite_console(void); +#ifdef CONFIG_PCI +/* + * PCI <-> OF matching functions + * (XXX should these be here?) + */ +struct pci_bus; +struct pci_dev; +extern int pci_device_from_OF_node(struct device_node *node, + u8 *bus, u8 *devfn); +extern struct device_node *pci_busdev_to_OF_node(struct pci_bus *bus, + int devfn); +extern struct device_node *pci_device_to_OF_node(struct pci_dev *dev); +extern void pci_create_OF_bus_map(void); +#endif + /* * OF address retreival & translation */ diff --git a/arch/microblaze/pci/Makefile b/arch/microblaze/pci/Makefile new file mode 100644 index 00000000000..2b8901864b2 --- /dev/null +++ b/arch/microblaze/pci/Makefile @@ -0,0 +1,5 @@ +# +# Makefile +# + +obj-$(CONFIG_PCI) += pci_32.o pci-common.o indirect_pci.o iomap.o diff --git a/arch/microblaze/pci/indirect_pci.c b/arch/microblaze/pci/indirect_pci.c new file mode 100644 index 00000000000..25f18f017f2 --- /dev/null +++ b/arch/microblaze/pci/indirect_pci.c @@ -0,0 +1,163 @@ +/* + * Support for indirect PCI bridges. + * + * Copyright (C) 1998 Gabriel Paubert. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static int +indirect_read_config(struct pci_bus *bus, unsigned int devfn, int offset, + int len, u32 *val) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + volatile void __iomem *cfg_data; + u8 cfg_type = 0; + u32 bus_no, reg; + + if (hose->indirect_type & INDIRECT_TYPE_NO_PCIE_LINK) { + if (bus->number != hose->first_busno) + return PCIBIOS_DEVICE_NOT_FOUND; + if (devfn != 0) + return PCIBIOS_DEVICE_NOT_FOUND; + } + + if (hose->indirect_type & INDIRECT_TYPE_SET_CFG_TYPE) + if (bus->number != hose->first_busno) + cfg_type = 1; + + bus_no = (bus->number == hose->first_busno) ? + hose->self_busno : bus->number; + + if (hose->indirect_type & INDIRECT_TYPE_EXT_REG) + reg = ((offset & 0xf00) << 16) | (offset & 0xfc); + else + reg = offset & 0xfc; /* Only 3 bits for function */ + + if (hose->indirect_type & INDIRECT_TYPE_BIG_ENDIAN) + out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | + (devfn << 8) | reg | cfg_type)); + else + out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | + (devfn << 8) | reg | cfg_type)); + + /* + * Note: the caller has already checked that offset is + * suitably aligned and that len is 1, 2 or 4. + */ + cfg_data = hose->cfg_data + (offset & 3); /* Only 3 bits for function */ + switch (len) { + case 1: + *val = in_8(cfg_data); + break; + case 2: + *val = in_le16(cfg_data); + break; + default: + *val = in_le32(cfg_data); + break; + } + return PCIBIOS_SUCCESSFUL; +} + +static int +indirect_write_config(struct pci_bus *bus, unsigned int devfn, int offset, + int len, u32 val) +{ + struct pci_controller *hose = pci_bus_to_host(bus); + volatile void __iomem *cfg_data; + u8 cfg_type = 0; + u32 bus_no, reg; + + if (hose->indirect_type & INDIRECT_TYPE_NO_PCIE_LINK) { + if (bus->number != hose->first_busno) + return PCIBIOS_DEVICE_NOT_FOUND; + if (devfn != 0) + return PCIBIOS_DEVICE_NOT_FOUND; + } + + if (hose->indirect_type & INDIRECT_TYPE_SET_CFG_TYPE) + if (bus->number != hose->first_busno) + cfg_type = 1; + + bus_no = (bus->number == hose->first_busno) ? + hose->self_busno : bus->number; + + if (hose->indirect_type & INDIRECT_TYPE_EXT_REG) + reg = ((offset & 0xf00) << 16) | (offset & 0xfc); + else + reg = offset & 0xfc; + + if (hose->indirect_type & INDIRECT_TYPE_BIG_ENDIAN) + out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | + (devfn << 8) | reg | cfg_type)); + else + out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | + (devfn << 8) | reg | cfg_type)); + + /* surpress setting of PCI_PRIMARY_BUS */ + if (hose->indirect_type & INDIRECT_TYPE_SURPRESS_PRIMARY_BUS) + if ((offset == PCI_PRIMARY_BUS) && + (bus->number == hose->first_busno)) + val &= 0xffffff00; + + /* Workaround for PCI_28 Errata in 440EPx/GRx */ + if ((hose->indirect_type & INDIRECT_TYPE_BROKEN_MRM) && + offset == PCI_CACHE_LINE_SIZE) { + val = 0; + } + + /* + * Note: the caller has already checked that offset is + * suitably aligned and that len is 1, 2 or 4. + */ + cfg_data = hose->cfg_data + (offset & 3); + switch (len) { + case 1: + out_8(cfg_data, val); + break; + case 2: + out_le16(cfg_data, val); + break; + default: + out_le32(cfg_data, val); + break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops indirect_pci_ops = { + .read = indirect_read_config, + .write = indirect_write_config, +}; + +void __init +setup_indirect_pci(struct pci_controller *hose, + resource_size_t cfg_addr, + resource_size_t cfg_data, u32 flags) +{ + resource_size_t base = cfg_addr & PAGE_MASK; + void __iomem *mbase; + + mbase = ioremap(base, PAGE_SIZE); + hose->cfg_addr = mbase + (cfg_addr & ~PAGE_MASK); + if ((cfg_data & PAGE_MASK) != base) + mbase = ioremap(cfg_data & PAGE_MASK, PAGE_SIZE); + hose->cfg_data = mbase + (cfg_data & ~PAGE_MASK); + hose->ops = &indirect_pci_ops; + hose->indirect_type = flags; +} diff --git a/arch/microblaze/pci/iomap.c b/arch/microblaze/pci/iomap.c new file mode 100644 index 00000000000..3fbf16f4e16 --- /dev/null +++ b/arch/microblaze/pci/iomap.c @@ -0,0 +1,39 @@ +/* + * ppc64 "iomap" interface implementation. + * + * (C) Copyright 2004 Linus Torvalds + */ +#include +#include +#include +#include +#include + +void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long max) +{ + resource_size_t start = pci_resource_start(dev, bar); + resource_size_t len = pci_resource_len(dev, bar); + unsigned long flags = pci_resource_flags(dev, bar); + + if (!len) + return NULL; + if (max && len > max) + len = max; + if (flags & IORESOURCE_IO) + return ioport_map(start, len); + if (flags & IORESOURCE_MEM) + return ioremap(start, len); + /* What? */ + return NULL; +} +EXPORT_SYMBOL(pci_iomap); + +void pci_iounmap(struct pci_dev *dev, void __iomem *addr) +{ + if (isa_vaddr_is_ioport(addr)) + return; + if (pcibios_vaddr_is_ioport(addr)) + return; + iounmap(addr); +} +EXPORT_SYMBOL(pci_iounmap); -- cgit v1.2.3 From 733cc2183116b216abb52e709709bb0e626c9a75 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 18 Jan 2010 15:27:11 +0100 Subject: microblaze: Add support for Xilinx PCI host bridge This patch is based on powerpc patch 64f16502475ddf663169369fffff6da9b10ea9fb We did some cleanups and removed powerpc parts. There is one new debug early listing function too. Exclude function is only in Debug options. We tested in on custom board. Signed-off-by: Michal Simek --- arch/microblaze/Kconfig | 4 + arch/microblaze/include/asm/pci.h | 7 ++ arch/microblaze/kernel/setup.c | 3 + arch/microblaze/pci/Makefile | 1 + arch/microblaze/pci/xilinx_pci.c | 168 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 arch/microblaze/pci/xilinx_pci.c (limited to 'arch') diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index e1fa0844ba4..c1e731bb330 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -267,6 +267,10 @@ config PCI_DOMAINS config PCI_SYSCALL def_bool PCI +config PCI_XILINX + bool "Xilinx PCI host bridge support" + depends on PCI + source "drivers/pci/Kconfig" endmenu diff --git a/arch/microblaze/include/asm/pci.h b/arch/microblaze/include/asm/pci.h index fecc0448110..bdd65aaee30 100644 --- a/arch/microblaze/include/asm/pci.h +++ b/arch/microblaze/include/asm/pci.h @@ -166,5 +166,12 @@ extern void pci_resource_to_user(const struct pci_dev *dev, int bar, extern void pcibios_setup_bus_devices(struct pci_bus *bus); extern void pcibios_setup_bus_self(struct pci_bus *bus); +/* This part of code was originaly in xilinx-pci.h */ +#ifdef CONFIG_PCI_XILINX +extern void __init xilinx_pci_init(void); +#else +static inline void __init xilinx_pci_init(void) { return; } +#endif + #endif /* __KERNEL__ */ #endif /* __ASM_MICROBLAZE_PCI_H */ diff --git a/arch/microblaze/kernel/setup.c b/arch/microblaze/kernel/setup.c index bc325ac4efd..cd68e988b48 100644 --- a/arch/microblaze/kernel/setup.c +++ b/arch/microblaze/kernel/setup.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,8 @@ void __init setup_arch(char **cmdline_p) setup_memory(); + xilinx_pci_init(); + #if defined(CONFIG_SELFMOD_INTC) || defined(CONFIG_SELFMOD_TIMER) printk(KERN_NOTICE "Self modified code enable\n"); #endif diff --git a/arch/microblaze/pci/Makefile b/arch/microblaze/pci/Makefile index 2b8901864b2..9889cc2e129 100644 --- a/arch/microblaze/pci/Makefile +++ b/arch/microblaze/pci/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_PCI) += pci_32.o pci-common.o indirect_pci.o iomap.o +obj-$(CONFIG_PCI_XILINX) += xilinx_pci.o diff --git a/arch/microblaze/pci/xilinx_pci.c b/arch/microblaze/pci/xilinx_pci.c new file mode 100644 index 00000000000..7869a41b0f9 --- /dev/null +++ b/arch/microblaze/pci/xilinx_pci.c @@ -0,0 +1,168 @@ +/* + * PCI support for Xilinx plbv46_pci soft-core which can be used on + * Xilinx Virtex ML410 / ML510 boards. + * + * Copyright 2009 Roderick Colenbrander + * Copyright 2009 Secret Lab Technologies Ltd. + * + * The pci bridge fixup code was copied from ppc4xx_pci.c and was written + * by Benjamin Herrenschmidt. + * Copyright 2007 Ben. Herrenschmidt , IBM Corp. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include + +#define XPLB_PCI_ADDR 0x10c +#define XPLB_PCI_DATA 0x110 +#define XPLB_PCI_BUS 0x114 + +#define PCI_HOST_ENABLE_CMD (PCI_COMMAND_SERR | PCI_COMMAND_PARITY | \ + PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY) + +static struct of_device_id xilinx_pci_match[] = { + { .compatible = "xlnx,plbv46-pci-1.03.a", }, + {} +}; + +/** + * xilinx_pci_fixup_bridge - Block Xilinx PHB configuration. + */ +static void xilinx_pci_fixup_bridge(struct pci_dev *dev) +{ + struct pci_controller *hose; + int i; + + if (dev->devfn || dev->bus->self) + return; + + hose = pci_bus_to_host(dev->bus); + if (!hose) + return; + + if (!of_match_node(xilinx_pci_match, hose->dn)) + return; + + /* Hide the PCI host BARs from the kernel as their content doesn't + * fit well in the resource management + */ + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + dev->resource[i].start = 0; + dev->resource[i].end = 0; + dev->resource[i].flags = 0; + } + + dev_info(&dev->dev, "Hiding Xilinx plb-pci host bridge resources %s\n", + pci_name(dev)); +} +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, xilinx_pci_fixup_bridge); + +#ifdef DEBUG +/** + * xilinx_pci_exclude_device - Don't do config access for non-root bus + * + * This is a hack. Config access to any bus other than bus 0 does not + * currently work on the ML510 so we prevent it here. + */ +static int +xilinx_pci_exclude_device(struct pci_controller *hose, u_char bus, u8 devfn) +{ + return (bus != 0); +} + +/** + * xilinx_early_pci_scan - List pci config space for available devices + * + * List pci devices in very early phase. + */ +void __init xilinx_early_pci_scan(struct pci_controller *hose) +{ + u32 bus = 0; + u32 val, dev, func, offset; + + /* Currently we have only 2 device connected - up-to 32 devices */ + for (dev = 0; dev < 2; dev++) { + /* List only first function number - up-to 8 functions */ + for (func = 0; func < 1; func++) { + printk(KERN_INFO "%02x:%02x:%02x", bus, dev, func); + /* read the first 64 standardized bytes */ + /* Up-to 192 bytes can be list of capabilities */ + for (offset = 0; offset < 64; offset += 4) { + early_read_config_dword(hose, bus, + PCI_DEVFN(dev, func), offset, &val); + if (offset == 0 && val == 0xFFFFFFFF) { + printk(KERN_CONT "\nABSENT"); + break; + } + if (!(offset % 0x10)) + printk(KERN_CONT "\n%04x: ", offset); + + printk(KERN_CONT "%08x ", val); + } + printk(KERN_INFO "\n"); + } + } +} +#else +void __init xilinx_early_pci_scan(struct pci_controller *hose) +{ +} +#endif + +/** + * xilinx_pci_init - Find and register a Xilinx PCI host bridge + */ +void __init xilinx_pci_init(void) +{ + struct pci_controller *hose; + struct resource r; + void __iomem *pci_reg; + struct device_node *pci_node; + + pci_node = of_find_matching_node(NULL, xilinx_pci_match); + if (!pci_node) + return; + + if (of_address_to_resource(pci_node, 0, &r)) { + pr_err("xilinx-pci: cannot resolve base address\n"); + return; + } + + hose = pcibios_alloc_controller(pci_node); + if (!hose) { + pr_err("xilinx-pci: pcibios_alloc_controller() failed\n"); + return; + } + + /* Setup config space */ + setup_indirect_pci(hose, r.start + XPLB_PCI_ADDR, + r.start + XPLB_PCI_DATA, + INDIRECT_TYPE_SET_CFG_TYPE); + + /* According to the xilinx plbv46_pci documentation the soft-core starts + * a self-init when the bus master enable bit is set. Without this bit + * set the pci bus can't be scanned. + */ + early_write_config_word(hose, 0, 0, PCI_COMMAND, PCI_HOST_ENABLE_CMD); + + /* Set the max latency timer to 255 */ + early_write_config_byte(hose, 0, 0, PCI_LATENCY_TIMER, 0xff); + + /* Set the max bus number to 255, and bus/subbus no's to 0 */ + pci_reg = of_iomap(pci_node, 0); + out_be32(pci_reg + XPLB_PCI_BUS, 0x000000ff); + iounmap(pci_reg); + + /* Register the host bridge with the linux kernel! */ + pci_process_bridge_OF_ranges(hose, pci_node, + INDIRECT_TYPE_SET_CFG_TYPE); + + pr_info("xilinx-pci: Registered PCI host bridge\n"); + xilinx_early_pci_scan(hose); +} -- cgit v1.2.3 From 777537905744c28b02c283692e7f75f5445c1afa Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Tue, 12 Jan 2010 09:55:10 +0100 Subject: microblaze: Add support from PREEMPT This patch add core PREEMPT support for Microblaze. I tried to trace it via tracers and I was able to see any output. I also added low level debug functions to see if that code is called. Signed-off-by: Michal Simek --- arch/microblaze/include/asm/tlbflush.h | 2 +- arch/microblaze/kernel/asm-offsets.c | 1 + arch/microblaze/kernel/entry.S | 25 ++++++++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/tlbflush.h b/arch/microblaze/include/asm/tlbflush.h index 10ec70cd873..bcb8b41d55a 100644 --- a/arch/microblaze/include/asm/tlbflush.h +++ b/arch/microblaze/include/asm/tlbflush.h @@ -23,7 +23,7 @@ extern void _tlbie(unsigned long address); extern void _tlbia(void); -#define __tlbia() _tlbia() +#define __tlbia() { preempt_disable(); _tlbia(); preempt_enable(); } static inline void local_flush_tlb_all(void) { __tlbia(); } diff --git a/arch/microblaze/kernel/asm-offsets.c b/arch/microblaze/kernel/asm-offsets.c index 7bc7b68f97d..0071260a672 100644 --- a/arch/microblaze/kernel/asm-offsets.c +++ b/arch/microblaze/kernel/asm-offsets.c @@ -90,6 +90,7 @@ int main(int argc, char *argv[]) DEFINE(TI_FLAGS, offsetof(struct thread_info, flags)); DEFINE(TI_ADDR_LIMIT, offsetof(struct thread_info, addr_limit)); DEFINE(TI_CPU_CONTEXT, offsetof(struct thread_info, cpu_context)); + DEFINE(TI_PREEMPT_COUNT, offsetof(struct thread_info, preempt_count)); BLANK(); /* struct cpu_context */ diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index 3bad4ff4947..1a6729dde49 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -853,7 +853,30 @@ no_intr_resched: lwi r1, r1, PT_R1 - PT_SIZE; bri 6f; /* MS: Return to kernel state. */ -2: VM_OFF /* MS: turn off MMU */ +2: +#ifdef CONFIG_PREEMPT + add r11, r0, CURRENT_TASK; + lwi r11, r11, TS_THREAD_INFO; + /* MS: get preempt_count from thread info */ + lwi r5, r11, TI_PREEMPT_COUNT; + bgti r5, restore; + + lwi r5, r11, TI_FLAGS; /* get flags in thread info */ + andi r5, r5, _TIF_NEED_RESCHED; + beqi r5, restore /* if zero jump over */ + +preempt: + /* interrupts are off that's why I am calling preempt_chedule_irq */ + bralid r15, preempt_schedule_irq + nop + add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ + lwi r11, r11, TS_THREAD_INFO; /* get thread info */ + lwi r5, r11, TI_FLAGS; /* get flags in thread info */ + andi r5, r5, _TIF_NEED_RESCHED; + bnei r5, preempt /* if non zero jump to resched */ +restore: +#endif + VM_OFF /* MS: turn off MMU */ tophys(r1,r1) lwi r3, r1, PTO + PT_R3; /* MS: restore saved r3, r4 registers */ lwi r4, r1, PTO + PT_R4; -- cgit v1.2.3 From 407c1da07d5afa001ed0fdb8f379c00bbd09990a Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Tue, 12 Jan 2010 14:51:04 +0100 Subject: microblaze: Move cache function to cache.c It is better to have init cache handling on one place. Signed-off-by: Michal Simek --- arch/microblaze/kernel/cpu/cache.c | 5 +++++ arch/microblaze/kernel/setup.c | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/cpu/cache.c b/arch/microblaze/kernel/cpu/cache.c index 2a56bccce4e..5425f89335e 100644 --- a/arch/microblaze/kernel/cpu/cache.c +++ b/arch/microblaze/kernel/cpu/cache.c @@ -532,4 +532,9 @@ void microblaze_cache_init(void) } } } + invalidate_dcache(); + enable_dcache(); + + invalidate_icache(); + enable_icache(); } diff --git a/arch/microblaze/kernel/setup.c b/arch/microblaze/kernel/setup.c index cd68e988b48..f974ec7aa35 100644 --- a/arch/microblaze/kernel/setup.c +++ b/arch/microblaze/kernel/setup.c @@ -57,12 +57,6 @@ void __init setup_arch(char **cmdline_p) microblaze_cache_init(); - invalidate_dcache(); - enable_dcache(); - - invalidate_icache(); - enable_icache(); - setup_memory(); xilinx_pci_init(); -- cgit v1.2.3 From d79f3b06a9e40b382bd5d5ae8dea9b3210eda9ce Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 8 Feb 2010 12:13:10 +0100 Subject: microblaze: Preliminary support for dma drivers I found several problems for ll_temac driver and on system with WB. This early fix should fix it. I will clean this patch before I will add it to mainline Signed-off-by: Michal Simek --- arch/microblaze/include/asm/io.h | 3 --- arch/microblaze/include/asm/page.h | 12 +++++------- arch/microblaze/kernel/dma.c | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 16 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/io.h b/arch/microblaze/include/asm/io.h index 06d804b15a5..32d621a56ae 100644 --- a/arch/microblaze/include/asm/io.h +++ b/arch/microblaze/include/asm/io.h @@ -140,9 +140,6 @@ static inline void writel(unsigned int v, volatile void __iomem *addr) #define virt_to_phys(addr) ((unsigned long)__virt_to_phys(addr)) #define virt_to_bus(addr) ((unsigned long)__virt_to_phys(addr)) -#define __page_address(page) \ - (PAGE_OFFSET + (((page) - mem_map) << PAGE_SHIFT)) -#define page_to_phys(page) virt_to_phys((void *)__page_address(page)) #define page_to_bus(page) (page_to_phys(page)) #define bus_to_virt(addr) (phys_to_virt(addr)) diff --git a/arch/microblaze/include/asm/page.h b/arch/microblaze/include/asm/page.h index 9b66c0fa9a3..2dd1d04129e 100644 --- a/arch/microblaze/include/asm/page.h +++ b/arch/microblaze/include/asm/page.h @@ -61,12 +61,6 @@ extern unsigned int __page_offset; */ #define PAGE_OFFSET CONFIG_KERNEL_START -/* - * MAP_NR -- given an address, calculate the index of the page struct which - * points to the address's page. - */ -#define MAP_NR(addr) (((unsigned long)(addr) - PAGE_OFFSET) >> PAGE_SHIFT) - /* * The basic type of a PTE - 32 bit physical addressing. */ @@ -154,7 +148,11 @@ extern int page_is_ram(unsigned long pfn); # define pfn_to_virt(pfn) __va(pfn_to_phys((pfn))) # ifdef CONFIG_MMU -# define virt_to_page(kaddr) (mem_map + MAP_NR(kaddr)) + +# define virt_to_page(kaddr) (pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)) +# define page_to_virt(page) __va(page_to_pfn(page) << PAGE_SHIFT) +# define page_to_phys(page) (page_to_pfn(page) << PAGE_SHIFT) + # else /* CONFIG_MMU */ # define virt_to_page(vaddr) (pfn_to_page(virt_to_pfn(vaddr))) # define page_to_virt(page) (pfn_to_virt(page_to_pfn(page))) diff --git a/arch/microblaze/kernel/dma.c b/arch/microblaze/kernel/dma.c index 64bc39f40ba..f230a8de0bc 100644 --- a/arch/microblaze/kernel/dma.c +++ b/arch/microblaze/kernel/dma.c @@ -21,10 +21,10 @@ * default the offset is PCI_DRAM_OFFSET. */ -static inline void __dma_sync_page(void *vaddr, unsigned long offset, +static inline void __dma_sync_page(void *paddr, unsigned long offset, size_t size, enum dma_data_direction direction) { - unsigned long start = virt_to_phys(vaddr); + unsigned long start = (unsigned long)paddr; switch (direction) { case DMA_TO_DEVICE: @@ -79,10 +79,12 @@ static int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, struct scatterlist *sg; int i; + /* FIXME this part of code is untested */ for_each_sg(sgl, sg, nents, i) { sg->dma_address = sg_phys(sg) + get_dma_direct_offset(dev); sg->dma_length = sg->length; - __dma_sync_page(sg_page(sg), sg->offset, sg->length, direction); + __dma_sync_page(page_to_phys(sg_page(sg)), sg->offset, + sg->length, direction); } return nents; @@ -107,7 +109,7 @@ static inline dma_addr_t dma_direct_map_page(struct device *dev, struct dma_attrs *attrs) { BUG_ON(direction == DMA_NONE); - __dma_sync_page(page, offset, size, direction); + __dma_sync_page(page_to_phys(page), offset, size, direction); return page_to_phys(page) + offset + get_dma_direct_offset(dev); } @@ -117,8 +119,12 @@ static inline void dma_direct_unmap_page(struct device *dev, enum dma_data_direction direction, struct dma_attrs *attrs) { -/* There is not necessary to do cache cleanup */ - /* __dma_sync_page(dma_address, 0 , size, direction); */ +/* There is not necessary to do cache cleanup + * + * phys_to_virt is here because in __dma_sync_page is __virt_to_phys and + * dma_address is physical address + */ + __dma_sync_page((void *)dma_address, 0 , size, direction); } struct dma_map_ops dma_direct_ops = { -- cgit v1.2.3 From 137d0795a72786fa33e6900cb2ac2eae81f4b6ee Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 4 Feb 2010 11:42:24 +0100 Subject: microblaze: Change temp register for cmdline For copy was used r7 register when CONFIG_CMDLINE_BOOL option is enabled. But r7 stores pointer to fdt that's why machine_early_init not detect compiled-in DTB. I also moved kernel PID setup to have TLB init in one block Signed-off-by: Michal Simek --- arch/microblaze/kernel/head.S | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/head.S b/arch/microblaze/kernel/head.S index 30916193fcc..cb7815cfe5a 100644 --- a/arch/microblaze/kernel/head.S +++ b/arch/microblaze/kernel/head.S @@ -99,8 +99,8 @@ no_fdt_arg: tophys(r4,r4) /* convert to phys address */ ori r3, r0, COMMAND_LINE_SIZE - 1 /* number of loops */ _copy_command_line: - lbu r7, r5, r6 /* r7=r5+r6 - r5 contain pointer to command line */ - sb r7, r4, r6 /* addr[r4+r6]= r7*/ + lbu r2, r5, r6 /* r7=r5+r6 - r5 contain pointer to command line */ + sb r2, r4, r6 /* addr[r4+r6]= r7*/ addik r6, r6, 1 /* increment counting */ bgtid r3, _copy_command_line /* loop for all entries */ addik r3, r3, -1 /* descrement loop */ @@ -136,6 +136,11 @@ _invalidate: addik r3, r3, -1 /* sync */ + /* Setup the kernel PID */ + mts rpid,r0 /* Load the kernel PID */ + nop + bri 4 + /* * We should still be executing code at physical address area * RAM_BASEADDR at this point. However, kernel code is at @@ -146,10 +151,6 @@ _invalidate: addik r3,r0, CONFIG_KERNEL_START /* Load the kernel virtual address */ tophys(r4,r3) /* Load the kernel physical address */ - mts rpid,r0 /* Load the kernel PID */ - nop - bri 4 - /* * Configure and load two entries into TLB slots 0 and 1. * In case we are pinning TLBs, these are reserved in by the -- cgit v1.2.3 From e786c6ad2ba7f1f9b9cc9d75d5e7ba7d9cdc550a Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 8 Feb 2010 14:48:16 +0100 Subject: microblaze: Use generic show_mem() Remove arch-specific show_mem() in favor of the generic version. It is based on powerpc patch. bda2fa535564ace56a395d5b65c6dc81305401fa Signed-off-by: Michal Simek --- arch/microblaze/mm/init.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/mm/init.c b/arch/microblaze/mm/init.c index 6eea5544ad8..aa6e163411b 100644 --- a/arch/microblaze/mm/init.c +++ b/arch/microblaze/mm/init.c @@ -193,12 +193,6 @@ void free_initmem(void) (unsigned long)(&__init_end)); } -/* FIXME from arch/powerpc/mm/mem.c*/ -void show_mem(void) -{ - printk(KERN_NOTICE "%s\n", __func__); -} - void __init mem_init(void) { high_memory = (void *)__va(memory_end); -- cgit v1.2.3 From 4c912c1a33abb67aefecb5ed8bd73d91887c4977 Mon Sep 17 00:00:00 2001 From: Frans Pop Date: Sat, 6 Feb 2010 18:47:12 +0100 Subject: microblaze: remove trailing space in messages Signed-off-by: Frans Pop Cc: microblaze-uclinux@itee.uq.edu.au Cc: Michal Simek Signed-off-by: Michal Simek --- arch/microblaze/kernel/cpu/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/cpu/cache.c b/arch/microblaze/kernel/cpu/cache.c index 5425f89335e..13f0c1de323 100644 --- a/arch/microblaze/kernel/cpu/cache.c +++ b/arch/microblaze/kernel/cpu/cache.c @@ -493,7 +493,7 @@ const struct scache wt_nomsr_noirq = { #define CPUVER_7_20_A 0x0c #define CPUVER_7_20_D 0x0f -#define INFO(s) printk(KERN_INFO "cache: " s " \n"); +#define INFO(s) printk(KERN_INFO "cache: " s "\n"); void microblaze_cache_init(void) { -- cgit v1.2.3 From dcbae4be907488df5e1cc8a89b7df1a0565c257c Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Tue, 9 Feb 2010 09:25:08 +0100 Subject: microblaze: Preliminary support for dma drivers I found several problems for ll_temac driver and on system with WB. This early fix should fix it. I will clean this patch before I will add it to mainline Signed-off-by: Michal Simek --- arch/microblaze/kernel/dma.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/dma.c b/arch/microblaze/kernel/dma.c index f230a8de0bc..fbe1e8184ef 100644 --- a/arch/microblaze/kernel/dma.c +++ b/arch/microblaze/kernel/dma.c @@ -20,18 +20,15 @@ * can set archdata.dma_data to an unsigned long holding the offset. By * default the offset is PCI_DRAM_OFFSET. */ - -static inline void __dma_sync_page(void *paddr, unsigned long offset, +static inline void __dma_sync_page(unsigned long paddr, unsigned long offset, size_t size, enum dma_data_direction direction) { - unsigned long start = (unsigned long)paddr; - switch (direction) { case DMA_TO_DEVICE: - flush_dcache_range(start + offset, start + offset + size); + flush_dcache_range(paddr + offset, paddr + offset + size); break; case DMA_FROM_DEVICE: - invalidate_dcache_range(start + offset, start + offset + size); + invalidate_dcache_range(paddr + offset, paddr + offset + size); break; default: BUG(); -- cgit v1.2.3 From 22607a28213068af113b46862eafa785f00a482e Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 15 Feb 2010 16:41:40 +0100 Subject: microblaze: Add define for ASM_LOOP It is default option but both options must be measured. Signed-off-by: Michal Simek --- arch/microblaze/kernel/cpu/cache.c | 204 +++++++++++++++++++++++++++++-------- 1 file changed, 161 insertions(+), 43 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/cpu/cache.c b/arch/microblaze/kernel/cpu/cache.c index 13f0c1de323..f04d8a86dea 100644 --- a/arch/microblaze/kernel/cpu/cache.c +++ b/arch/microblaze/kernel/cpu/cache.c @@ -15,25 +15,6 @@ #include #include -static inline void __invalidate_flush_icache(unsigned int addr) -{ - __asm__ __volatile__ ("wic %0, r0;" \ - : : "r" (addr)); -} - -static inline void __flush_dcache(unsigned int addr) -{ - __asm__ __volatile__ ("wdc.flush %0, r0;" \ - : : "r" (addr)); -} - -static inline void __invalidate_dcache(unsigned int baseaddr, - unsigned int offset) -{ - __asm__ __volatile__ ("wdc.clear %0, %1;" \ - : : "r" (baseaddr), "r" (offset)); -} - static inline void __enable_icache_msr(void) { __asm__ __volatile__ (" msrset r0, %0; \ @@ -148,9 +129,9 @@ do { \ int step = -line_length; \ BUG_ON(step >= 0); \ \ - __asm__ __volatile__ (" 1: " #op " r0, %0; \ - bgtid %0, 1b; \ - addk %0, %0, %1; \ + __asm__ __volatile__ (" 1: " #op " r0, %0; \ + bgtid %0, 1b; \ + addk %0, %0, %1; \ " : : "r" (len), "r" (step) \ : "memory"); \ } while (0); @@ -162,9 +143,9 @@ do { \ int count = end - start; \ BUG_ON(count <= 0); \ \ - __asm__ __volatile__ (" 1: " #op " %0, %1; \ - bgtid %1, 1b; \ - addk %1, %1, %2; \ + __asm__ __volatile__ (" 1: " #op " %0, %1; \ + bgtid %1, 1b; \ + addk %1, %1, %2; \ " : : "r" (start), "r" (count), \ "r" (step) : "memory"); \ } while (0); @@ -175,7 +156,7 @@ do { \ int volatile temp; \ BUG_ON(end - start <= 0); \ \ - __asm__ __volatile__ (" 1: " #op " %1, r0; \ + __asm__ __volatile__ (" 1: " #op " %1, r0; \ cmpu %0, %1, %2; \ bgtid %0, 1b; \ addk %1, %1, %3; \ @@ -183,10 +164,14 @@ do { \ "r" (line_length) : "memory"); \ } while (0); +#define ASM_LOOP + static void __flush_icache_range_msr_irq(unsigned long start, unsigned long end) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); @@ -196,8 +181,13 @@ static void __flush_icache_range_msr_irq(unsigned long start, unsigned long end) local_irq_save(flags); __disable_icache_msr(); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_1(start, end, cpuinfo.icache_line_length, wic); - +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wic %0, r0;" \ + : : "r" (i)); +#endif __enable_icache_msr(); local_irq_restore(flags); } @@ -206,7 +196,9 @@ static void __flush_icache_range_nomsr_irq(unsigned long start, unsigned long end) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); @@ -216,7 +208,13 @@ static void __flush_icache_range_nomsr_irq(unsigned long start, local_irq_save(flags); __disable_icache_nomsr(); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_1(start, end, cpuinfo.icache_line_length, wic); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wic %0, r0;" \ + : : "r" (i)); +#endif __enable_icache_nomsr(); local_irq_restore(flags); @@ -225,25 +223,41 @@ static void __flush_icache_range_nomsr_irq(unsigned long start, static void __flush_icache_range_noirq(unsigned long start, unsigned long end) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); CACHE_LOOP_LIMITS(start, end, cpuinfo.icache_line_length, cpuinfo.icache_size); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_1(start, end, cpuinfo.icache_line_length, wic); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wic %0, r0;" \ + : : "r" (i)); +#endif } static void __flush_icache_all_msr_irq(void) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); local_irq_save(flags); __disable_icache_msr(); - +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.icache_size, cpuinfo.icache_line_length, wic); - +#else + for (i = 0; i < cpuinfo.icache_size; + i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wic %0, r0;" \ + : : "r" (i)); +#endif __enable_icache_msr(); local_irq_restore(flags); } @@ -251,35 +265,59 @@ static void __flush_icache_all_msr_irq(void) static void __flush_icache_all_nomsr_irq(void) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); local_irq_save(flags); __disable_icache_nomsr(); - +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.icache_size, cpuinfo.icache_line_length, wic); - +#else + for (i = 0; i < cpuinfo.icache_size; + i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wic %0, r0;" \ + : : "r" (i)); +#endif __enable_icache_nomsr(); local_irq_restore(flags); } static void __flush_icache_all_noirq(void) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.icache_size, cpuinfo.icache_line_length, wic); +#else + for (i = 0; i < cpuinfo.icache_size; + i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wic %0, r0;" \ + : : "r" (i)); +#endif } static void __invalidate_dcache_all_msr_irq(void) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); local_irq_save(flags); __disable_dcache_msr(); - +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc); - +#else + for (i = 0; i < cpuinfo.dcache_size; + i += cpuinfo.dcache_line_length) + __asm__ __volatile__ ("wdc %0, r0;" \ + : : "r" (i)); +#endif __enable_dcache_msr(); local_irq_restore(flags); } @@ -287,60 +325,107 @@ static void __invalidate_dcache_all_msr_irq(void) static void __invalidate_dcache_all_nomsr_irq(void) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); local_irq_save(flags); __disable_dcache_nomsr(); - +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc); - +#else + for (i = 0; i < cpuinfo.dcache_size; + i += cpuinfo.dcache_line_length) + __asm__ __volatile__ ("wdc %0, r0;" \ + : : "r" (i)); +#endif __enable_dcache_nomsr(); local_irq_restore(flags); } static void __invalidate_dcache_all_noirq_wt(void) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc) +#else + for (i = 0; i < cpuinfo.dcache_size; + i += cpuinfo.dcache_line_length) + __asm__ __volatile__ ("wdc %0, r0;" \ + : : "r" (i)); +#endif } /* FIXME this is weird - should be only wdc but not work * MS: I am getting bus errors and other weird things */ static void __invalidate_dcache_all_wb(void) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); +#ifdef ASM_LOOP CACHE_ALL_LOOP2(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc.clear) +#else + for (i = 0; i < cpuinfo.dcache_size; + i += cpuinfo.dcache_line_length) + __asm__ __volatile__ ("wdc.clear %0, r0;" \ + : : "r" (i)); +#endif } static void __invalidate_dcache_range_wb(unsigned long start, unsigned long end) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); CACHE_LOOP_LIMITS(start, end, cpuinfo.dcache_line_length, cpuinfo.dcache_size); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.clear); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wdc.clear %0, r0;" \ + : : "r" (i)); +#endif } static void __invalidate_dcache_range_nomsr_wt(unsigned long start, unsigned long end) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); CACHE_LOOP_LIMITS(start, end, cpuinfo.dcache_line_length, cpuinfo.dcache_size); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wdc %0, r0;" \ + : : "r" (i)); +#endif } static void __invalidate_dcache_range_msr_irq_wt(unsigned long start, unsigned long end) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); CACHE_LOOP_LIMITS(start, end, @@ -349,7 +434,13 @@ static void __invalidate_dcache_range_msr_irq_wt(unsigned long start, local_irq_save(flags); __disable_dcache_msr(); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wdc %0, r0;" \ + : : "r" (i)); +#endif __enable_dcache_msr(); local_irq_restore(flags); @@ -359,7 +450,9 @@ static void __invalidate_dcache_range_nomsr_irq(unsigned long start, unsigned long end) { unsigned long flags; - +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); @@ -369,7 +462,13 @@ static void __invalidate_dcache_range_nomsr_irq(unsigned long start, local_irq_save(flags); __disable_dcache_nomsr(); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wdc %0, r0;" \ + : : "r" (i)); +#endif __enable_dcache_nomsr(); local_irq_restore(flags); @@ -377,19 +476,38 @@ static void __invalidate_dcache_range_nomsr_irq(unsigned long start, static void __flush_dcache_all_wb(void) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s\n", __func__); +#ifdef ASM_LOOP CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc.flush); +#else + for (i = 0; i < cpuinfo.dcache_size; + i += cpuinfo.dcache_line_length) + __asm__ __volatile__ ("wdc.flush %0, r0;" \ + : : "r" (i)); +#endif } static void __flush_dcache_range_wb(unsigned long start, unsigned long end) { +#ifndef ASM_LOOP + int i; +#endif pr_debug("%s: start 0x%x, end 0x%x\n", __func__, (unsigned int)start, (unsigned int) end); CACHE_LOOP_LIMITS(start, end, cpuinfo.dcache_line_length, cpuinfo.dcache_size); +#ifdef ASM_LOOP CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.flush); +#else + for (i = start; i < end; i += cpuinfo.icache_line_length) + __asm__ __volatile__ ("wdc.flush %0, r0;" \ + : : "r" (i)); +#endif } /* struct for wb caches and for wt caches */ -- cgit v1.2.3 From cca5613f0278fb0ae0aba285a496add55d0cabab Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 22 Feb 2010 11:27:27 +0100 Subject: microblaze: Remove VMALLOC_VMADDR Signed-off-by: Michal Simek --- arch/microblaze/include/asm/pgtable.h | 1 - arch/microblaze/mm/pgtable.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/pgtable.h b/arch/microblaze/include/asm/pgtable.h index 1c47f6f8bfb..e8d25fb8847 100644 --- a/arch/microblaze/include/asm/pgtable.h +++ b/arch/microblaze/include/asm/pgtable.h @@ -85,7 +85,6 @@ static inline pte_t pte_mkspecial(pte_t pte) { return pte; } #define VMALLOC_START (CONFIG_KERNEL_START + \ max(32 * 1024 * 1024UL, memory_size)) #define VMALLOC_END ioremap_bot -#define VMALLOC_VMADDR(x) ((unsigned long)(x)) #endif /* __ASSEMBLY__ */ diff --git a/arch/microblaze/mm/pgtable.c b/arch/microblaze/mm/pgtable.c index 2820081b21a..63a6fd07c48 100644 --- a/arch/microblaze/mm/pgtable.c +++ b/arch/microblaze/mm/pgtable.c @@ -103,7 +103,7 @@ static void __iomem *__ioremap(phys_addr_t addr, unsigned long size, area = get_vm_area(size, VM_IOREMAP); if (area == NULL) return NULL; - v = VMALLOC_VMADDR(area->addr); + v = (unsigned long) area->addr; } else { v = (ioremap_bot -= size); } -- cgit v1.2.3 From b8a84059b5c3a3b9ba3973dcdab1e1d9cc4975e0 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 22 Feb 2010 11:33:07 +0100 Subject: microblaze: Remove ancient Kconfig option for consistent mapping We don't use CONSISTENT option from Kconfig that's why I am removing them. Signed-off-by: Michal Simek --- arch/microblaze/Kconfig | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index c1e731bb330..e451c0898e7 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -145,7 +145,6 @@ menu "Advanced setup" config ADVANCED_OPTIONS bool "Prompt for advanced kernel configuration options" - depends on MMU help This option will enable prompting for a variety of advanced kernel configuration options. These options can cause the kernel to not @@ -174,7 +173,7 @@ config HIGHMEM_START config LOWMEM_SIZE_BOOL bool "Set maximum low memory" - depends on ADVANCED_OPTIONS + depends on ADVANCED_OPTIONS && MMU help This option allows you to set the maximum amount of memory which will be used as "low memory", that is, memory which the kernel can @@ -186,7 +185,6 @@ config LOWMEM_SIZE_BOOL config LOWMEM_SIZE hex "Maximum low memory size (in bytes)" if LOWMEM_SIZE_BOOL - depends on MMU default "0x30000000" config KERNEL_START_BOOL @@ -207,7 +205,7 @@ config KERNEL_START config TASK_SIZE_BOOL bool "Set custom user task size" - depends on ADVANCED_OPTIONS + depends on ADVANCED_OPTIONS && MMU help This option allows you to set the amount of virtual address space allocated to user tasks. This can be useful in optimizing the @@ -217,35 +215,8 @@ config TASK_SIZE_BOOL config TASK_SIZE hex "Size of user task space" if TASK_SIZE_BOOL - depends on MMU default "0x80000000" -config CONSISTENT_START_BOOL - bool "Set custom consistent memory pool address" - depends on ADVANCED_OPTIONS && NOT_COHERENT_CACHE - help - This option allows you to set the base virtual address - of the the consistent memory pool. This pool of virtual - memory is used to make consistent memory allocations. - -config CONSISTENT_START - hex "Base virtual address of consistent memory pool" if CONSISTENT_START_BOOL - depends on MMU - default "0xff100000" if NOT_COHERENT_CACHE - -config CONSISTENT_SIZE_BOOL - bool "Set custom consistent memory pool size" - depends on ADVANCED_OPTIONS && NOT_COHERENT_CACHE - help - This option allows you to set the size of the the - consistent memory pool. This pool of virtual memory - is used to make consistent memory allocations. - -config CONSISTENT_SIZE - hex "Size of consistent memory pool" if CONSISTENT_SIZE_BOOL - depends on MMU - default "0x00200000" if NOT_COHERENT_CACHE - endmenu source "mm/Kconfig" -- cgit v1.2.3 From ae8ee1505162f47f8b8cf7a44c26ea6b172e1445 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 22 Feb 2010 12:09:02 +0100 Subject: microblaze: pgtable.h: move consistent functions Consistent functions will be used for noMMU and MMU kernels. Signed-off-by: Michal Simek --- arch/microblaze/include/asm/pgtable.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/pgtable.h b/arch/microblaze/include/asm/pgtable.h index e8d25fb8847..adcc3320e52 100644 --- a/arch/microblaze/include/asm/pgtable.h +++ b/arch/microblaze/include/asm/pgtable.h @@ -68,7 +68,6 @@ static inline int pte_file(pte_t pte) { return 0; } extern unsigned long va_to_phys(unsigned long address); extern pte_t *va_to_pte(unsigned long address); -extern unsigned long ioremap_bot, ioremap_base; /* * The following only work if pte_present() is true. @@ -411,7 +410,7 @@ static inline unsigned long pte_update(pte_t *p, unsigned long clr, mts rmsr, %2\n\ nop" : "=&r" (old), "=&r" (tmp), "=&r" (msr), "=m" (*p) - : "r" ((unsigned long)(p+1) - 4), "r" (clr), "r" (set), "m" (*p) + : "r" ((unsigned long)(p + 1) - 4), "r" (clr), "r" (set), "m" (*p) : "cc"); return old; @@ -580,18 +579,11 @@ void mapin_ram(void); int map_page(unsigned long va, phys_addr_t pa, int flags); extern int mem_init_done; -extern unsigned long ioremap_base; -extern unsigned long ioremap_bot; asmlinkage void __init mmu_init(void); void __init *early_get_page(void); -void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle); -void consistent_free(void *vaddr); -void consistent_sync(void *vaddr, size_t size, int direction); -void consistent_sync_page(struct page *page, unsigned long offset, - size_t size, int direction); #endif /* __ASSEMBLY__ */ #endif /* __KERNEL__ */ @@ -600,6 +592,14 @@ void consistent_sync_page(struct page *page, unsigned long offset, #ifndef __ASSEMBLY__ #include +extern unsigned long ioremap_bot, ioremap_base; + +void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle); +void consistent_free(void *vaddr); +void consistent_sync(void *vaddr, size_t size, int direction); +void consistent_sync_page(struct page *page, unsigned long offset, + size_t size, int direction); + void setup_memory(void); #endif /* __ASSEMBLY__ */ -- cgit v1.2.3 From 3a0d7a4dd5b3a6545e5764735b48ab84e64af723 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 22 Feb 2010 12:16:08 +0100 Subject: microblaze: Add consistent code Remove ancient Kconfig option for consistent code. MMU uses cache inhibit pages. noMMU uses UNCACHE SHADOW feature where is used double ram size. For example: Physical ram is 256MB and cache are setup to cover the same size. But if you setup in HW that size is 512MB and cache covers 256MB than you can use adresses from 256-512MB without caches and correspond with 0-256MB with cache. That's why I am using dcache base/high addresses to find out uncache area. Signed-off-by: Michal Simek --- arch/microblaze/Kconfig | 9 ++ arch/microblaze/mm/Makefile | 2 +- arch/microblaze/mm/consistent.c | 246 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 arch/microblaze/mm/consistent.c (limited to 'arch') diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index e451c0898e7..203ec61c6d4 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -156,6 +156,15 @@ config ADVANCED_OPTIONS comment "Default settings for advanced configuration options are used" depends on !ADVANCED_OPTIONS +config XILINX_UNCACHED_SHADOW + bool "Are you using uncached shadow for RAM ?" + depends on ADVANCED_OPTIONS && !MMU + default n + help + This is needed to be able to allocate uncachable memory regions. + The feature requires the design to define the RAM memory controller + window to be twice as large as the actual physical memory. + config HIGHMEM_START_BOOL bool "Set high memory pool address" depends on ADVANCED_OPTIONS && HIGHMEM diff --git a/arch/microblaze/mm/Makefile b/arch/microblaze/mm/Makefile index 6c8a924d9e2..09c49ed8723 100644 --- a/arch/microblaze/mm/Makefile +++ b/arch/microblaze/mm/Makefile @@ -2,6 +2,6 @@ # Makefile # -obj-y := init.o +obj-y := consistent.o init.o obj-$(CONFIG_MMU) += pgtable.o mmu_context.o fault.o diff --git a/arch/microblaze/mm/consistent.c b/arch/microblaze/mm/consistent.c new file mode 100644 index 00000000000..a9b443e3fb9 --- /dev/null +++ b/arch/microblaze/mm/consistent.c @@ -0,0 +1,246 @@ +/* + * Microblaze support for cache consistent memory. + * Copyright (C) 2010 Michal Simek + * Copyright (C) 2010 PetaLogix + * Copyright (C) 2005 John Williams + * + * Based on PowerPC version derived from arch/arm/mm/consistent.c + * Copyright (C) 2001 Dan Malek (dmalek@jlc.net) + * Copyright (C) 2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CONFIG_MMU + +/* I have to use dcache values because I can't relate on ram size */ +#define UNCACHED_SHADOW_MASK (cpuinfo.dcache_high - cpuinfo.dcache_base + 1) + +/* + * Consistent memory allocators. Used for DMA devices that want to + * share uncached memory with the processor core. + * My crufty no-MMU approach is simple. In the HW platform we can optionally + * mirror the DDR up above the processor cacheable region. So, memory accessed + * in this mirror region will not be cached. It's alloced from the same + * pool as normal memory, but the handle we return is shifted up into the + * uncached region. This will no doubt cause big problems if memory allocated + * here is not also freed properly. -- JW + */ +void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle) +{ + struct page *page, *end, *free; + unsigned long order; + void *ret, *virt; + + if (in_interrupt()) + BUG(); + + size = PAGE_ALIGN(size); + order = get_order(size); + + page = alloc_pages(gfp, order); + if (!page) + goto no_page; + + /* We could do with a page_to_phys and page_to_bus here. */ + virt = page_address(page); + ret = ioremap(virt_to_phys(virt), size); + if (!ret) + goto no_remap; + + /* + * Here's the magic! Note if the uncached shadow is not implemented, + * it's up to the calling code to also test that condition and make + * other arranegments, such as manually flushing the cache and so on. + */ +#ifdef CONFIG_XILINX_UNCACHED_SHADOW + ret = (void *)((unsigned) ret | UNCACHED_SHADOW_MASK); +#endif + /* dma_handle is same as physical (shadowed) address */ + *dma_handle = (dma_addr_t)ret; + + /* + * free wasted pages. We skip the first page since we know + * that it will have count = 1 and won't require freeing. + * We also mark the pages in use as reserved so that + * remap_page_range works. + */ + page = virt_to_page(virt); + free = page + (size >> PAGE_SHIFT); + end = page + (1 << order); + + for (; page < end; page++) { + init_page_count(page); + if (page >= free) + __free_page(page); + else + SetPageReserved(page); + } + + return ret; +no_remap: + __free_pages(page, order); +no_page: + return NULL; +} + +#else + +void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle) +{ + int order, err, i; + unsigned long page, va, flags; + phys_addr_t pa; + struct vm_struct *area; + void *ret; + + if (in_interrupt()) + BUG(); + + /* Only allocate page size areas. */ + size = PAGE_ALIGN(size); + order = get_order(size); + + page = __get_free_pages(gfp, order); + if (!page) { + BUG(); + return NULL; + } + + /* + * we need to ensure that there are no cachelines in use, + * or worse dirty in this area. + */ + flush_dcache_range(virt_to_phys(page), virt_to_phys(page) + size); + + /* Allocate some common virtual space to map the new pages. */ + area = get_vm_area(size, VM_ALLOC); + if (area == NULL) { + free_pages(page, order); + return NULL; + } + va = (unsigned long) area->addr; + ret = (void *)va; + + /* This gives us the real physical address of the first page. */ + *dma_handle = pa = virt_to_bus((void *)page); + + /* MS: This is the whole magic - use cache inhibit pages */ + flags = _PAGE_KERNEL | _PAGE_NO_CACHE; + + /* + * Set refcount=1 on all pages in an order>0 + * allocation so that vfree() will actually + * free all pages that were allocated. + */ + if (order > 0) { + struct page *rpage = virt_to_page(page); + for (i = 1; i < (1 << order); i++) + init_page_count(rpage+i); + } + + err = 0; + for (i = 0; i < size && err == 0; i += PAGE_SIZE) + err = map_page(va+i, pa+i, flags); + + if (err) { + vfree((void *)va); + return NULL; + } + + return ret; +} +#endif /* CONFIG_MMU */ +EXPORT_SYMBOL(consistent_alloc); + +/* + * free page(s) as defined by the above mapping. + */ +void consistent_free(void *vaddr) +{ + if (in_interrupt()) + BUG(); + + /* Clear SHADOW_MASK bit in address, and free as per usual */ +#ifdef CONFIG_XILINX_UNCACHED_SHADOW + vaddr = (void *)((unsigned)vaddr & ~UNCACHED_SHADOW_MASK); +#endif + vfree(vaddr); +} +EXPORT_SYMBOL(consistent_free); + +/* + * make an area consistent. + */ +void consistent_sync(void *vaddr, size_t size, int direction) +{ + unsigned long start; + unsigned long end; + + start = (unsigned long)vaddr; + + /* Convert start address back down to unshadowed memory region */ +#ifdef CONFIG_XILINX_UNCACHED_SHADOW + start &= ~UNCACHED_SHADOW_MASK; +#endif + end = start + size; + + switch (direction) { + case PCI_DMA_NONE: + BUG(); + case PCI_DMA_FROMDEVICE: /* invalidate only */ + flush_dcache_range(start, end); + break; + case PCI_DMA_TODEVICE: /* writeback only */ + flush_dcache_range(start, end); + break; + case PCI_DMA_BIDIRECTIONAL: /* writeback and invalidate */ + flush_dcache_range(start, end); + break; + } +} +EXPORT_SYMBOL(consistent_sync); + +/* + * consistent_sync_page makes memory consistent. identical + * to consistent_sync, but takes a struct page instead of a + * virtual address + */ +void consistent_sync_page(struct page *page, unsigned long offset, + size_t size, int direction) +{ + unsigned long start = (unsigned long)page_address(page) + offset; + consistent_sync((void *)start, size, direction); +} +EXPORT_SYMBOL(consistent_sync_page); -- cgit v1.2.3 From 1be53e084a5bd8f59850348e1066d25aa0200031 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 11 Mar 2010 14:15:48 +0100 Subject: microblaze: Fix dma alloc and free coherent dma functions We have to use consistent code to be able to do coherent dma function. In consistent code is used cache inhibit page mapping. Xilinx reported that there is bug in Microblaze for WB and d-cache_always use option. Microblaze 7.30.a should be first version where is this bug removed. Signed-off-by: Michal Simek --- arch/microblaze/kernel/dma.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/dma.c b/arch/microblaze/kernel/dma.c index fbe1e8184ef..b1084974fcc 100644 --- a/arch/microblaze/kernel/dma.c +++ b/arch/microblaze/kernel/dma.c @@ -43,9 +43,14 @@ static unsigned long get_dma_direct_offset(struct device *dev) return PCI_DRAM_OFFSET; /* FIXME Not sure if is correct */ } -void *dma_direct_alloc_coherent(struct device *dev, size_t size, +#define NOT_COHERENT_CACHE + +static void *dma_direct_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag) { +#ifdef NOT_COHERENT_CACHE + return consistent_alloc(flag, size, dma_handle); +#else void *ret; struct page *page; int node = dev_to_node(dev); @@ -61,12 +66,17 @@ void *dma_direct_alloc_coherent(struct device *dev, size_t size, *dma_handle = virt_to_phys(ret) + get_dma_direct_offset(dev); return ret; +#endif } -void dma_direct_free_coherent(struct device *dev, size_t size, +static void dma_direct_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle) { +#ifdef NOT_COHERENT_CACHE + consistent_free(vaddr); +#else free_pages((unsigned long)vaddr, get_order(size)); +#endif } static int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, @@ -105,7 +115,6 @@ static inline dma_addr_t dma_direct_map_page(struct device *dev, enum dma_data_direction direction, struct dma_attrs *attrs) { - BUG_ON(direction == DMA_NONE); __dma_sync_page(page_to_phys(page), offset, size, direction); return page_to_phys(page) + offset + get_dma_direct_offset(dev); } @@ -121,7 +130,7 @@ static inline void dma_direct_unmap_page(struct device *dev, * phys_to_virt is here because in __dma_sync_page is __virt_to_phys and * dma_address is physical address */ - __dma_sync_page((void *)dma_address, 0 , size, direction); + __dma_sync_page(dma_address, 0 , size, direction); } struct dma_map_ops dma_direct_ops = { -- cgit v1.2.3 From 79bf3a137617e6deeac411c39f1660b7e91d6348 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 20 Jan 2010 15:17:08 +0100 Subject: microblaze: PCI early support for noMMU system Signed-off-by: Michal Simek --- arch/microblaze/include/asm/pgalloc.h | 2 +- arch/microblaze/include/asm/pgtable.h | 6 ++++++ arch/microblaze/mm/init.c | 14 +++++++++----- arch/microblaze/pci/pci-common.c | 2 ++ 4 files changed, 18 insertions(+), 6 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/include/asm/pgalloc.h b/arch/microblaze/include/asm/pgalloc.h index 7547f506456..f44b0d696fe 100644 --- a/arch/microblaze/include/asm/pgalloc.h +++ b/arch/microblaze/include/asm/pgalloc.h @@ -19,6 +19,7 @@ #include #include #include +#include #define PGDIR_ORDER 0 @@ -111,7 +112,6 @@ static inline pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) { pte_t *pte; - extern int mem_init_done; extern void *early_get_page(void); if (mem_init_done) { pte = (pte_t *)__get_free_page(GFP_KERNEL | diff --git a/arch/microblaze/include/asm/pgtable.h b/arch/microblaze/include/asm/pgtable.h index adcc3320e52..dd2bb60651c 100644 --- a/arch/microblaze/include/asm/pgtable.h +++ b/arch/microblaze/include/asm/pgtable.h @@ -16,6 +16,10 @@ #define io_remap_pfn_range(vma, vaddr, pfn, size, prot) \ remap_pfn_range(vma, vaddr, pfn, size, prot) +#ifndef __ASSEMBLY__ +extern int mem_init_done; +#endif + #ifndef CONFIG_MMU #define pgd_present(pgd) (1) /* pages are always present on non MMU */ @@ -51,6 +55,8 @@ static inline int pte_file(pte_t pte) { return 0; } #define arch_enter_lazy_cpu_mode() do {} while (0) +#define pgprot_noncached_wc(prot) prot + #else /* CONFIG_MMU */ #include diff --git a/arch/microblaze/mm/init.c b/arch/microblaze/mm/init.c index aa6e163411b..1608e2e1a44 100644 --- a/arch/microblaze/mm/init.c +++ b/arch/microblaze/mm/init.c @@ -23,6 +23,9 @@ #include #include +/* Use for MMU and noMMU because of PCI generic code */ +int mem_init_done; + #ifndef CONFIG_MMU unsigned int __page_offset; EXPORT_SYMBOL(__page_offset); @@ -30,7 +33,6 @@ EXPORT_SYMBOL(__page_offset); #else DEFINE_PER_CPU(struct mmu_gather, mmu_gathers); -int mem_init_done; static int init_bootmem_done; #endif /* CONFIG_MMU */ @@ -202,9 +204,7 @@ void __init mem_init(void) printk(KERN_INFO "Memory: %luk/%luk available\n", nr_free_pages() << (PAGE_SHIFT-10), num_physpages << (PAGE_SHIFT-10)); -#ifdef CONFIG_MMU mem_init_done = 1; -#endif } #ifndef CONFIG_MMU @@ -216,6 +216,10 @@ int ___range_ok(unsigned long addr, unsigned long size) } EXPORT_SYMBOL(___range_ok); +int page_is_ram(unsigned long pfn) +{ + return __range_ok(pfn, 0); +} #else int page_is_ram(unsigned long pfn) { @@ -344,6 +348,8 @@ void __init *early_get_page(void) return p; } +#endif /* CONFIG_MMU */ + void * __init_refok alloc_maybe_bootmem(size_t size, gfp_t mask) { if (mem_init_done) @@ -365,5 +371,3 @@ void * __init_refok zalloc_maybe_bootmem(size_t size, gfp_t mask) } return p; } - -#endif /* CONFIG_MMU */ diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c index f03f8be2740..0be34350d73 100644 --- a/arch/microblaze/pci/pci-common.c +++ b/arch/microblaze/pci/pci-common.c @@ -561,11 +561,13 @@ int pci_mmap_legacy_page_range(struct pci_bus *bus, * memory, effectively behaving just like /dev/zero */ if ((offset + size) > hose->isa_mem_size) { +#ifdef CONFIG_MMU printk(KERN_DEBUG "Process %s (pid:%d) mapped non-existing PCI" "legacy memory for 0%04x:%02x\n", current->comm, current->pid, pci_domain_nr(bus), bus->number); +#endif if (vma->vm_flags & VM_SHARED) return shmem_zero_setup(vma); return 0; -- cgit v1.2.3 From b1d70c62fff3e8b6224699801c610c244882685a Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Fri, 22 Jan 2010 10:24:06 +0100 Subject: microblaze: Simplify entry.S - save/restore r3/r4 - ret_from_trap There is possible to save r3/r4 at the beggining of user part before calling handlers and at the end restore it. Signed-off-by: Michal Simek --- arch/microblaze/kernel/entry.S | 78 ++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 48 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index 1a6729dde49..772fe7415f8 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -305,7 +305,7 @@ C_ENTRY(_user_exception): swi r11, r1, PTO+PT_R1; /* Store user SP. */ addi r11, r0, 1; swi r11, r0, TOPHYS(PER_CPU(KM)); /* Now we're in kernel-mode. */ -2: lwi r31, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* get saved current */ +2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* Save away the syscall number. */ swi r12, r1, PTO+PT_R0; tovirt(r1,r1) @@ -322,8 +322,7 @@ C_ENTRY(_user_exception): rtid r11, 0 nop 3: - add r11, r0, CURRENT_TASK /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO /* get thread info */ + lwi r11, CURRENT_TASK, TS_THREAD_INFO /* get thread info */ lwi r11, r11, TI_FLAGS /* get flags in thread info */ andi r11, r11, _TIF_WORK_SYSCALL_MASK beqi r11, 4f @@ -382,58 +381,50 @@ C_ENTRY(ret_from_trap): /* See if returning to kernel mode, if so, skip resched &c. */ bnei r11, 2f; + swi r3, r1, PTO + PT_R3 + swi r4, r1, PTO + PT_R4 + /* We're returning to user mode, so check for various conditions that * trigger rescheduling. */ - # FIXME: Restructure all these flag checks. - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ + /* FIXME: Restructure all these flag checks. */ + lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_WORK_SYSCALL_MASK beqi r11, 1f - swi r3, r1, PTO + PT_R3 - swi r4, r1, PTO + PT_R4 brlid r15, do_syscall_trace_leave addik r5, r1, PTO + PT_R0 - lwi r3, r1, PTO + PT_R3 - lwi r4, r1, PTO + PT_R4 1: - /* We're returning to user mode, so check for various conditions that * trigger rescheduling. */ - /* Get current task ptr into r11 */ - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ + /* get thread info from current task */ + lwi r11, CURRENT_TASK, TS_THREAD_INFO; lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_NEED_RESCHED; beqi r11, 5f; - swi r3, r1, PTO + PT_R3; /* store syscall result */ - swi r4, r1, PTO + PT_R4; bralid r15, schedule; /* Call scheduler */ nop; /* delay slot */ - lwi r3, r1, PTO + PT_R3; /* restore syscall result */ - lwi r4, r1, PTO + PT_R4; /* Maybe handle a signal */ -5: add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ +5: /* get thread info from current task*/ + lwi r11, CURRENT_TASK, TS_THREAD_INFO; lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_SIGPENDING; beqi r11, 1f; /* Signals to handle, handle them */ - swi r3, r1, PTO + PT_R3; /* store syscall result */ - swi r4, r1, PTO + PT_R4; la r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ add r6, r0, r0; /* Arg 2: sigset_t *oldset */ addi r7, r0, 1; /* Arg 3: int in_syscall */ bralid r15, do_signal; /* Handle any signals */ nop; + +/* Finally, return to user state. */ +1: lwi r3, r1, PTO + PT_R3; /* restore syscall result */ lwi r4, r1, PTO + PT_R4; -/* Finally, return to user state. */ -1: swi r0, r0, PER_CPU(KM); /* Now officially in user state. */ + swi r0, r0, PER_CPU(KM); /* Now officially in user state. */ add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ swi r11, r0, PER_CPU(CURRENT_SAVE); /* save current */ VM_OFF; @@ -565,7 +556,7 @@ C_ENTRY(sys_rt_sigreturn_wrapper): swi r11, r1, PTO+PT_R1; /* Store user SP. */ \ addi r11, r0, 1; \ swi r11, r0, TOPHYS(PER_CPU(KM)); /* Now we're in kernel-mode.*/\ -2: lwi r31, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* get saved current */\ +2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); \ /* Save away the syscall number. */ \ swi r0, r1, PTO+PT_R0; \ tovirt(r1,r1) @@ -673,9 +664,7 @@ C_ENTRY(ret_from_exc): /* We're returning to user mode, so check for various conditions that trigger rescheduling. */ - /* Get current task ptr into r11 */ - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ + lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_NEED_RESCHED; beqi r11, 5f; @@ -685,8 +674,7 @@ C_ENTRY(ret_from_exc): nop; /* delay slot */ /* Maybe handle a signal */ -5: add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ +5: lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_SIGPENDING; beqi r11, 1f; /* Signals to handle, handle them */ @@ -802,7 +790,7 @@ C_ENTRY(_interrupt): swi r11, r0, TOPHYS(PER_CPU(KM)); 2: - lwi r31, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); + lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); swi r0, r1, PTO + PT_R0; tovirt(r1,r1) la r5, r1, PTO; @@ -817,8 +805,7 @@ ret_from_irq: lwi r11, r1, PTO + PT_MODE; bnei r11, 2f; - add r11, r0, CURRENT_TASK; - lwi r11, r11, TS_THREAD_INFO; + lwi r11, CURRENT_TASK, TS_THREAD_INFO; lwi r11, r11, TI_FLAGS; /* MS: get flags from thread info */ andi r11, r11, _TIF_NEED_RESCHED; beqi r11, 5f @@ -826,8 +813,7 @@ ret_from_irq: nop; /* delay slot */ /* Maybe handle a signal */ -5: add r11, r0, CURRENT_TASK; - lwi r11, r11, TS_THREAD_INFO; /* MS: get thread info */ +5: lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* MS: get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_SIGPENDING; beqid r11, no_intr_resched @@ -855,8 +841,7 @@ no_intr_resched: /* MS: Return to kernel state. */ 2: #ifdef CONFIG_PREEMPT - add r11, r0, CURRENT_TASK; - lwi r11, r11, TS_THREAD_INFO; + lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* MS: get preempt_count from thread info */ lwi r5, r11, TI_PREEMPT_COUNT; bgti r5, restore; @@ -869,8 +854,7 @@ preempt: /* interrupts are off that's why I am calling preempt_chedule_irq */ bralid r15, preempt_schedule_irq nop - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ + lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r5, r11, TI_FLAGS; /* get flags in thread info */ andi r5, r5, _TIF_NEED_RESCHED; bnei r5, preempt /* if non zero jump to resched */ @@ -938,7 +922,7 @@ C_ENTRY(_debug_exception): swi r11, r1, PTO+PT_R1; /* Store user SP. */ addi r11, r0, 1; swi r11, r0, TOPHYS(PER_CPU(KM)); /* Now we're in kernel-mode. */ -2: lwi r31, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* get saved current */ +2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* Save away the syscall number. */ swi r0, r1, PTO+PT_R0; tovirt(r1,r1) @@ -958,8 +942,7 @@ dbtrap_call: rtbd r11, 0; bnei r11, 2f; /* Get current task ptr into r11 */ - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ + lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_NEED_RESCHED; beqi r11, 5f; @@ -972,8 +955,7 @@ dbtrap_call: rtbd r11, 0; /* XXX m68knommu also checks TASK_STATE & TASK_COUNTER here. */ /* Maybe handle a signal */ -5: add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - lwi r11, r11, TS_THREAD_INFO; /* get thread info */ +5: lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_SIGPENDING; beqi r11, 1f; /* Signals to handle, handle them */ @@ -1030,7 +1012,7 @@ DBTRAP_return: /* Make global symbol for debugging */ ENTRY(_switch_to) /* prepare return value */ - addk r3, r0, r31 + addk r3, r0, CURRENT_TASK /* save registers in cpu_context */ /* use r11 and r12, volatile registers, as temp register */ @@ -1074,10 +1056,10 @@ ENTRY(_switch_to) nop swi r12, r11, CC_FSR - /* update r31, the current */ - lwi r31, r6, TI_TASK/* give me pointer to task which will be next */ + /* update r31, the current-give me pointer to task which will be next */ + lwi CURRENT_TASK, r6, TI_TASK /* stored it to current_save too */ - swi r31, r0, PER_CPU(CURRENT_SAVE) + swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE) /* get new process' cpu context and restore */ /* give me start where start context of next task */ -- cgit v1.2.3 From 8633bebc63ba5752254925f8b49a19102df1a0ff Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 22 Feb 2010 13:24:43 +0100 Subject: microblaze: Save current task directly Signed-off-by: Michal Simek --- arch/microblaze/kernel/entry.S | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index 772fe7415f8..cc9885d441d 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -425,8 +425,7 @@ C_ENTRY(ret_from_trap): lwi r4, r1, PTO + PT_R4; swi r0, r0, PER_CPU(KM); /* Now officially in user state. */ - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - swi r11, r0, PER_CPU(CURRENT_SAVE); /* save current */ + swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE); /* save current */ VM_OFF; tophys(r1,r1); RESTORE_REGS; @@ -700,8 +699,7 @@ C_ENTRY(ret_from_exc): /* Finally, return to user state. */ 1: swi r0, r0, PER_CPU(KM); /* Now officially in user state. */ - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - swi r11, r0, PER_CPU(CURRENT_SAVE); /* save current */ + swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE); /* save current */ VM_OFF; tophys(r1,r1); @@ -828,8 +826,7 @@ no_intr_resched: /* Disable interrupts, we are now committed to the state restore */ disable_irq swi r0, r0, PER_CPU(KM); /* MS: Now officially in user state. */ - add r11, r0, CURRENT_TASK; - swi r11, r0, PER_CPU(CURRENT_SAVE); + swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE); VM_OFF; tophys(r1,r1); lwi r3, r1, PTO + PT_R3; /* MS: restore saved r3, r4 registers */ @@ -979,8 +976,7 @@ dbtrap_call: rtbd r11, 0; /* Finally, return to user state. */ 1: swi r0, r0, PER_CPU(KM); /* Now officially in user state. */ - add r11, r0, CURRENT_TASK; /* Get current task ptr into r11 */ - swi r11, r0, PER_CPU(CURRENT_SAVE); /* save current */ + swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE); /* save current */ VM_OFF; tophys(r1,r1); -- cgit v1.2.3 From 841d6e8c4e969b2cdd80f7216af34d932c41b1a6 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Fri, 22 Jan 2010 14:28:36 +0100 Subject: microblaze: entry.S use delay slot for return handlers Signed-off-by: Michal Simek --- arch/microblaze/kernel/entry.S | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'arch') diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index cc9885d441d..c0ede25c5b9 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -414,10 +414,9 @@ C_ENTRY(ret_from_trap): beqi r11, 1f; /* Signals to handle, handle them */ la r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ - add r6, r0, r0; /* Arg 2: sigset_t *oldset */ addi r7, r0, 1; /* Arg 3: int in_syscall */ bralid r15, do_signal; /* Handle any signals */ - nop; + add r6, r0, r0; /* Arg 2: sigset_t *oldset */ /* Finally, return to user state. */ 1: @@ -692,10 +691,9 @@ C_ENTRY(ret_from_exc): * store return registers separately because this macros is use * for others exceptions */ la r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ - add r6, r0, r0; /* Arg 2: sigset_t *oldset */ addi r7, r0, 0; /* Arg 3: int in_syscall */ bralid r15, do_signal; /* Handle any signals */ - nop; + add r6, r0, r0; /* Arg 2: sigset_t *oldset */ /* Finally, return to user state. */ 1: swi r0, r0, PER_CPU(KM); /* Now officially in user state. */ @@ -968,10 +966,9 @@ dbtrap_call: rtbd r11, 0; (in a possibly modified form) after do_signal returns. */ la r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ - add r6, r0, r0; /* Arg 2: sigset_t *oldset */ addi r7, r0, 0; /* Arg 3: int in_syscall */ bralid r15, do_signal; /* Handle any signals */ - nop; + add r6, r0, r0; /* Arg 2: sigset_t *oldset */ /* Finally, return to user state. */ -- cgit v1.2.3