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 + -
显示快捷键?