prom.c

来自「linux 内核源代码」· C语言 代码 · 共 1,743 行 · 第 1/3 页

C
1,743
字号
/* * Procedures for creating, accessing and interpreting the device tree. * * Paul Mackerras	August 1996. * Copyright (C) 1996-2005 Paul Mackerras. *  *  Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner. *    {engebret|bergner}@us.ibm.com  * *  Adapted for sparc64 by David S. Miller davem@davemloft.net * *      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 <linux/kernel.h>#include <linux/types.h>#include <linux/string.h>#include <linux/mm.h>#include <linux/bootmem.h>#include <linux/module.h>#include <asm/prom.h>#include <asm/of_device.h>#include <asm/oplib.h>#include <asm/irq.h>#include <asm/asi.h>#include <asm/upa.h>#include <asm/smp.h>extern struct device_node *allnodes;	/* temporary while merging */extern rwlock_t devtree_lock;	/* temporary while merging */struct device_node *of_find_node_by_phandle(phandle handle){	struct device_node *np;	for (np = allnodes; np != 0; np = np->allnext)		if (np->node == handle)			break;	return np;}EXPORT_SYMBOL(of_find_node_by_phandle);int of_getintprop_default(struct device_node *np, const char *name, int def){	struct property *prop;	int len;	prop = of_find_property(np, name, &len);	if (!prop || len != 4)		return def;	return *(int *) prop->value;}EXPORT_SYMBOL(of_getintprop_default);int of_set_property(struct device_node *dp, const char *name, void *val, int len){	struct property **prevp;	void *new_val;	int err;	new_val = kmalloc(len, GFP_KERNEL);	if (!new_val)		return -ENOMEM;	memcpy(new_val, val, len);	err = -ENODEV;	write_lock(&devtree_lock);	prevp = &dp->properties;	while (*prevp) {		struct property *prop = *prevp;		if (!strcasecmp(prop->name, name)) {			void *old_val = prop->value;			int ret;			ret = prom_setprop(dp->node, name, val, len);			err = -EINVAL;			if (ret >= 0) {				prop->value = new_val;				prop->length = len;				if (OF_IS_DYNAMIC(prop))					kfree(old_val);				OF_MARK_DYNAMIC(prop);				err = 0;			}			break;		}		prevp = &(*prevp)->next;	}	write_unlock(&devtree_lock);	/* XXX Upate procfs if necessary... */	return err;}EXPORT_SYMBOL(of_set_property);int of_find_in_proplist(const char *list, const char *match, int len){	while (len > 0) {		int l;		if (!strcmp(list, match))			return 1;		l = strlen(list) + 1;		list += l;		len -= l;	}	return 0;}EXPORT_SYMBOL(of_find_in_proplist);static unsigned int prom_early_allocated;static void * __init prom_early_alloc(unsigned long size){	void *ret;	ret = __alloc_bootmem(size, SMP_CACHE_BYTES, 0UL);	if (ret != NULL)		memset(ret, 0, size);	prom_early_allocated += size;	return ret;}#ifdef CONFIG_PCI/* PSYCHO interrupt mapping support. */#define PSYCHO_IMAP_A_SLOT0	0x0c00UL#define PSYCHO_IMAP_B_SLOT0	0x0c20ULstatic unsigned long psycho_pcislot_imap_offset(unsigned long ino){	unsigned int bus =  (ino & 0x10) >> 4;	unsigned int slot = (ino & 0x0c) >> 2;	if (bus == 0)		return PSYCHO_IMAP_A_SLOT0 + (slot * 8);	else		return PSYCHO_IMAP_B_SLOT0 + (slot * 8);}#define PSYCHO_IMAP_SCSI	0x1000UL#define PSYCHO_IMAP_ETH		0x1008UL#define PSYCHO_IMAP_BPP		0x1010UL#define PSYCHO_IMAP_AU_REC	0x1018UL#define PSYCHO_IMAP_AU_PLAY	0x1020UL#define PSYCHO_IMAP_PFAIL	0x1028UL#define PSYCHO_IMAP_KMS		0x1030UL#define PSYCHO_IMAP_FLPY	0x1038UL#define PSYCHO_IMAP_SHW		0x1040UL#define PSYCHO_IMAP_KBD		0x1048UL#define PSYCHO_IMAP_MS		0x1050UL#define PSYCHO_IMAP_SER		0x1058UL#define PSYCHO_IMAP_TIM0	0x1060UL#define PSYCHO_IMAP_TIM1	0x1068UL#define PSYCHO_IMAP_UE		0x1070UL#define PSYCHO_IMAP_CE		0x1078UL#define PSYCHO_IMAP_A_ERR	0x1080UL#define PSYCHO_IMAP_B_ERR	0x1088UL#define PSYCHO_IMAP_PMGMT	0x1090UL#define PSYCHO_IMAP_GFX		0x1098UL#define PSYCHO_IMAP_EUPA	0x10a0ULstatic unsigned long __psycho_onboard_imap_off[] = {/*0x20*/	PSYCHO_IMAP_SCSI,/*0x21*/	PSYCHO_IMAP_ETH,/*0x22*/	PSYCHO_IMAP_BPP,/*0x23*/	PSYCHO_IMAP_AU_REC,/*0x24*/	PSYCHO_IMAP_AU_PLAY,/*0x25*/	PSYCHO_IMAP_PFAIL,/*0x26*/	PSYCHO_IMAP_KMS,/*0x27*/	PSYCHO_IMAP_FLPY,/*0x28*/	PSYCHO_IMAP_SHW,/*0x29*/	PSYCHO_IMAP_KBD,/*0x2a*/	PSYCHO_IMAP_MS,/*0x2b*/	PSYCHO_IMAP_SER,/*0x2c*/	PSYCHO_IMAP_TIM0,/*0x2d*/	PSYCHO_IMAP_TIM1,/*0x2e*/	PSYCHO_IMAP_UE,/*0x2f*/	PSYCHO_IMAP_CE,/*0x30*/	PSYCHO_IMAP_A_ERR,/*0x31*/	PSYCHO_IMAP_B_ERR,/*0x32*/	PSYCHO_IMAP_PMGMT,/*0x33*/	PSYCHO_IMAP_GFX,/*0x34*/	PSYCHO_IMAP_EUPA,};#define PSYCHO_ONBOARD_IRQ_BASE		0x20#define PSYCHO_ONBOARD_IRQ_LAST		0x34#define psycho_onboard_imap_offset(__ino) \	__psycho_onboard_imap_off[(__ino) - PSYCHO_ONBOARD_IRQ_BASE]#define PSYCHO_ICLR_A_SLOT0	0x1400UL#define PSYCHO_ICLR_SCSI	0x1800UL#define psycho_iclr_offset(ino)					      \	((ino & 0x20) ? (PSYCHO_ICLR_SCSI + (((ino) & 0x1f) << 3)) :  \			(PSYCHO_ICLR_A_SLOT0 + (((ino) & 0x1f)<<3)))static unsigned int psycho_irq_build(struct device_node *dp,				     unsigned int ino,				     void *_data){	unsigned long controller_regs = (unsigned long) _data;	unsigned long imap, iclr;	unsigned long imap_off, iclr_off;	int inofixup = 0;	ino &= 0x3f;	if (ino < PSYCHO_ONBOARD_IRQ_BASE) {		/* PCI slot */		imap_off = psycho_pcislot_imap_offset(ino);	} else {		/* Onboard device */		if (ino > PSYCHO_ONBOARD_IRQ_LAST) {			prom_printf("psycho_irq_build: Wacky INO [%x]\n", ino);			prom_halt();		}		imap_off = psycho_onboard_imap_offset(ino);	}	/* Now build the IRQ bucket. */	imap = controller_regs + imap_off;	iclr_off = psycho_iclr_offset(ino);	iclr = controller_regs + iclr_off;	if ((ino & 0x20) == 0)		inofixup = ino & 0x03;	return build_irq(inofixup, iclr, imap);}static void __init psycho_irq_trans_init(struct device_node *dp){	const struct linux_prom64_registers *regs;	dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller));	dp->irq_trans->irq_build = psycho_irq_build;	regs = of_get_property(dp, "reg", NULL);	dp->irq_trans->data = (void *) regs[2].phys_addr;}#define sabre_read(__reg) \({	u64 __ret; \	__asm__ __volatile__("ldxa [%1] %2, %0" \			     : "=r" (__ret) \			     : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \			     : "memory"); \	__ret; \})struct sabre_irq_data {	unsigned long controller_regs;	unsigned int pci_first_busno;};#define SABRE_CONFIGSPACE	0x001000000UL#define SABRE_WRSYNC		0x1c20UL#define SABRE_CONFIG_BASE(CONFIG_SPACE)	\	(CONFIG_SPACE | (1UL << 24))#define SABRE_CONFIG_ENCODE(BUS, DEVFN, REG)	\	(((unsigned long)(BUS)   << 16) |	\	 ((unsigned long)(DEVFN) << 8)  |	\	 ((unsigned long)(REG)))/* When a device lives behind a bridge deeper in the PCI bus topology * than APB, a special sequence must run to make sure all pending DMA * transfers at the time of IRQ delivery are visible in the coherency * domain by the cpu.  This sequence is to perform a read on the far * side of the non-APB bridge, then perform a read of Sabre's DMA * write-sync register. */static void sabre_wsync_handler(unsigned int ino, void *_arg1, void *_arg2){	unsigned int phys_hi = (unsigned int) (unsigned long) _arg1;	struct sabre_irq_data *irq_data = _arg2;	unsigned long controller_regs = irq_data->controller_regs;	unsigned long sync_reg = controller_regs + SABRE_WRSYNC;	unsigned long config_space = controller_regs + SABRE_CONFIGSPACE;	unsigned int bus, devfn;	u16 _unused;	config_space = SABRE_CONFIG_BASE(config_space);	bus = (phys_hi >> 16) & 0xff;	devfn = (phys_hi >> 8) & 0xff;	config_space |= SABRE_CONFIG_ENCODE(bus, devfn, 0x00);	__asm__ __volatile__("membar #Sync\n\t"			     "lduha [%1] %2, %0\n\t"			     "membar #Sync"			     : "=r" (_unused)			     : "r" ((u16 *) config_space),			       "i" (ASI_PHYS_BYPASS_EC_E_L)			     : "memory");	sabre_read(sync_reg);}#define SABRE_IMAP_A_SLOT0	0x0c00UL#define SABRE_IMAP_B_SLOT0	0x0c20UL#define SABRE_IMAP_SCSI		0x1000UL#define SABRE_IMAP_ETH		0x1008UL#define SABRE_IMAP_BPP		0x1010UL#define SABRE_IMAP_AU_REC	0x1018UL#define SABRE_IMAP_AU_PLAY	0x1020UL#define SABRE_IMAP_PFAIL	0x1028UL#define SABRE_IMAP_KMS		0x1030UL#define SABRE_IMAP_FLPY		0x1038UL#define SABRE_IMAP_SHW		0x1040UL#define SABRE_IMAP_KBD		0x1048UL#define SABRE_IMAP_MS		0x1050UL#define SABRE_IMAP_SER		0x1058UL#define SABRE_IMAP_UE		0x1070UL#define SABRE_IMAP_CE		0x1078UL#define SABRE_IMAP_PCIERR	0x1080UL#define SABRE_IMAP_GFX		0x1098UL#define SABRE_IMAP_EUPA		0x10a0UL#define SABRE_ICLR_A_SLOT0	0x1400UL#define SABRE_ICLR_B_SLOT0	0x1480UL#define SABRE_ICLR_SCSI		0x1800UL#define SABRE_ICLR_ETH		0x1808UL#define SABRE_ICLR_BPP		0x1810UL#define SABRE_ICLR_AU_REC	0x1818UL#define SABRE_ICLR_AU_PLAY	0x1820UL#define SABRE_ICLR_PFAIL	0x1828UL#define SABRE_ICLR_KMS		0x1830UL#define SABRE_ICLR_FLPY		0x1838UL#define SABRE_ICLR_SHW		0x1840UL#define SABRE_ICLR_KBD		0x1848UL#define SABRE_ICLR_MS		0x1850UL#define SABRE_ICLR_SER		0x1858UL#define SABRE_ICLR_UE		0x1870UL#define SABRE_ICLR_CE		0x1878UL#define SABRE_ICLR_PCIERR	0x1880ULstatic unsigned long sabre_pcislot_imap_offset(unsigned long ino){	unsigned int bus =  (ino & 0x10) >> 4;	unsigned int slot = (ino & 0x0c) >> 2;	if (bus == 0)		return SABRE_IMAP_A_SLOT0 + (slot * 8);	else		return SABRE_IMAP_B_SLOT0 + (slot * 8);}static unsigned long __sabre_onboard_imap_off[] = {/*0x20*/	SABRE_IMAP_SCSI,/*0x21*/	SABRE_IMAP_ETH,/*0x22*/	SABRE_IMAP_BPP,/*0x23*/	SABRE_IMAP_AU_REC,/*0x24*/	SABRE_IMAP_AU_PLAY,/*0x25*/	SABRE_IMAP_PFAIL,/*0x26*/	SABRE_IMAP_KMS,/*0x27*/	SABRE_IMAP_FLPY,/*0x28*/	SABRE_IMAP_SHW,/*0x29*/	SABRE_IMAP_KBD,/*0x2a*/	SABRE_IMAP_MS,/*0x2b*/	SABRE_IMAP_SER,/*0x2c*/	0 /* reserved */,/*0x2d*/	0 /* reserved */,/*0x2e*/	SABRE_IMAP_UE,/*0x2f*/	SABRE_IMAP_CE,/*0x30*/	SABRE_IMAP_PCIERR,/*0x31*/	0 /* reserved */,/*0x32*/	0 /* reserved */,/*0x33*/	SABRE_IMAP_GFX,/*0x34*/	SABRE_IMAP_EUPA,};#define SABRE_ONBOARD_IRQ_BASE		0x20#define SABRE_ONBOARD_IRQ_LAST		0x30#define sabre_onboard_imap_offset(__ino) \	__sabre_onboard_imap_off[(__ino) - SABRE_ONBOARD_IRQ_BASE]#define sabre_iclr_offset(ino)					      \	((ino & 0x20) ? (SABRE_ICLR_SCSI + (((ino) & 0x1f) << 3)) :  \			(SABRE_ICLR_A_SLOT0 + (((ino) & 0x1f)<<3)))static int sabre_device_needs_wsync(struct device_node *dp){	struct device_node *parent = dp->parent;	const char *parent_model, *parent_compat;	/* This traversal up towards the root is meant to	 * handle two cases:	 *	 * 1) non-PCI bus sitting under PCI, such as 'ebus'	 * 2) the PCI controller interrupts themselves, which	 *    will use the sabre_irq_build but do not need	 *    the DMA synchronization handling	 */	while (parent) {		if (!strcmp(parent->type, "pci"))			break;		parent = parent->parent;	}	if (!parent)		return 0;	parent_model = of_get_property(parent,				       "model", NULL);	if (parent_model &&	    (!strcmp(parent_model, "SUNW,sabre") ||	     !strcmp(parent_model, "SUNW,simba")))		return 0;	parent_compat = of_get_property(parent,					"compatible", NULL);	if (parent_compat &&	    (!strcmp(parent_compat, "pci108e,a000") ||	     !strcmp(parent_compat, "pci108e,a001")))		return 0;	return 1;}static unsigned int sabre_irq_build(struct device_node *dp,				    unsigned int ino,				    void *_data){	struct sabre_irq_data *irq_data = _data;	unsigned long controller_regs = irq_data->controller_regs;	const struct linux_prom_pci_registers *regs;	unsigned long imap, iclr;	unsigned long imap_off, iclr_off;	int inofixup = 0;	int virt_irq;	ino &= 0x3f;	if (ino < SABRE_ONBOARD_IRQ_BASE) {		/* PCI slot */		imap_off = sabre_pcislot_imap_offset(ino);	} else {		/* onboard device */		if (ino > SABRE_ONBOARD_IRQ_LAST) {			prom_printf("sabre_irq_build: Wacky INO [%x]\n", ino);			prom_halt();		}		imap_off = sabre_onboard_imap_offset(ino);	}	/* Now build the IRQ bucket. */	imap = controller_regs + imap_off;	iclr_off = sabre_iclr_offset(ino);	iclr = controller_regs + iclr_off;	if ((ino & 0x20) == 0)		inofixup = ino & 0x03;	virt_irq = build_irq(inofixup, iclr, imap);	/* If the parent device is a PCI<->PCI bridge other than	 * APB, we have to install a pre-handler to ensure that	 * all pending DMA is drained before the interrupt handler	 * is run.	 */	regs = of_get_property(dp, "reg", NULL);	if (regs && sabre_device_needs_wsync(dp)) {		irq_install_pre_handler(virt_irq,					sabre_wsync_handler,					(void *) (long) regs->phys_hi,					(void *) irq_data);	}	return virt_irq;}static void __init sabre_irq_trans_init(struct device_node *dp){	const struct linux_prom64_registers *regs;	struct sabre_irq_data *irq_data;	const u32 *busrange;	dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller));	dp->irq_trans->irq_build = sabre_irq_build;	irq_data = prom_early_alloc(sizeof(struct sabre_irq_data));	regs = of_get_property(dp, "reg", NULL);	irq_data->controller_regs = regs[0].phys_addr;	busrange = of_get_property(dp, "bus-range", NULL);	irq_data->pci_first_busno = busrange[0];	dp->irq_trans->data = irq_data;}/* SCHIZO interrupt mapping support.  Unlike Psycho, for this controller the * imap/iclr registers are per-PBM. */#define SCHIZO_IMAP_BASE	0x1000UL#define SCHIZO_ICLR_BASE	0x1400ULstatic unsigned long schizo_imap_offset(unsigned long ino){	return SCHIZO_IMAP_BASE + (ino * 8UL);}static unsigned long schizo_iclr_offset(unsigned long ino){	return SCHIZO_ICLR_BASE + (ino * 8UL);}static unsigned long schizo_ino_to_iclr(unsigned long pbm_regs,					unsigned int ino){	return pbm_regs + schizo_iclr_offset(ino);}static unsigned long schizo_ino_to_imap(unsigned long pbm_regs,					unsigned int ino){	return pbm_regs + schizo_imap_offset(ino);}#define schizo_read(__reg) \({	u64 __ret; \	__asm__ __volatile__("ldxa [%1] %2, %0" \			     : "=r" (__ret) \			     : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \			     : "memory"); \	__ret; \})#define schizo_write(__reg, __val) \	__asm__ __volatile__("stxa %0, [%1] %2" \			     : /* no outputs */ \			     : "r" (__val), "r" (__reg), \			       "i" (ASI_PHYS_BYPASS_EC_E) \			     : "memory")static void tomatillo_wsync_handler(unsigned int ino, void *_arg1, void *_arg2){	unsigned long sync_reg = (unsigned long) _arg2;	u64 mask = 1UL << (ino & IMAP_INO);	u64 val;	int limit;	schizo_write(sync_reg, mask);	limit = 100000;	val = 0;	while (--limit) {		val = schizo_read(sync_reg);		if (!(val & mask))			break;	}	if (limit <= 0) {		printk("tomatillo_wsync_handler: DMA won't sync [%lx:%lx]\n",		       val, mask);	}	if (_arg1) {		static unsigned char cacheline[64]			__attribute__ ((aligned (64)));		__asm__ __volatile__("rd %%fprs, %0\n\t"				     "or %0, %4, %1\n\t"				     "wr %1, 0x0, %%fprs\n\t"				     "stda %%f0, [%5] %6\n\t"				     "wr %0, 0x0, %%fprs\n\t"				     "membar #Sync"				     : "=&r" (mask), "=&r" (val)

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?