diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/ia64/sn |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/ia64/sn')
40 files changed, 7149 insertions, 0 deletions
diff --git a/arch/ia64/sn/Makefile b/arch/ia64/sn/Makefile new file mode 100644 index 00000000000..a269f6d84c2 --- /dev/null +++ b/arch/ia64/sn/Makefile @@ -0,0 +1,14 @@ +# arch/ia64/sn/Makefile +# +# 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. +# +# Copyright (C) 2004 Silicon Graphics, Inc. All Rights Reserved. +# +# Makefile for the sn ia64 subplatform +# + +CPPFLAGS += -I$(srctree)/arch/ia64/sn/include + +obj-y += kernel/ pci/ diff --git a/arch/ia64/sn/include/ioerror.h b/arch/ia64/sn/include/ioerror.h new file mode 100644 index 00000000000..e68f2b0789a --- /dev/null +++ b/arch/ia64/sn/include/ioerror.h @@ -0,0 +1,81 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2003 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_IOERROR_H +#define _ASM_IA64_SN_IOERROR_H + +/* + * IO error structure. + * + * This structure would expand to hold the information retrieved from + * all IO related error registers. + * + * This structure is defined to hold all system specific + * information related to a single error. + * + * This serves a couple of purpose. + * - Error handling often involves translating one form of address to other + * form. So, instead of having different data structures at each level, + * we have a single structure, and the appropriate fields get filled in + * at each layer. + * - This provides a way to dump all error related information in any layer + * of erorr handling (debugging aid). + * + * A second possibility is to allow each layer to define its own error + * data structure, and fill in the proper fields. This has the advantage + * of isolating the layers. + * A big concern is the potential stack usage (and overflow), if each layer + * defines these structures on stack (assuming we don't want to do kmalloc. + * + * Any layer wishing to pass extra information to a layer next to it in + * error handling hierarchy, can do so as a separate parameter. + */ + +typedef struct io_error_s { + /* Bit fields indicating which structure fields are valid */ + union { + struct { + unsigned ievb_errortype:1; + unsigned ievb_widgetnum:1; + unsigned ievb_widgetdev:1; + unsigned ievb_srccpu:1; + unsigned ievb_srcnode:1; + unsigned ievb_errnode:1; + unsigned ievb_sysioaddr:1; + unsigned ievb_xtalkaddr:1; + unsigned ievb_busspace:1; + unsigned ievb_busaddr:1; + unsigned ievb_vaddr:1; + unsigned ievb_memaddr:1; + unsigned ievb_epc:1; + unsigned ievb_ef:1; + unsigned ievb_tnum:1; + } iev_b; + unsigned iev_a; + } ie_v; + + short ie_errortype; /* error type: extra info about error */ + short ie_widgetnum; /* Widget number that's in error */ + short ie_widgetdev; /* Device within widget in error */ + cpuid_t ie_srccpu; /* CPU on srcnode generating error */ + cnodeid_t ie_srcnode; /* Node which caused the error */ + cnodeid_t ie_errnode; /* Node where error was noticed */ + iopaddr_t ie_sysioaddr; /* Sys specific IO address */ + iopaddr_t ie_xtalkaddr; /* Xtalk (48bit) addr of Error */ + iopaddr_t ie_busspace; /* Bus specific address space */ + iopaddr_t ie_busaddr; /* Bus specific address */ + caddr_t ie_vaddr; /* Virtual address of error */ + iopaddr_t ie_memaddr; /* Physical memory address */ + caddr_t ie_epc; /* pc when error reported */ + caddr_t ie_ef; /* eframe when error reported */ + short ie_tnum; /* Xtalk TNUM field */ +} ioerror_t; + +#define IOERROR_INIT(e) do { (e)->ie_v.iev_a = 0; } while (0) +#define IOERROR_SETVALUE(e,f,v) do { (e)->ie_ ## f = (v); (e)->ie_v.iev_b.ievb_ ## f = 1; } while (0) + +#endif /* _ASM_IA64_SN_IOERROR_H */ diff --git a/arch/ia64/sn/include/pci/pcibr_provider.h b/arch/ia64/sn/include/pci/pcibr_provider.h new file mode 100644 index 00000000000..b1f05ffec70 --- /dev/null +++ b/arch/ia64/sn/include/pci/pcibr_provider.h @@ -0,0 +1,149 @@ +/* + * 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. + * + * Copyright (C) 1992-1997,2000-2004 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_PCI_PCIBR_PROVIDER_H +#define _ASM_IA64_SN_PCI_PCIBR_PROVIDER_H + +/* Workarounds */ +#define PV907516 (1 << 1) /* TIOCP: Don't write the write buffer flush reg */ + +#define BUSTYPE_MASK 0x1 + +/* Macros given a pcibus structure */ +#define IS_PCIX(ps) ((ps)->pbi_bridge_mode & BUSTYPE_MASK) +#define IS_PCI_BRIDGE_ASIC(asic) (asic == PCIIO_ASIC_TYPE_PIC || \ + asic == PCIIO_ASIC_TYPE_TIOCP) +#define IS_PIC_SOFT(ps) (ps->pbi_bridge_type == PCIBR_BRIDGETYPE_PIC) + + +/* + * The different PCI Bridge types supported on the SGI Altix platforms + */ +#define PCIBR_BRIDGETYPE_UNKNOWN -1 +#define PCIBR_BRIDGETYPE_PIC 2 +#define PCIBR_BRIDGETYPE_TIOCP 3 + +/* + * Bridge 64bit Direct Map Attributes + */ +#define PCI64_ATTR_PREF (1ull << 59) +#define PCI64_ATTR_PREC (1ull << 58) +#define PCI64_ATTR_VIRTUAL (1ull << 57) +#define PCI64_ATTR_BAR (1ull << 56) +#define PCI64_ATTR_SWAP (1ull << 55) +#define PCI64_ATTR_VIRTUAL1 (1ull << 54) + +#define PCI32_LOCAL_BASE 0 +#define PCI32_MAPPED_BASE 0x40000000 +#define PCI32_DIRECT_BASE 0x80000000 + +#define IS_PCI32_MAPPED(x) ((uint64_t)(x) < PCI32_DIRECT_BASE && \ + (uint64_t)(x) >= PCI32_MAPPED_BASE) +#define IS_PCI32_DIRECT(x) ((uint64_t)(x) >= PCI32_MAPPED_BASE) + + +/* + * Bridge PMU Address Transaltion Entry Attibutes + */ +#define PCI32_ATE_V (0x1 << 0) +#define PCI32_ATE_CO (0x1 << 1) +#define PCI32_ATE_PREC (0x1 << 2) +#define PCI32_ATE_PREF (0x1 << 3) +#define PCI32_ATE_BAR (0x1 << 4) +#define PCI32_ATE_ADDR_SHFT 12 + +#define MINIMAL_ATES_REQUIRED(addr, size) \ + (IOPG(IOPGOFF(addr) + (size) - 1) == IOPG((size) - 1)) + +#define MINIMAL_ATE_FLAG(addr, size) \ + (MINIMAL_ATES_REQUIRED((uint64_t)addr, size) ? 1 : 0) + +/* bit 29 of the pci address is the SWAP bit */ +#define ATE_SWAPSHIFT 29 +#define ATE_SWAP_ON(x) ((x) |= (1 << ATE_SWAPSHIFT)) +#define ATE_SWAP_OFF(x) ((x) &= ~(1 << ATE_SWAPSHIFT)) + +/* + * I/O page size + */ +#if PAGE_SIZE < 16384 +#define IOPFNSHIFT 12 /* 4K per mapped page */ +#else +#define IOPFNSHIFT 14 /* 16K per mapped page */ +#endif + +#define IOPGSIZE (1 << IOPFNSHIFT) +#define IOPG(x) ((x) >> IOPFNSHIFT) +#define IOPGOFF(x) ((x) & (IOPGSIZE-1)) + +#define PCIBR_DEV_SWAP_DIR (1ull << 19) +#define PCIBR_CTRL_PAGE_SIZE (0x1 << 21) + +/* + * PMU resources. + */ +struct ate_resource{ + uint64_t *ate; + uint64_t num_ate; + uint64_t lowest_free_index; +}; + +struct pcibus_info { + struct pcibus_bussoft pbi_buscommon; /* common header */ + uint32_t pbi_moduleid; + short pbi_bridge_type; + short pbi_bridge_mode; + + struct ate_resource pbi_int_ate_resource; + uint64_t pbi_int_ate_size; + + uint64_t pbi_dir_xbase; + char pbi_hub_xid; + + uint64_t pbi_devreg[8]; + spinlock_t pbi_lock; + + uint32_t pbi_valid_devices; + uint32_t pbi_enabled_devices; +}; + +/* + * pcibus_info structure locking macros + */ +inline static unsigned long +pcibr_lock(struct pcibus_info *pcibus_info) +{ + unsigned long flag; + spin_lock_irqsave(&pcibus_info->pbi_lock, flag); + return(flag); +} +#define pcibr_unlock(pcibus_info, flag) spin_unlock_irqrestore(&pcibus_info->pbi_lock, flag) + +extern void *pcibr_bus_fixup(struct pcibus_bussoft *); +extern uint64_t pcibr_dma_map(struct pcidev_info *, unsigned long, size_t, unsigned int); +extern void pcibr_dma_unmap(struct pcidev_info *, dma_addr_t, int); + +/* + * prototypes for the bridge asic register access routines in pcibr_reg.c + */ +extern void pcireg_control_bit_clr(struct pcibus_info *, uint64_t); +extern void pcireg_control_bit_set(struct pcibus_info *, uint64_t); +extern uint64_t pcireg_tflush_get(struct pcibus_info *); +extern uint64_t pcireg_intr_status_get(struct pcibus_info *); +extern void pcireg_intr_enable_bit_clr(struct pcibus_info *, uint64_t); +extern void pcireg_intr_enable_bit_set(struct pcibus_info *, uint64_t); +extern void pcireg_intr_addr_addr_set(struct pcibus_info *, int, uint64_t); +extern void pcireg_force_intr_set(struct pcibus_info *, int); +extern uint64_t pcireg_wrb_flush_get(struct pcibus_info *, int); +extern void pcireg_int_ate_set(struct pcibus_info *, int, uint64_t); +extern uint64_t * pcireg_int_ate_addr(struct pcibus_info *, int); +extern void pcibr_force_interrupt(struct sn_irq_info *sn_irq_info); +extern void pcibr_change_devices_irq(struct sn_irq_info *sn_irq_info); +extern int pcibr_ate_alloc(struct pcibus_info *, int); +extern void pcibr_ate_free(struct pcibus_info *, int); +extern void ate_write(struct pcibus_info *, int, int, uint64_t); +#endif diff --git a/arch/ia64/sn/include/pci/pcibus_provider_defs.h b/arch/ia64/sn/include/pci/pcibus_provider_defs.h new file mode 100644 index 00000000000..07065615bbe --- /dev/null +++ b/arch/ia64/sn/include/pci/pcibus_provider_defs.h @@ -0,0 +1,43 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_PCI_PCIBUS_PROVIDER_H +#define _ASM_IA64_SN_PCI_PCIBUS_PROVIDER_H + +/* + * SN pci asic types. Do not ever renumber these or reuse values. The + * values must agree with what prom thinks they are. + */ + +#define PCIIO_ASIC_TYPE_UNKNOWN 0 +#define PCIIO_ASIC_TYPE_PPB 1 +#define PCIIO_ASIC_TYPE_PIC 2 +#define PCIIO_ASIC_TYPE_TIOCP 3 + +/* + * Common pciio bus provider data. There should be one of these as the + * first field in any pciio based provider soft structure (e.g. pcibr_soft + * tioca_soft, etc). + */ + +struct pcibus_bussoft { + uint32_t bs_asic_type; /* chipset type */ + uint32_t bs_xid; /* xwidget id */ + uint64_t bs_persist_busnum; /* Persistent Bus Number */ + uint64_t bs_legacy_io; /* legacy io pio addr */ + uint64_t bs_legacy_mem; /* legacy mem pio addr */ + uint64_t bs_base; /* widget base */ + struct xwidget_info *bs_xwidget_info; +}; + +/* + * DMA mapping flags + */ + +#define SN_PCIDMA_CONSISTENT 0x0001 + +#endif /* _ASM_IA64_SN_PCI_PCIBUS_PROVIDER_H */ diff --git a/arch/ia64/sn/include/pci/pcidev.h b/arch/ia64/sn/include/pci/pcidev.h new file mode 100644 index 00000000000..81eb95d3bf4 --- /dev/null +++ b/arch/ia64/sn/include/pci/pcidev.h @@ -0,0 +1,54 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_PCI_PCIDEV_H +#define _ASM_IA64_SN_PCI_PCIDEV_H + +#include <linux/pci.h> + +extern struct sn_irq_info **sn_irq; + +#define SN_PCIDEV_INFO(pci_dev) \ + ((struct pcidev_info *)(pci_dev)->sysdata) + +/* + * Given a pci_bus, return the sn pcibus_bussoft struct. Note that + * this only works for root busses, not for busses represented by PPB's. + */ + +#define SN_PCIBUS_BUSSOFT(pci_bus) \ + ((struct pcibus_bussoft *)(PCI_CONTROLLER((pci_bus))->platform_data)) + +/* + * Given a struct pci_dev, return the sn pcibus_bussoft struct. Note + * that this is not equivalent to SN_PCIBUS_BUSSOFT(pci_dev->bus) due + * due to possible PPB's in the path. + */ + +#define SN_PCIDEV_BUSSOFT(pci_dev) \ + (SN_PCIDEV_INFO(pci_dev)->pdi_host_pcidev_info->pdi_pcibus_info) + +#define PCIIO_BUS_NONE 255 /* bus 255 reserved */ +#define PCIIO_SLOT_NONE 255 +#define PCIIO_FUNC_NONE 255 +#define PCIIO_VENDOR_ID_NONE (-1) + +struct pcidev_info { + uint64_t pdi_pio_mapped_addr[7]; /* 6 BARs PLUS 1 ROM */ + uint64_t pdi_slot_host_handle; /* Bus and devfn Host pci_dev */ + + struct pcibus_bussoft *pdi_pcibus_info; /* Kernel common bus soft */ + struct pcidev_info *pdi_host_pcidev_info; /* Kernel Host pci_dev */ + struct pci_dev *pdi_linux_pcidev; /* Kernel pci_dev */ + + struct sn_irq_info *pdi_sn_irq_info; +}; + +extern void sn_irq_fixup(struct pci_dev *pci_dev, + struct sn_irq_info *sn_irq_info); + +#endif /* _ASM_IA64_SN_PCI_PCIDEV_H */ diff --git a/arch/ia64/sn/include/pci/pic.h b/arch/ia64/sn/include/pci/pic.h new file mode 100644 index 00000000000..fd18acecb1e --- /dev/null +++ b/arch/ia64/sn/include/pci/pic.h @@ -0,0 +1,261 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2003 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_PCI_PIC_H +#define _ASM_IA64_SN_PCI_PIC_H + +/* + * PIC AS DEVICE ZERO + * ------------------ + * + * PIC handles PCI/X busses. PCI/X requires that the 'bridge' (i.e. PIC) + * be designated as 'device 0'. That is a departure from earlier SGI + * PCI bridges. Because of that we use config space 1 to access the + * config space of the first actual PCI device on the bus. + * Here's what the PIC manual says: + * + * The current PCI-X bus specification now defines that the parent + * hosts bus bridge (PIC for example) must be device 0 on bus 0. PIC + * reduced the total number of devices from 8 to 4 and removed the + * device registers and windows, now only supporting devices 0,1,2, and + * 3. PIC did leave all 8 configuration space windows. The reason was + * there was nothing to gain by removing them. Here in lies the problem. + * The device numbering we do using 0 through 3 is unrelated to the device + * numbering which PCI-X requires in configuration space. In the past we + * correlated Configs pace and our device space 0 <-> 0, 1 <-> 1, etc. + * PCI-X requires we start a 1, not 0 and currently the PX brick + * does associate our: + * + * device 0 with configuration space window 1, + * device 1 with configuration space window 2, + * device 2 with configuration space window 3, + * device 3 with configuration space window 4. + * + * The net effect is that all config space access are off-by-one with + * relation to other per-slot accesses on the PIC. + * Here is a table that shows some of that: + * + * Internal Slot# + * | + * | 0 1 2 3 + * ----------|--------------------------------------- + * config | 0x21000 0x22000 0x23000 0x24000 + * | + * even rrb | 0[0] n/a 1[0] n/a [] == implied even/odd + * | + * odd rrb | n/a 0[1] n/a 1[1] + * | + * int dev | 00 01 10 11 + * | + * ext slot# | 1 2 3 4 + * ----------|--------------------------------------- + */ + +#define PIC_ATE_TARGETID_SHFT 8 +#define PIC_HOST_INTR_ADDR 0x0000FFFFFFFFFFFFUL +#define PIC_PCI64_ATTR_TARG_SHFT 60 + + +/***************************************************************************** + *********************** PIC MMR structure mapping *************************** + *****************************************************************************/ + +/* NOTE: PIC WAR. PV#854697. PIC does not allow writes just to [31:0] + * of a 64-bit register. When writing PIC registers, always write the + * entire 64 bits. + */ + +struct pic { + + /* 0x000000-0x00FFFF -- Local Registers */ + + /* 0x000000-0x000057 -- Standard Widget Configuration */ + uint64_t p_wid_id; /* 0x000000 */ + uint64_t p_wid_stat; /* 0x000008 */ + uint64_t p_wid_err_upper; /* 0x000010 */ + uint64_t p_wid_err_lower; /* 0x000018 */ + #define p_wid_err p_wid_err_lower + uint64_t p_wid_control; /* 0x000020 */ + uint64_t p_wid_req_timeout; /* 0x000028 */ + uint64_t p_wid_int_upper; /* 0x000030 */ + uint64_t p_wid_int_lower; /* 0x000038 */ + #define p_wid_int p_wid_int_lower + uint64_t p_wid_err_cmdword; /* 0x000040 */ + uint64_t p_wid_llp; /* 0x000048 */ + uint64_t p_wid_tflush; /* 0x000050 */ + + /* 0x000058-0x00007F -- Bridge-specific Widget Configuration */ + uint64_t p_wid_aux_err; /* 0x000058 */ + uint64_t p_wid_resp_upper; /* 0x000060 */ + uint64_t p_wid_resp_lower; /* 0x000068 */ + #define p_wid_resp p_wid_resp_lower + uint64_t p_wid_tst_pin_ctrl; /* 0x000070 */ + uint64_t p_wid_addr_lkerr; /* 0x000078 */ + + /* 0x000080-0x00008F -- PMU & MAP */ + uint64_t p_dir_map; /* 0x000080 */ + uint64_t _pad_000088; /* 0x000088 */ + + /* 0x000090-0x00009F -- SSRAM */ + uint64_t p_map_fault; /* 0x000090 */ + uint64_t _pad_000098; /* 0x000098 */ + + /* 0x0000A0-0x0000AF -- Arbitration */ + uint64_t p_arb; /* 0x0000A0 */ + uint64_t _pad_0000A8; /* 0x0000A8 */ + + /* 0x0000B0-0x0000BF -- Number In A Can or ATE Parity Error */ + uint64_t p_ate_parity_err; /* 0x0000B0 */ + uint64_t _pad_0000B8; /* 0x0000B8 */ + + /* 0x0000C0-0x0000FF -- PCI/GIO */ + uint64_t p_bus_timeout; /* 0x0000C0 */ + uint64_t p_pci_cfg; /* 0x0000C8 */ + uint64_t p_pci_err_upper; /* 0x0000D0 */ + uint64_t p_pci_err_lower; /* 0x0000D8 */ + #define p_pci_err p_pci_err_lower + uint64_t _pad_0000E0[4]; /* 0x0000{E0..F8} */ + + /* 0x000100-0x0001FF -- Interrupt */ + uint64_t p_int_status; /* 0x000100 */ + uint64_t p_int_enable; /* 0x000108 */ + uint64_t p_int_rst_stat; /* 0x000110 */ + uint64_t p_int_mode; /* 0x000118 */ + uint64_t p_int_device; /* 0x000120 */ + uint64_t p_int_host_err; /* 0x000128 */ + uint64_t p_int_addr[8]; /* 0x0001{30,,,68} */ + uint64_t p_err_int_view; /* 0x000170 */ + uint64_t p_mult_int; /* 0x000178 */ + uint64_t p_force_always[8]; /* 0x0001{80,,,B8} */ + uint64_t p_force_pin[8]; /* 0x0001{C0,,,F8} */ + + /* 0x000200-0x000298 -- Device */ + uint64_t p_device[4]; /* 0x0002{00,,,18} */ + uint64_t _pad_000220[4]; /* 0x0002{20,,,38} */ + uint64_t p_wr_req_buf[4]; /* 0x0002{40,,,58} */ + uint64_t _pad_000260[4]; /* 0x0002{60,,,78} */ + uint64_t p_rrb_map[2]; /* 0x0002{80,,,88} */ + #define p_even_resp p_rrb_map[0] /* 0x000280 */ + #define p_odd_resp p_rrb_map[1] /* 0x000288 */ + uint64_t p_resp_status; /* 0x000290 */ + uint64_t p_resp_clear; /* 0x000298 */ + + uint64_t _pad_0002A0[12]; /* 0x0002{A0..F8} */ + + /* 0x000300-0x0003F8 -- Buffer Address Match Registers */ + struct { + uint64_t upper; /* 0x0003{00,,,F0} */ + uint64_t lower; /* 0x0003{08,,,F8} */ + } p_buf_addr_match[16]; + + /* 0x000400-0x0005FF -- Performance Monitor Registers (even only) */ + struct { + uint64_t flush_w_touch; /* 0x000{400,,,5C0} */ + uint64_t flush_wo_touch; /* 0x000{408,,,5C8} */ + uint64_t inflight; /* 0x000{410,,,5D0} */ + uint64_t prefetch; /* 0x000{418,,,5D8} */ + uint64_t total_pci_retry; /* 0x000{420,,,5E0} */ + uint64_t max_pci_retry; /* 0x000{428,,,5E8} */ + uint64_t max_latency; /* 0x000{430,,,5F0} */ + uint64_t clear_all; /* 0x000{438,,,5F8} */ + } p_buf_count[8]; + + + /* 0x000600-0x0009FF -- PCI/X registers */ + uint64_t p_pcix_bus_err_addr; /* 0x000600 */ + uint64_t p_pcix_bus_err_attr; /* 0x000608 */ + uint64_t p_pcix_bus_err_data; /* 0x000610 */ + uint64_t p_pcix_pio_split_addr; /* 0x000618 */ + uint64_t p_pcix_pio_split_attr; /* 0x000620 */ + uint64_t p_pcix_dma_req_err_attr; /* 0x000628 */ + uint64_t p_pcix_dma_req_err_addr; /* 0x000630 */ + uint64_t p_pcix_timeout; /* 0x000638 */ + + uint64_t _pad_000640[120]; /* 0x000{640,,,9F8} */ + + /* 0x000A00-0x000BFF -- PCI/X Read&Write Buffer */ + struct { + uint64_t p_buf_addr; /* 0x000{A00,,,AF0} */ + uint64_t p_buf_attr; /* 0X000{A08,,,AF8} */ + } p_pcix_read_buf_64[16]; + + struct { + uint64_t p_buf_addr; /* 0x000{B00,,,BE0} */ + uint64_t p_buf_attr; /* 0x000{B08,,,BE8} */ + uint64_t p_buf_valid; /* 0x000{B10,,,BF0} */ + uint64_t __pad1; /* 0x000{B18,,,BF8} */ + } p_pcix_write_buf_64[8]; + + /* End of Local Registers -- Start of Address Map space */ + + char _pad_000c00[0x010000 - 0x000c00]; + + /* 0x010000-0x011fff -- Internal ATE RAM (Auto Parity Generation) */ + uint64_t p_int_ate_ram[1024]; /* 0x010000-0x011fff */ + + /* 0x012000-0x013fff -- Internal ATE RAM (Manual Parity Generation) */ + uint64_t p_int_ate_ram_mp[1024]; /* 0x012000-0x013fff */ + + char _pad_014000[0x18000 - 0x014000]; + + /* 0x18000-0x197F8 -- PIC Write Request Ram */ + uint64_t p_wr_req_lower[256]; /* 0x18000 - 0x187F8 */ + uint64_t p_wr_req_upper[256]; /* 0x18800 - 0x18FF8 */ + uint64_t p_wr_req_parity[256]; /* 0x19000 - 0x197F8 */ + + char _pad_019800[0x20000 - 0x019800]; + + /* 0x020000-0x027FFF -- PCI Device Configuration Spaces */ + union { + uint8_t c[0x1000 / 1]; /* 0x02{0000,,,7FFF} */ + uint16_t s[0x1000 / 2]; /* 0x02{0000,,,7FFF} */ + uint32_t l[0x1000 / 4]; /* 0x02{0000,,,7FFF} */ + uint64_t d[0x1000 / 8]; /* 0x02{0000,,,7FFF} */ + union { + uint8_t c[0x100 / 1]; + uint16_t s[0x100 / 2]; + uint32_t l[0x100 / 4]; + uint64_t d[0x100 / 8]; + } f[8]; + } p_type0_cfg_dev[8]; /* 0x02{0000,,,7FFF} */ + + /* 0x028000-0x028FFF -- PCI Type 1 Configuration Space */ + union { + uint8_t c[0x1000 / 1]; /* 0x028000-0x029000 */ + uint16_t s[0x1000 / 2]; /* 0x028000-0x029000 */ + uint32_t l[0x1000 / 4]; /* 0x028000-0x029000 */ + uint64_t d[0x1000 / 8]; /* 0x028000-0x029000 */ + union { + uint8_t c[0x100 / 1]; + uint16_t s[0x100 / 2]; + uint32_t l[0x100 / 4]; + uint64_t d[0x100 / 8]; + } f[8]; + } p_type1_cfg; /* 0x028000-0x029000 */ + + char _pad_029000[0x030000-0x029000]; + + /* 0x030000-0x030007 -- PCI Interrupt Acknowledge Cycle */ + union { + uint8_t c[8 / 1]; + uint16_t s[8 / 2]; + uint32_t l[8 / 4]; + uint64_t d[8 / 8]; + } p_pci_iack; /* 0x030000-0x030007 */ + + char _pad_030007[0x040000-0x030008]; + + /* 0x040000-0x030007 -- PCIX Special Cycle */ + union { + uint8_t c[8 / 1]; + uint16_t s[8 / 2]; + uint32_t l[8 / 4]; + uint64_t d[8 / 8]; + } p_pcix_cycle; /* 0x040000-0x040007 */ +}; + +#endif /* _ASM_IA64_SN_PCI_PIC_H */ diff --git a/arch/ia64/sn/include/pci/tiocp.h b/arch/ia64/sn/include/pci/tiocp.h new file mode 100644 index 00000000000..f07c83b2bf6 --- /dev/null +++ b/arch/ia64/sn/include/pci/tiocp.h @@ -0,0 +1,256 @@ +/* + * 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. + * + * Copyright (C) 2003-2004 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_PCI_TIOCP_H +#define _ASM_IA64_SN_PCI_TIOCP_H + +#define TIOCP_HOST_INTR_ADDR 0x003FFFFFFFFFFFFFUL +#define TIOCP_PCI64_CMDTYPE_MEM (0x1ull << 60) + + +/***************************************************************************** + *********************** TIOCP MMR structure mapping *************************** + *****************************************************************************/ + +struct tiocp{ + + /* 0x000000-0x00FFFF -- Local Registers */ + + /* 0x000000-0x000057 -- (Legacy Widget Space) Configuration */ + uint64_t cp_id; /* 0x000000 */ + uint64_t cp_stat; /* 0x000008 */ + uint64_t cp_err_upper; /* 0x000010 */ + uint64_t cp_err_lower; /* 0x000018 */ + #define cp_err cp_err_lower + uint64_t cp_control; /* 0x000020 */ + uint64_t cp_req_timeout; /* 0x000028 */ + uint64_t cp_intr_upper; /* 0x000030 */ + uint64_t cp_intr_lower; /* 0x000038 */ + #define cp_intr cp_intr_lower + uint64_t cp_err_cmdword; /* 0x000040 */ + uint64_t _pad_000048; /* 0x000048 */ + uint64_t cp_tflush; /* 0x000050 */ + + /* 0x000058-0x00007F -- Bridge-specific Configuration */ + uint64_t cp_aux_err; /* 0x000058 */ + uint64_t cp_resp_upper; /* 0x000060 */ + uint64_t cp_resp_lower; /* 0x000068 */ + #define cp_resp cp_resp_lower + uint64_t cp_tst_pin_ctrl; /* 0x000070 */ + uint64_t cp_addr_lkerr; /* 0x000078 */ + + /* 0x000080-0x00008F -- PMU & MAP */ + uint64_t cp_dir_map; /* 0x000080 */ + uint64_t _pad_000088; /* 0x000088 */ + + /* 0x000090-0x00009F -- SSRAM */ + uint64_t cp_map_fault; /* 0x000090 */ + uint64_t _pad_000098; /* 0x000098 */ + + /* 0x0000A0-0x0000AF -- Arbitration */ + uint64_t cp_arb; /* 0x0000A0 */ + uint64_t _pad_0000A8; /* 0x0000A8 */ + + /* 0x0000B0-0x0000BF -- Number In A Can or ATE Parity Error */ + uint64_t cp_ate_parity_err; /* 0x0000B0 */ + uint64_t _pad_0000B8; /* 0x0000B8 */ + + /* 0x0000C0-0x0000FF -- PCI/GIO */ + uint64_t cp_bus_timeout; /* 0x0000C0 */ + uint64_t cp_pci_cfg; /* 0x0000C8 */ + uint64_t cp_pci_err_upper; /* 0x0000D0 */ + uint64_t cp_pci_err_lower; /* 0x0000D8 */ + #define cp_pci_err cp_pci_err_lower + uint64_t _pad_0000E0[4]; /* 0x0000{E0..F8} */ + + /* 0x000100-0x0001FF -- Interrupt */ + uint64_t cp_int_status; /* 0x000100 */ + uint64_t cp_int_enable; /* 0x000108 */ + uint64_t cp_int_rst_stat; /* 0x000110 */ + uint64_t cp_int_mode; /* 0x000118 */ + uint64_t cp_int_device; /* 0x000120 */ + uint64_t cp_int_host_err; /* 0x000128 */ + uint64_t cp_int_addr[8]; /* 0x0001{30,,,68} */ + uint64_t cp_err_int_view; /* 0x000170 */ + uint64_t cp_mult_int; /* 0x000178 */ + uint64_t cp_force_always[8]; /* 0x0001{80,,,B8} */ + uint64_t cp_force_pin[8]; /* 0x0001{C0,,,F8} */ + + /* 0x000200-0x000298 -- Device */ + uint64_t cp_device[4]; /* 0x0002{00,,,18} */ + uint64_t _pad_000220[4]; /* 0x0002{20,,,38} */ + uint64_t cp_wr_req_buf[4]; /* 0x0002{40,,,58} */ + uint64_t _pad_000260[4]; /* 0x0002{60,,,78} */ + uint64_t cp_rrb_map[2]; /* 0x0002{80,,,88} */ + #define cp_even_resp cp_rrb_map[0] /* 0x000280 */ + #define cp_odd_resp cp_rrb_map[1] /* 0x000288 */ + uint64_t cp_resp_status; /* 0x000290 */ + uint64_t cp_resp_clear; /* 0x000298 */ + + uint64_t _pad_0002A0[12]; /* 0x0002{A0..F8} */ + + /* 0x000300-0x0003F8 -- Buffer Address Match Registers */ + struct { + uint64_t upper; /* 0x0003{00,,,F0} */ + uint64_t lower; /* 0x0003{08,,,F8} */ + } cp_buf_addr_match[16]; + + /* 0x000400-0x0005FF -- Performance Monitor Registers (even only) */ + struct { + uint64_t flush_w_touch; /* 0x000{400,,,5C0} */ + uint64_t flush_wo_touch; /* 0x000{408,,,5C8} */ + uint64_t inflight; /* 0x000{410,,,5D0} */ + uint64_t prefetch; /* 0x000{418,,,5D8} */ + uint64_t total_pci_retry; /* 0x000{420,,,5E0} */ + uint64_t max_pci_retry; /* 0x000{428,,,5E8} */ + uint64_t max_latency; /* 0x000{430,,,5F0} */ + uint64_t clear_all; /* 0x000{438,,,5F8} */ + } cp_buf_count[8]; + + + /* 0x000600-0x0009FF -- PCI/X registers */ + uint64_t cp_pcix_bus_err_addr; /* 0x000600 */ + uint64_t cp_pcix_bus_err_attr; /* 0x000608 */ + uint64_t cp_pcix_bus_err_data; /* 0x000610 */ + uint64_t cp_pcix_pio_split_addr; /* 0x000618 */ + uint64_t cp_pcix_pio_split_attr; /* 0x000620 */ + uint64_t cp_pcix_dma_req_err_attr; /* 0x000628 */ + uint64_t cp_pcix_dma_req_err_addr; /* 0x000630 */ + uint64_t cp_pcix_timeout; /* 0x000638 */ + + uint64_t _pad_000640[24]; /* 0x000{640,,,6F8} */ + + /* 0x000700-0x000737 -- Debug Registers */ + uint64_t cp_ct_debug_ctl; /* 0x000700 */ + uint64_t cp_br_debug_ctl; /* 0x000708 */ + uint64_t cp_mux3_debug_ctl; /* 0x000710 */ + uint64_t cp_mux4_debug_ctl; /* 0x000718 */ + uint64_t cp_mux5_debug_ctl; /* 0x000720 */ + uint64_t cp_mux6_debug_ctl; /* 0x000728 */ + uint64_t cp_mux7_debug_ctl; /* 0x000730 */ + + uint64_t _pad_000738[89]; /* 0x000{738,,,9F8} */ + + /* 0x000A00-0x000BFF -- PCI/X Read&Write Buffer */ + struct { + uint64_t cp_buf_addr; /* 0x000{A00,,,AF0} */ + uint64_t cp_buf_attr; /* 0X000{A08,,,AF8} */ + } cp_pcix_read_buf_64[16]; + + struct { + uint64_t cp_buf_addr; /* 0x000{B00,,,BE0} */ + uint64_t cp_buf_attr; /* 0x000{B08,,,BE8} */ + uint64_t cp_buf_valid; /* 0x000{B10,,,BF0} */ + uint64_t __pad1; /* 0x000{B18,,,BF8} */ + } cp_pcix_write_buf_64[8]; + + /* End of Local Registers -- Start of Address Map space */ + + char _pad_000c00[0x010000 - 0x000c00]; + + /* 0x010000-0x011FF8 -- Internal ATE RAM (Auto Parity Generation) */ + uint64_t cp_int_ate_ram[1024]; /* 0x010000-0x011FF8 */ + + char _pad_012000[0x14000 - 0x012000]; + + /* 0x014000-0x015FF8 -- Internal ATE RAM (Manual Parity Generation) */ + uint64_t cp_int_ate_ram_mp[1024]; /* 0x014000-0x015FF8 */ + + char _pad_016000[0x18000 - 0x016000]; + + /* 0x18000-0x197F8 -- TIOCP Write Request Ram */ + uint64_t cp_wr_req_lower[256]; /* 0x18000 - 0x187F8 */ + uint64_t cp_wr_req_upper[256]; /* 0x18800 - 0x18FF8 */ + uint64_t cp_wr_req_parity[256]; /* 0x19000 - 0x197F8 */ + + char _pad_019800[0x1C000 - 0x019800]; + + /* 0x1C000-0x1EFF8 -- TIOCP Read Response Ram */ + uint64_t cp_rd_resp_lower[512]; /* 0x1C000 - 0x1CFF8 */ + uint64_t cp_rd_resp_upper[512]; /* 0x1D000 - 0x1DFF8 */ + uint64_t cp_rd_resp_parity[512]; /* 0x1E000 - 0x1EFF8 */ + + char _pad_01F000[0x20000 - 0x01F000]; + + /* 0x020000-0x021FFF -- Host Device (CP) Configuration Space (not used) */ + char _pad_020000[0x021000 - 0x20000]; + + /* 0x021000-0x027FFF -- PCI Device Configuration Spaces */ + union { + uint8_t c[0x1000 / 1]; /* 0x02{0000,,,7FFF} */ + uint16_t s[0x1000 / 2]; /* 0x02{0000,,,7FFF} */ + uint32_t l[0x1000 / 4]; /* 0x02{0000,,,7FFF} */ + uint64_t d[0x1000 / 8]; /* 0x02{0000,,,7FFF} */ + union { + uint8_t c[0x100 / 1]; + uint16_t s[0x100 / 2]; + uint32_t l[0x100 / 4]; + uint64_t d[0x100 / 8]; + } f[8]; + } cp_type0_cfg_dev[7]; /* 0x02{1000,,,7FFF} */ + + /* 0x028000-0x028FFF -- PCI Type 1 Configuration Space */ + union { + uint8_t c[0x1000 / 1]; /* 0x028000-0x029000 */ + uint16_t s[0x1000 / 2]; /* 0x028000-0x029000 */ + uint32_t l[0x1000 / 4]; /* 0x028000-0x029000 */ + uint64_t d[0x1000 / 8]; /* 0x028000-0x029000 */ + union { + uint8_t c[0x100 / 1]; + uint16_t s[0x100 / 2]; + uint32_t l[0x100 / 4]; + uint64_t d[0x100 / 8]; + } f[8]; + } cp_type1_cfg; /* 0x028000-0x029000 */ + + char _pad_029000[0x030000-0x029000]; + + /* 0x030000-0x030007 -- PCI Interrupt Acknowledge Cycle */ + union { + uint8_t c[8 / 1]; + uint16_t s[8 / 2]; + uint32_t l[8 / 4]; + uint64_t d[8 / 8]; + } cp_pci_iack; /* 0x030000-0x030007 */ + + char _pad_030007[0x040000-0x030008]; + + /* 0x040000-0x040007 -- PCIX Special Cycle */ + union { + uint8_t c[8 / 1]; + uint16_t s[8 / 2]; + uint32_t l[8 / 4]; + uint64_t d[8 / 8]; + } cp_pcix_cycle; /* 0x040000-0x040007 */ + + char _pad_040007[0x200000-0x040008]; + + /* 0x200000-0x7FFFFF -- PCI/GIO Device Spaces */ + union { + uint8_t c[0x100000 / 1]; + uint16_t s[0x100000 / 2]; + uint32_t l[0x100000 / 4]; + uint64_t d[0x100000 / 8]; + } cp_devio_raw[6]; /* 0x200000-0x7FFFFF */ + + #define cp_devio(n) cp_devio_raw[((n)<2)?(n*2):(n+2)] + + char _pad_800000[0xA00000-0x800000]; + + /* 0xA00000-0xBFFFFF -- PCI/GIO Device Spaces w/flush */ + union { + uint8_t c[0x100000 / 1]; + uint16_t s[0x100000 / 2]; + uint32_t l[0x100000 / 4]; + uint64_t d[0x100000 / 8]; + } cp_devio_raw_flush[6]; /* 0xA00000-0xBFFFFF */ + + #define cp_devio_flush(n) cp_devio_raw_flush[((n)<2)?(n*2):(n+2)] + +}; + +#endif /* _ASM_IA64_SN_PCI_TIOCP_H */ diff --git a/arch/ia64/sn/include/tio.h b/arch/ia64/sn/include/tio.h new file mode 100644 index 00000000000..0139124dd54 --- /dev/null +++ b/arch/ia64/sn/include/tio.h @@ -0,0 +1,37 @@ +/* + * 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. + * + * Copyright (C) 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#ifndef _ASM_IA64_SN_TIO_H +#define _ASM_IA64_SN_TIO_H + +#define TIO_MMR_ADDR_MOD + +#define TIO_NODE_ID TIO_MMR_ADDR_MOD(0x0000000090060e80) + +#define TIO_ITTE_BASE 0xb0008800 /* base of translation table entries */ +#define TIO_ITTE(bigwin) (TIO_ITTE_BASE + 8*(bigwin)) + +#define TIO_ITTE_OFFSET_BITS 8 /* size of offset field */ +#define TIO_ITTE_OFFSET_MASK ((1<<TIO_ITTE_OFFSET_BITS)-1) +#define TIO_ITTE_OFFSET_SHIFT 0 + +#define TIO_ITTE_WIDGET_BITS 2 /* size of widget field */ +#define TIO_ITTE_WIDGET_MASK ((1<<TIO_ITTE_WIDGET_BITS)-1) +#define TIO_ITTE_WIDGET_SHIFT 12 +#define TIO_ITTE_VALID_MASK 0x1 +#define TIO_ITTE_VALID_SHIFT 16 + + +#define TIO_ITTE_PUT(nasid, bigwin, widget, addr, valid) \ + REMOTE_HUB_S((nasid), TIO_ITTE(bigwin), \ + (((((addr) >> TIO_BWIN_SIZE_BITS) & \ + TIO_ITTE_OFFSET_MASK) << TIO_ITTE_OFFSET_SHIFT) | \ + (((widget) & TIO_ITTE_WIDGET_MASK) << TIO_ITTE_WIDGET_SHIFT)) | \ + (( (valid) & TIO_ITTE_VALID_MASK) << TIO_ITTE_VALID_SHIFT)) + +#endif /* _ASM_IA64_SN_TIO_H */ diff --git a/arch/ia64/sn/include/xtalk/hubdev.h b/arch/ia64/sn/include/xtalk/hubdev.h new file mode 100644 index 00000000000..868e7ecae84 --- /dev/null +++ b/arch/ia64/sn/include/xtalk/hubdev.h @@ -0,0 +1,67 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ +#ifndef _ASM_IA64_SN_XTALK_HUBDEV_H +#define _ASM_IA64_SN_XTALK_HUBDEV_H + +#define HUB_WIDGET_ID_MAX 0xf +#define DEV_PER_WIDGET (2*2*8) +#define IIO_ITTE_WIDGET_BITS 4 /* size of widget field */ +#define IIO_ITTE_WIDGET_MASK ((1<<IIO_ITTE_WIDGET_BITS)-1) +#define IIO_ITTE_WIDGET_SHIFT 8 + +/* + * Use the top big window as a surrogate for the first small window + */ +#define SWIN0_BIGWIN HUB_NUM_BIG_WINDOW +#define IIO_NUM_ITTES 7 +#define HUB_NUM_BIG_WINDOW (IIO_NUM_ITTES - 1) + +struct sn_flush_device_list { + int sfdl_bus; + int sfdl_slot; + int sfdl_pin; + struct bar_list { + unsigned long start; + unsigned long end; + } sfdl_bar_list[6]; + unsigned long sfdl_force_int_addr; + unsigned long sfdl_flush_value; + volatile unsigned long *sfdl_flush_addr; + uint64_t sfdl_persistent_busnum; + struct pcibus_info *sfdl_pcibus_info; + spinlock_t sfdl_flush_lock; +}; + +/* + * **widget_p - Used as an array[wid_num][device] of sn_flush_device_list. + */ +struct sn_flush_nasid_entry { + struct sn_flush_device_list **widget_p; /* Used as a array of wid_num */ + uint64_t iio_itte[8]; +}; + +struct hubdev_info { + geoid_t hdi_geoid; + short hdi_nasid; + short hdi_peer_nasid; /* Dual Porting Peer */ + + struct sn_flush_nasid_entry hdi_flush_nasid_list; + struct xwidget_info hdi_xwidget_info[HUB_WIDGET_ID_MAX + 1]; + + + void *hdi_nodepda; + void *hdi_node_vertex; + void *hdi_xtalk_vertex; +}; + +extern void hubdev_init_node(nodepda_t *, cnodeid_t); +extern void hub_error_init(struct hubdev_info *); +extern void ice_error_init(struct hubdev_info *); + + +#endif /* _ASM_IA64_SN_XTALK_HUBDEV_H */ diff --git a/arch/ia64/sn/include/xtalk/xbow.h b/arch/ia64/sn/include/xtalk/xbow.h new file mode 100644 index 00000000000..ec56b3432f1 --- /dev/null +++ b/arch/ia64/sn/include/xtalk/xbow.h @@ -0,0 +1,291 @@ +/* + * 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. + * + * Copyright (C) 1992-1997,2000-2004 Silicon Graphics, Inc. All Rights Reserved. + */ +#ifndef _ASM_IA64_SN_XTALK_XBOW_H +#define _ASM_IA64_SN_XTALK_XBOW_H + +#define XBOW_PORT_8 0x8 +#define XBOW_PORT_C 0xc +#define XBOW_PORT_F 0xf + +#define MAX_XBOW_PORTS 8 /* number of ports on xbow chip */ +#define BASE_XBOW_PORT XBOW_PORT_8 /* Lowest external port */ + +#define XBOW_CREDIT 4 + +#define MAX_XBOW_NAME 16 + +/* Register set for each xbow link */ +typedef volatile struct xb_linkregs_s { +/* + * we access these through synergy unswizzled space, so the address + * gets twiddled (i.e. references to 0x4 actually go to 0x0 and vv.) + * That's why we put the register first and filler second. + */ + uint32_t link_ibf; + uint32_t filler0; /* filler for proper alignment */ + uint32_t link_control; + uint32_t filler1; + uint32_t link_status; + uint32_t filler2; + uint32_t link_arb_upper; + uint32_t filler3; + uint32_t link_arb_lower; + uint32_t filler4; + uint32_t link_status_clr; + uint32_t filler5; + uint32_t link_reset; + uint32_t filler6; + uint32_t link_aux_status; + uint32_t filler7; +} xb_linkregs_t; + +typedef volatile struct xbow_s { + /* standard widget configuration 0x000000-0x000057 */ + struct widget_cfg xb_widget; /* 0x000000 */ + + /* helper fieldnames for accessing bridge widget */ + +#define xb_wid_id xb_widget.w_id +#define xb_wid_stat xb_widget.w_status +#define xb_wid_err_upper xb_widget.w_err_upper_addr +#define xb_wid_err_lower xb_widget.w_err_lower_addr +#define xb_wid_control xb_widget.w_control +#define xb_wid_req_timeout xb_widget.w_req_timeout +#define xb_wid_int_upper xb_widget.w_intdest_upper_addr +#define xb_wid_int_lower xb_widget.w_intdest_lower_addr +#define xb_wid_err_cmdword xb_widget.w_err_cmd_word +#define xb_wid_llp xb_widget.w_llp_cfg +#define xb_wid_stat_clr xb_widget.w_tflush + +/* + * we access these through synergy unswizzled space, so the address + * gets twiddled (i.e. references to 0x4 actually go to 0x0 and vv.) + * That's why we put the register first and filler second. + */ + /* xbow-specific widget configuration 0x000058-0x0000FF */ + uint32_t xb_wid_arb_reload; /* 0x00005C */ + uint32_t _pad_000058; + uint32_t xb_perf_ctr_a; /* 0x000064 */ + uint32_t _pad_000060; + uint32_t xb_perf_ctr_b; /* 0x00006c */ + uint32_t _pad_000068; + uint32_t xb_nic; /* 0x000074 */ + uint32_t _pad_000070; + + /* Xbridge only */ + uint32_t xb_w0_rst_fnc; /* 0x00007C */ + uint32_t _pad_000078; + uint32_t xb_l8_rst_fnc; /* 0x000084 */ + uint32_t _pad_000080; + uint32_t xb_l9_rst_fnc; /* 0x00008c */ + uint32_t _pad_000088; + uint32_t xb_la_rst_fnc; /* 0x000094 */ + uint32_t _pad_000090; + uint32_t xb_lb_rst_fnc; /* 0x00009c */ + uint32_t _pad_000098; + uint32_t xb_lc_rst_fnc; /* 0x0000a4 */ + uint32_t _pad_0000a0; + uint32_t xb_ld_rst_fnc; /* 0x0000ac */ + uint32_t _pad_0000a8; + uint32_t xb_le_rst_fnc; /* 0x0000b4 */ + uint32_t _pad_0000b0; + uint32_t xb_lf_rst_fnc; /* 0x0000bc */ + uint32_t _pad_0000b8; + uint32_t xb_lock; /* 0x0000c4 */ + uint32_t _pad_0000c0; + uint32_t xb_lock_clr; /* 0x0000cc */ + uint32_t _pad_0000c8; + /* end of Xbridge only */ + uint32_t _pad_0000d0[12]; + + /* Link Specific Registers, port 8..15 0x000100-0x000300 */ + xb_linkregs_t xb_link_raw[MAX_XBOW_PORTS]; +#define xb_link(p) xb_link_raw[(p) & (MAX_XBOW_PORTS - 1)] + +} xbow_t; + +#define XB_FLAGS_EXISTS 0x1 /* device exists */ +#define XB_FLAGS_MASTER 0x2 +#define XB_FLAGS_SLAVE 0x0 +#define XB_FLAGS_GBR 0x4 +#define XB_FLAGS_16BIT 0x8 +#define XB_FLAGS_8BIT 0x0 + +/* is widget port number valid? (based on version 7.0 of xbow spec) */ +#define XBOW_WIDGET_IS_VALID(wid) ((wid) >= XBOW_PORT_8 && (wid) <= XBOW_PORT_F) + +/* whether to use upper or lower arbitration register, given source widget id */ +#define XBOW_ARB_IS_UPPER(wid) ((wid) >= XBOW_PORT_8 && (wid) <= XBOW_PORT_B) +#define XBOW_ARB_IS_LOWER(wid) ((wid) >= XBOW_PORT_C && (wid) <= XBOW_PORT_F) + +/* offset of arbitration register, given source widget id */ +#define XBOW_ARB_OFF(wid) (XBOW_ARB_IS_UPPER(wid) ? 0x1c : 0x24) + +#define XBOW_WID_ID WIDGET_ID +#define XBOW_WID_STAT WIDGET_STATUS +#define XBOW_WID_ERR_UPPER WIDGET_ERR_UPPER_ADDR +#define XBOW_WID_ERR_LOWER WIDGET_ERR_LOWER_ADDR +#define XBOW_WID_CONTROL WIDGET_CONTROL +#define XBOW_WID_REQ_TO WIDGET_REQ_TIMEOUT +#define XBOW_WID_INT_UPPER WIDGET_INTDEST_UPPER_ADDR +#define XBOW_WID_INT_LOWER WIDGET_INTDEST_LOWER_ADDR +#define XBOW_WID_ERR_CMDWORD WIDGET_ERR_CMD_WORD +#define XBOW_WID_LLP WIDGET_LLP_CFG +#define XBOW_WID_STAT_CLR WIDGET_TFLUSH +#define XBOW_WID_ARB_RELOAD 0x5c +#define XBOW_WID_PERF_CTR_A 0x64 +#define XBOW_WID_PERF_CTR_B 0x6c +#define XBOW_WID_NIC 0x74 + +/* Xbridge only */ +#define XBOW_W0_RST_FNC 0x00007C +#define XBOW_L8_RST_FNC 0x000084 +#define XBOW_L9_RST_FNC 0x00008c +#define XBOW_LA_RST_FNC 0x000094 +#define XBOW_LB_RST_FNC 0x00009c +#define XBOW_LC_RST_FNC 0x0000a4 +#define XBOW_LD_RST_FNC 0x0000ac +#define XBOW_LE_RST_FNC 0x0000b4 +#define XBOW_LF_RST_FNC 0x0000bc +#define XBOW_RESET_FENCE(x) ((x) > 7 && (x) < 16) ? \ + (XBOW_W0_RST_FNC + ((x) - 7) * 8) : \ + ((x) == 0) ? XBOW_W0_RST_FNC : 0 +#define XBOW_LOCK 0x0000c4 +#define XBOW_LOCK_CLR 0x0000cc +/* End of Xbridge only */ + +/* used only in ide, but defined here within the reserved portion */ +/* of the widget0 address space (before 0xf4) */ +#define XBOW_WID_UNDEF 0xe4 + +/* xbow link register set base, legal value for x is 0x8..0xf */ +#define XB_LINK_BASE 0x100 +#define XB_LINK_OFFSET 0x40 +#define XB_LINK_REG_BASE(x) (XB_LINK_BASE + ((x) & (MAX_XBOW_PORTS - 1)) * XB_LINK_OFFSET) + +#define XB_LINK_IBUF_FLUSH(x) (XB_LINK_REG_BASE(x) + 0x4) +#define XB_LINK_CTRL(x) (XB_LINK_REG_BASE(x) + 0xc) +#define XB_LINK_STATUS(x) (XB_LINK_REG_BASE(x) + 0x14) +#define XB_LINK_ARB_UPPER(x) (XB_LINK_REG_BASE(x) + 0x1c) +#define XB_LINK_ARB_LOWER(x) (XB_LINK_REG_BASE(x) + 0x24) +#define XB_LINK_STATUS_CLR(x) (XB_LINK_REG_BASE(x) + 0x2c) +#define XB_LINK_RESET(x) (XB_LINK_REG_BASE(x) + 0x34) +#define XB_LINK_AUX_STATUS(x) (XB_LINK_REG_BASE(x) + 0x3c) + +/* link_control(x) */ +#define XB_CTRL_LINKALIVE_IE 0x80000000 /* link comes alive */ + /* reserved: 0x40000000 */ +#define XB_CTRL_PERF_CTR_MODE_MSK 0x30000000 /* perf counter mode */ +#define XB_CTRL_IBUF_LEVEL_MSK 0x0e000000 /* input packet buffer level */ +#define XB_CTRL_8BIT_MODE 0x01000000 /* force link into 8 bit mode */ +#define XB_CTRL_BAD_LLP_PKT 0x00800000 /* force bad LLP packet */ +#define XB_CTRL_WIDGET_CR_MSK 0x007c0000 /* LLP widget credit mask */ +#define XB_CTRL_WIDGET_CR_SHFT 18 /* LLP widget credit shift */ +#define XB_CTRL_ILLEGAL_DST_IE 0x00020000 /* illegal destination */ +#define XB_CTRL_OALLOC_IBUF_IE 0x00010000 /* overallocated input buffer */ + /* reserved: 0x0000fe00 */ +#define XB_CTRL_BNDWDTH_ALLOC_IE 0x00000100 /* bandwidth alloc */ +#define XB_CTRL_RCV_CNT_OFLOW_IE 0x00000080 /* rcv retry overflow */ +#define XB_CTRL_XMT_CNT_OFLOW_IE 0x00000040 /* xmt retry overflow */ +#define XB_CTRL_XMT_MAX_RTRY_IE 0x00000020 /* max transmit retry */ +#define XB_CTRL_RCV_IE 0x00000010 /* receive */ +#define XB_CTRL_XMT_RTRY_IE 0x00000008 /* transmit retry */ + /* reserved: 0x00000004 */ +#define XB_CTRL_MAXREQ_TOUT_IE 0x00000002 /* maximum request timeout */ +#define XB_CTRL_SRC_TOUT_IE 0x00000001 /* source timeout */ + +/* link_status(x) */ +#define XB_STAT_LINKALIVE XB_CTRL_LINKALIVE_IE + /* reserved: 0x7ff80000 */ +#define XB_STAT_MULTI_ERR 0x00040000 /* multi error */ +#define XB_STAT_ILLEGAL_DST_ERR XB_CTRL_ILLEGAL_DST_IE +#define XB_STAT_OALLOC_IBUF_ERR XB_CTRL_OALLOC_IBUF_IE +#define XB_STAT_BNDWDTH_ALLOC_ID_MSK 0x0000ff00 /* port bitmask */ +#define XB_STAT_RCV_CNT_OFLOW_ERR XB_CTRL_RCV_CNT_OFLOW_IE +#define XB_STAT_XMT_CNT_OFLOW_ERR XB_CTRL_XMT_CNT_OFLOW_IE +#define XB_STAT_XMT_MAX_RTRY_ERR XB_CTRL_XMT_MAX_RTRY_IE +#define XB_STAT_RCV_ERR XB_CTRL_RCV_IE +#define XB_STAT_XMT_RTRY_ERR XB_CTRL_XMT_RTRY_IE + /* reserved: 0x00000004 */ +#define XB_STAT_MAXREQ_TOUT_ERR XB_CTRL_MAXREQ_TOUT_IE +#define XB_STAT_SRC_TOUT_ERR XB_CTRL_SRC_TOUT_IE + +/* link_aux_status(x) */ +#define XB_AUX_STAT_RCV_CNT 0xff000000 +#define XB_AUX_STAT_XMT_CNT 0x00ff0000 +#define XB_AUX_STAT_TOUT_DST 0x0000ff00 +#define XB_AUX_LINKFAIL_RST_BAD 0x00000040 +#define XB_AUX_STAT_PRESENT 0x00000020 +#define XB_AUX_STAT_PORT_WIDTH 0x00000010 + /* reserved: 0x0000000f */ + +/* + * link_arb_upper/link_arb_lower(x), (reg) should be the link_arb_upper + * register if (x) is 0x8..0xb, link_arb_lower if (x) is 0xc..0xf + */ +#define XB_ARB_GBR_MSK 0x1f +#define XB_ARB_RR_MSK 0x7 +#define XB_ARB_GBR_SHFT(x) (((x) & 0x3) * 8) +#define XB_ARB_RR_SHFT(x) (((x) & 0x3) * 8 + 5) +#define XB_ARB_GBR_CNT(reg,x) ((reg) >> XB_ARB_GBR_SHFT(x) & XB_ARB_GBR_MSK) +#define XB_ARB_RR_CNT(reg,x) ((reg) >> XB_ARB_RR_SHFT(x) & XB_ARB_RR_MSK) + +/* XBOW_WID_STAT */ +#define XB_WID_STAT_LINK_INTR_SHFT (24) +#define XB_WID_STAT_LINK_INTR_MASK (0xFF << XB_WID_STAT_LINK_INTR_SHFT) +#define XB_WID_STAT_LINK_INTR(x) (0x1 << (((x)&7) + XB_WID_STAT_LINK_INTR_SHFT)) +#define XB_WID_STAT_WIDGET0_INTR 0x00800000 +#define XB_WID_STAT_SRCID_MASK 0x000003c0 /* Xbridge only */ +#define XB_WID_STAT_REG_ACC_ERR 0x00000020 +#define XB_WID_STAT_RECV_TOUT 0x00000010 /* Xbridge only */ +#define XB_WID_STAT_ARB_TOUT 0x00000008 /* Xbridge only */ +#define XB_WID_STAT_XTALK_ERR 0x00000004 +#define XB_WID_STAT_DST_TOUT 0x00000002 /* Xbridge only */ +#define XB_WID_STAT_MULTI_ERR 0x00000001 + +#define XB_WID_STAT_SRCID_SHFT 6 + +/* XBOW_WID_CONTROL */ +#define XB_WID_CTRL_REG_ACC_IE XB_WID_STAT_REG_ACC_ERR +#define XB_WID_CTRL_RECV_TOUT XB_WID_STAT_RECV_TOUT +#define XB_WID_CTRL_ARB_TOUT XB_WID_STAT_ARB_TOUT +#define XB_WID_CTRL_XTALK_IE XB_WID_STAT_XTALK_ERR + +/* XBOW_WID_INT_UPPER */ +/* defined in xwidget.h for WIDGET_INTDEST_UPPER_ADDR */ + +/* XBOW WIDGET part number, in the ID register */ +#define XBOW_WIDGET_PART_NUM 0x0 /* crossbow */ +#define XXBOW_WIDGET_PART_NUM 0xd000 /* Xbridge */ +#define XBOW_WIDGET_MFGR_NUM 0x0 +#define XXBOW_WIDGET_MFGR_NUM 0x0 +#define PXBOW_WIDGET_PART_NUM 0xd100 /* PIC */ + +#define XBOW_REV_1_0 0x1 /* xbow rev 1.0 is "1" */ +#define XBOW_REV_1_1 0x2 /* xbow rev 1.1 is "2" */ +#define XBOW_REV_1_2 0x3 /* xbow rev 1.2 is "3" */ +#define XBOW_REV_1_3 0x4 /* xbow rev 1.3 is "4" */ +#define XBOW_REV_2_0 0x5 /* xbow rev 2.0 is "5" */ + +#define XXBOW_PART_REV_1_0 (XXBOW_WIDGET_PART_NUM << 4 | 0x1 ) +#define XXBOW_PART_REV_2_0 (XXBOW_WIDGET_PART_NUM << 4 | 0x2 ) + +/* XBOW_WID_ARB_RELOAD */ +#define XBOW_WID_ARB_RELOAD_INT 0x3f /* GBR reload interval */ + +#define IS_XBRIDGE_XBOW(wid) \ + (XWIDGET_PART_NUM(wid) == XXBOW_WIDGET_PART_NUM && \ + XWIDGET_MFG_NUM(wid) == XXBOW_WIDGET_MFGR_NUM) + +#define IS_PIC_XBOW(wid) \ + (XWIDGET_PART_NUM(wid) == PXBOW_WIDGET_PART_NUM && \ + XWIDGET_MFG_NUM(wid) == XXBOW_WIDGET_MFGR_NUM) + +#define XBOW_WAR_ENABLED(pv, widid) ((1 << XWIDGET_REV_NUM(widid)) & pv) + +#endif /* _ASM_IA64_SN_XTALK_XBOW_H */ diff --git a/arch/ia64/sn/include/xtalk/xwidgetdev.h b/arch/ia64/sn/include/xtalk/xwidgetdev.h new file mode 100644 index 00000000000..c5f4bc5cc03 --- /dev/null +++ b/arch/ia64/sn/include/xtalk/xwidgetdev.h @@ -0,0 +1,70 @@ +/* + * 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. + * + * Copyright (C) 1992-1997,2000-2003 Silicon Graphics, Inc. All Rights Reserved. + */ +#ifndef _ASM_IA64_SN_XTALK_XWIDGET_H +#define _ASM_IA64_SN_XTALK_XWIDGET_H + +/* WIDGET_ID */ +#define WIDGET_REV_NUM 0xf0000000 +#define WIDGET_PART_NUM 0x0ffff000 +#define WIDGET_MFG_NUM 0x00000ffe +#define WIDGET_REV_NUM_SHFT 28 +#define WIDGET_PART_NUM_SHFT 12 +#define WIDGET_MFG_NUM_SHFT 1 + +#define XWIDGET_PART_NUM(widgetid) (((widgetid) & WIDGET_PART_NUM) >> WIDGET_PART_NUM_SHFT) +#define XWIDGET_REV_NUM(widgetid) (((widgetid) & WIDGET_REV_NUM) >> WIDGET_REV_NUM_SHFT) +#define XWIDGET_MFG_NUM(widgetid) (((widgetid) & WIDGET_MFG_NUM) >> WIDGET_MFG_NUM_SHFT) +#define XWIDGET_PART_REV_NUM(widgetid) ((XWIDGET_PART_NUM(widgetid) << 4) | \ + XWIDGET_REV_NUM(widgetid)) +#define XWIDGET_PART_REV_NUM_REV(partrev) (partrev & 0xf) + +/* widget configuration registers */ +struct widget_cfg{ + uint32_t w_id; /* 0x04 */ + uint32_t w_pad_0; /* 0x00 */ + uint32_t w_status; /* 0x0c */ + uint32_t w_pad_1; /* 0x08 */ + uint32_t w_err_upper_addr; /* 0x14 */ + uint32_t w_pad_2; /* 0x10 */ + uint32_t w_err_lower_addr; /* 0x1c */ + uint32_t w_pad_3; /* 0x18 */ + uint32_t w_control; /* 0x24 */ + uint32_t w_pad_4; /* 0x20 */ + uint32_t w_req_timeout; /* 0x2c */ + uint32_t w_pad_5; /* 0x28 */ + uint32_t w_intdest_upper_addr; /* 0x34 */ + uint32_t w_pad_6; /* 0x30 */ + uint32_t w_intdest_lower_addr; /* 0x3c */ + uint32_t w_pad_7; /* 0x38 */ + uint32_t w_err_cmd_word; /* 0x44 */ + uint32_t w_pad_8; /* 0x40 */ + uint32_t w_llp_cfg; /* 0x4c */ + uint32_t w_pad_9; /* 0x48 */ + uint32_t w_tflush; /* 0x54 */ + uint32_t w_pad_10; /* 0x50 */ +}; + +/* + * Crosstalk Widget Hardware Identification, as defined in the Crosstalk spec. + */ +struct xwidget_hwid{ + int mfg_num; + int rev_num; + int part_num; +}; + +struct xwidget_info{ + + struct xwidget_hwid xwi_hwid; /* Widget Identification */ + char xwi_masterxid; /* Hub's Widget Port Number */ + void *xwi_hubinfo; /* Hub's provider private info */ + uint64_t *xwi_hub_provider; /* prom provider functions */ + void *xwi_vertex; +}; + +#endif /* _ASM_IA64_SN_XTALK_XWIDGET_H */ diff --git a/arch/ia64/sn/kernel/Makefile b/arch/ia64/sn/kernel/Makefile new file mode 100644 index 00000000000..6c7f4d9e8ea --- /dev/null +++ b/arch/ia64/sn/kernel/Makefile @@ -0,0 +1,12 @@ +# arch/ia64/sn/kernel/Makefile +# +# 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. +# +# Copyright (C) 1999,2001-2003 Silicon Graphics, Inc. All Rights Reserved. +# + +obj-y += setup.o bte.o bte_error.o irq.o mca.o idle.o \ + huberror.o io_init.o iomv.o klconflib.o sn2/ +obj-$(CONFIG_IA64_GENERIC) += machvec.o diff --git a/arch/ia64/sn/kernel/bte.c b/arch/ia64/sn/kernel/bte.c new file mode 100644 index 00000000000..ce0bc4085ea --- /dev/null +++ b/arch/ia64/sn/kernel/bte.c @@ -0,0 +1,453 @@ +/* + * 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. + * + * Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/sn/nodepda.h> +#include <asm/sn/addrs.h> +#include <asm/sn/arch.h> +#include <asm/sn/sn_cpuid.h> +#include <asm/sn/pda.h> +#include <asm/sn/shubio.h> +#include <asm/nodedata.h> +#include <asm/delay.h> + +#include <linux/bootmem.h> +#include <linux/string.h> +#include <linux/sched.h> + +#include <asm/sn/bte.h> + +#ifndef L1_CACHE_MASK +#define L1_CACHE_MASK (L1_CACHE_BYTES - 1) +#endif + +/* two interfaces on two btes */ +#define MAX_INTERFACES_TO_TRY 4 + +static struct bteinfo_s *bte_if_on_node(nasid_t nasid, int interface) +{ + nodepda_t *tmp_nodepda; + + tmp_nodepda = NODEPDA(nasid_to_cnodeid(nasid)); + return &tmp_nodepda->bte_if[interface]; + +} + +/************************************************************************ + * Block Transfer Engine copy related functions. + * + ***********************************************************************/ + +/* + * bte_copy(src, dest, len, mode, notification) + * + * Use the block transfer engine to move kernel memory from src to dest + * using the assigned mode. + * + * Paramaters: + * src - physical address of the transfer source. + * dest - physical address of the transfer destination. + * len - number of bytes to transfer from source to dest. + * mode - hardware defined. See reference information + * for IBCT0/1 in the SHUB Programmers Reference + * notification - kernel virtual address of the notification cache + * line. If NULL, the default is used and + * the bte_copy is synchronous. + * + * NOTE: This function requires src, dest, and len to + * be cacheline aligned. + */ +bte_result_t bte_copy(u64 src, u64 dest, u64 len, u64 mode, void *notification) +{ + u64 transfer_size; + u64 transfer_stat; + struct bteinfo_s *bte; + bte_result_t bte_status; + unsigned long irq_flags; + unsigned long itc_end = 0; + struct bteinfo_s *btes_to_try[MAX_INTERFACES_TO_TRY]; + int bte_if_index; + int bte_pri, bte_sec; + + BTE_PRINTK(("bte_copy(0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%p)\n", + src, dest, len, mode, notification)); + + if (len == 0) { + return BTE_SUCCESS; + } + + BUG_ON((len & L1_CACHE_MASK) || + (src & L1_CACHE_MASK) || (dest & L1_CACHE_MASK)); + BUG_ON(!(len < ((BTE_LEN_MASK + 1) << L1_CACHE_SHIFT))); + + /* CPU 0 (per node) tries bte0 first, CPU 1 try bte1 first */ + if (cpuid_to_subnode(smp_processor_id()) == 0) { + bte_pri = 0; + bte_sec = 1; + } else { + bte_pri = 1; + bte_sec = 0; + } + + if (mode & BTE_USE_DEST) { + /* try remote then local */ + btes_to_try[0] = bte_if_on_node(NASID_GET(dest), bte_pri); + btes_to_try[1] = bte_if_on_node(NASID_GET(dest), bte_sec); + if (mode & BTE_USE_ANY) { + btes_to_try[2] = bte_if_on_node(get_nasid(), bte_pri); + btes_to_try[3] = bte_if_on_node(get_nasid(), bte_sec); + } else { + btes_to_try[2] = NULL; + btes_to_try[3] = NULL; + } + } else { + /* try local then remote */ + btes_to_try[0] = bte_if_on_node(get_nasid(), bte_pri); + btes_to_try[1] = bte_if_on_node(get_nasid(), bte_sec); + if (mode & BTE_USE_ANY) { + btes_to_try[2] = bte_if_on_node(NASID_GET(dest), bte_pri); + btes_to_try[3] = bte_if_on_node(NASID_GET(dest), bte_sec); + } else { + btes_to_try[2] = NULL; + btes_to_try[3] = NULL; + } + } + +retry_bteop: + do { + local_irq_save(irq_flags); + + bte_if_index = 0; + + /* Attempt to lock one of the BTE interfaces. */ + while (bte_if_index < MAX_INTERFACES_TO_TRY) { + bte = btes_to_try[bte_if_index++]; + + if (bte == NULL) { + continue; + } + + if (spin_trylock(&bte->spinlock)) { + if (!(*bte->most_rcnt_na & BTE_WORD_AVAILABLE) || + (BTE_LNSTAT_LOAD(bte) & BTE_ACTIVE)) { + /* Got the lock but BTE still busy */ + spin_unlock(&bte->spinlock); + } else { + /* we got the lock and it's not busy */ + break; + } + } + bte = NULL; + } + + if (bte != NULL) { + break; + } + + local_irq_restore(irq_flags); + + if (!(mode & BTE_WACQUIRE)) { + return BTEFAIL_NOTAVAIL; + } + } while (1); + + if (notification == NULL) { + /* User does not want to be notified. */ + bte->most_rcnt_na = &bte->notify; + } else { + bte->most_rcnt_na = notification; + } + + /* Calculate the number of cache lines to transfer. */ + transfer_size = ((len >> L1_CACHE_SHIFT) & BTE_LEN_MASK); + + /* Initialize the notification to a known value. */ + *bte->most_rcnt_na = BTE_WORD_BUSY; + + /* Set the status reg busy bit and transfer length */ + BTE_PRINTKV(("IBLS = 0x%lx\n", IBLS_BUSY | transfer_size)); + BTE_LNSTAT_STORE(bte, IBLS_BUSY | transfer_size); + + /* Set the source and destination registers */ + BTE_PRINTKV(("IBSA = 0x%lx)\n", (TO_PHYS(src)))); + BTE_SRC_STORE(bte, TO_PHYS(src)); + BTE_PRINTKV(("IBDA = 0x%lx)\n", (TO_PHYS(dest)))); + BTE_DEST_STORE(bte, TO_PHYS(dest)); + + /* Set the notification register */ + BTE_PRINTKV(("IBNA = 0x%lx)\n", + TO_PHYS(ia64_tpa((unsigned long)bte->most_rcnt_na)))); + BTE_NOTIF_STORE(bte, + TO_PHYS(ia64_tpa((unsigned long)bte->most_rcnt_na))); + + /* Initiate the transfer */ + BTE_PRINTK(("IBCT = 0x%lx)\n", BTE_VALID_MODE(mode))); + BTE_CTRL_STORE(bte, BTE_VALID_MODE(mode)); + + itc_end = ia64_get_itc() + (40000000 * local_cpu_data->cyc_per_usec); + + spin_unlock_irqrestore(&bte->spinlock, irq_flags); + + if (notification != NULL) { + return BTE_SUCCESS; + } + + while ((transfer_stat = *bte->most_rcnt_na) == BTE_WORD_BUSY) { + if (ia64_get_itc() > itc_end) { + BTE_PRINTK(("BTE timeout nasid 0x%x bte%d IBLS = 0x%lx na 0x%lx\n", + NASID_GET(bte->bte_base_addr), bte->bte_num, + BTE_LNSTAT_LOAD(bte), *bte->most_rcnt_na) ); + bte->bte_error_count++; + bte->bh_error = IBLS_ERROR; + bte_error_handler((unsigned long)NODEPDA(bte->bte_cnode)); + *bte->most_rcnt_na = BTE_WORD_AVAILABLE; + goto retry_bteop; + } + } + + BTE_PRINTKV((" Delay Done. IBLS = 0x%lx, most_rcnt_na = 0x%lx\n", + BTE_LNSTAT_LOAD(bte), *bte->most_rcnt_na)); + + if (transfer_stat & IBLS_ERROR) { + bte_status = transfer_stat & ~IBLS_ERROR; + } else { + bte_status = BTE_SUCCESS; + } + *bte->most_rcnt_na = BTE_WORD_AVAILABLE; + + BTE_PRINTK(("Returning status is 0x%lx and most_rcnt_na is 0x%lx\n", + BTE_LNSTAT_LOAD(bte), *bte->most_rcnt_na)); + + return bte_status; +} + +EXPORT_SYMBOL(bte_copy); + +/* + * bte_unaligned_copy(src, dest, len, mode) + * + * use the block transfer engine to move kernel + * memory from src to dest using the assigned mode. + * + * Paramaters: + * src - physical address of the transfer source. + * dest - physical address of the transfer destination. + * len - number of bytes to transfer from source to dest. + * mode - hardware defined. See reference information + * for IBCT0/1 in the SGI documentation. + * + * NOTE: If the source, dest, and len are all cache line aligned, + * then it would be _FAR_ preferrable to use bte_copy instead. + */ +bte_result_t bte_unaligned_copy(u64 src, u64 dest, u64 len, u64 mode) +{ + int destFirstCacheOffset; + u64 headBteSource; + u64 headBteLen; + u64 headBcopySrcOffset; + u64 headBcopyDest; + u64 headBcopyLen; + u64 footBteSource; + u64 footBteLen; + u64 footBcopyDest; + u64 footBcopyLen; + bte_result_t rv; + char *bteBlock, *bteBlock_unaligned; + + if (len == 0) { + return BTE_SUCCESS; + } + + /* temporary buffer used during unaligned transfers */ + bteBlock_unaligned = kmalloc(len + 3 * L1_CACHE_BYTES, + GFP_KERNEL | GFP_DMA); + if (bteBlock_unaligned == NULL) { + return BTEFAIL_NOTAVAIL; + } + bteBlock = (char *)L1_CACHE_ALIGN((u64) bteBlock_unaligned); + + headBcopySrcOffset = src & L1_CACHE_MASK; + destFirstCacheOffset = dest & L1_CACHE_MASK; + + /* + * At this point, the transfer is broken into + * (up to) three sections. The first section is + * from the start address to the first physical + * cache line, the second is from the first physical + * cache line to the last complete cache line, + * and the third is from the last cache line to the + * end of the buffer. The first and third sections + * are handled by bte copying into a temporary buffer + * and then bcopy'ing the necessary section into the + * final location. The middle section is handled with + * a standard bte copy. + * + * One nasty exception to the above rule is when the + * source and destination are not symetrically + * mis-aligned. If the source offset from the first + * cache line is different from the destination offset, + * we make the first section be the entire transfer + * and the bcopy the entire block into place. + */ + if (headBcopySrcOffset == destFirstCacheOffset) { + + /* + * Both the source and destination are the same + * distance from a cache line boundary so we can + * use the bte to transfer the bulk of the + * data. + */ + headBteSource = src & ~L1_CACHE_MASK; + headBcopyDest = dest; + if (headBcopySrcOffset) { + headBcopyLen = + (len > + (L1_CACHE_BYTES - + headBcopySrcOffset) ? L1_CACHE_BYTES + - headBcopySrcOffset : len); + headBteLen = L1_CACHE_BYTES; + } else { + headBcopyLen = 0; + headBteLen = 0; + } + + if (len > headBcopyLen) { + footBcopyLen = (len - headBcopyLen) & L1_CACHE_MASK; + footBteLen = L1_CACHE_BYTES; + + footBteSource = src + len - footBcopyLen; + footBcopyDest = dest + len - footBcopyLen; + + if (footBcopyDest == (headBcopyDest + headBcopyLen)) { + /* + * We have two contigous bcopy + * blocks. Merge them. + */ + headBcopyLen += footBcopyLen; + headBteLen += footBteLen; + } else if (footBcopyLen > 0) { + rv = bte_copy(footBteSource, + ia64_tpa((unsigned long)bteBlock), + footBteLen, mode, NULL); + if (rv != BTE_SUCCESS) { + kfree(bteBlock_unaligned); + return rv; + } + + memcpy(__va(footBcopyDest), + (char *)bteBlock, footBcopyLen); + } + } else { + footBcopyLen = 0; + footBteLen = 0; + } + + if (len > (headBcopyLen + footBcopyLen)) { + /* now transfer the middle. */ + rv = bte_copy((src + headBcopyLen), + (dest + + headBcopyLen), + (len - headBcopyLen - + footBcopyLen), mode, NULL); + if (rv != BTE_SUCCESS) { + kfree(bteBlock_unaligned); + return rv; + } + + } + } else { + + /* + * The transfer is not symetric, we will + * allocate a buffer large enough for all the + * data, bte_copy into that buffer and then + * bcopy to the destination. + */ + + /* Add the leader from source */ + headBteLen = len + (src & L1_CACHE_MASK); + /* Add the trailing bytes from footer. */ + headBteLen += L1_CACHE_BYTES - (headBteLen & L1_CACHE_MASK); + headBteSource = src & ~L1_CACHE_MASK; + headBcopySrcOffset = src & L1_CACHE_MASK; + headBcopyDest = dest; + headBcopyLen = len; + } + + if (headBcopyLen > 0) { + rv = bte_copy(headBteSource, + ia64_tpa((unsigned long)bteBlock), headBteLen, + mode, NULL); + if (rv != BTE_SUCCESS) { + kfree(bteBlock_unaligned); + return rv; + } + + memcpy(__va(headBcopyDest), ((char *)bteBlock + + headBcopySrcOffset), headBcopyLen); + } + kfree(bteBlock_unaligned); + return BTE_SUCCESS; +} + +EXPORT_SYMBOL(bte_unaligned_copy); + +/************************************************************************ + * Block Transfer Engine initialization functions. + * + ***********************************************************************/ + +/* + * bte_init_node(nodepda, cnode) + * + * Initialize the nodepda structure with BTE base addresses and + * spinlocks. + */ +void bte_init_node(nodepda_t * mynodepda, cnodeid_t cnode) +{ + int i; + + /* + * Indicate that all the block transfer engines on this node + * are available. + */ + + /* + * Allocate one bte_recover_t structure per node. It holds + * the recovery lock for node. All the bte interface structures + * will point at this one bte_recover structure to get the lock. + */ + spin_lock_init(&mynodepda->bte_recovery_lock); + init_timer(&mynodepda->bte_recovery_timer); + mynodepda->bte_recovery_timer.function = bte_error_handler; + mynodepda->bte_recovery_timer.data = (unsigned long)mynodepda; + + for (i = 0; i < BTES_PER_NODE; i++) { + /* Which link status register should we use? */ + unsigned long link_status = (i == 0 ? IIO_IBLS0 : IIO_IBLS1); + mynodepda->bte_if[i].bte_base_addr = (u64 *) + REMOTE_HUB_ADDR(cnodeid_to_nasid(cnode), link_status); + + /* + * Initialize the notification and spinlock + * so the first transfer can occur. + */ + mynodepda->bte_if[i].most_rcnt_na = + &(mynodepda->bte_if[i].notify); + mynodepda->bte_if[i].notify = BTE_WORD_AVAILABLE; + spin_lock_init(&mynodepda->bte_if[i].spinlock); + + mynodepda->bte_if[i].bte_cnode = cnode; + mynodepda->bte_if[i].bte_error_count = 0; + mynodepda->bte_if[i].bte_num = i; + mynodepda->bte_if[i].cleanup_active = 0; + mynodepda->bte_if[i].bh_error = 0; + } + +} diff --git a/arch/ia64/sn/kernel/bte_error.c b/arch/ia64/sn/kernel/bte_error.c new file mode 100644 index 00000000000..fd104312c6b --- /dev/null +++ b/arch/ia64/sn/kernel/bte_error.c @@ -0,0 +1,198 @@ +/* + * 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. + * + * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved. + */ + +#include <linux/types.h> +#include <asm/sn/sn_sal.h> +#include "ioerror.h" +#include <asm/sn/addrs.h> +#include <asm/sn/shubio.h> +#include <asm/sn/geo.h> +#include "xtalk/xwidgetdev.h" +#include "xtalk/hubdev.h" +#include <asm/sn/bte.h> +#include <asm/param.h> + +/* + * Bte error handling is done in two parts. The first captures + * any crb related errors. Since there can be multiple crbs per + * interface and multiple interfaces active, we need to wait until + * all active crbs are completed. This is the first job of the + * second part error handler. When all bte related CRBs are cleanly + * completed, it resets the interfaces and gets them ready for new + * transfers to be queued. + */ + +void bte_error_handler(unsigned long); + +/* + * Wait until all BTE related CRBs are completed + * and then reset the interfaces. + */ +void bte_error_handler(unsigned long _nodepda) +{ + struct nodepda_s *err_nodepda = (struct nodepda_s *)_nodepda; + spinlock_t *recovery_lock = &err_nodepda->bte_recovery_lock; + struct timer_list *recovery_timer = &err_nodepda->bte_recovery_timer; + nasid_t nasid; + int i; + int valid_crbs; + unsigned long irq_flags; + volatile u64 *notify; + bte_result_t bh_error; + ii_imem_u_t imem; /* II IMEM Register */ + ii_icrb0_d_u_t icrbd; /* II CRB Register D */ + ii_ibcr_u_t ibcr; + ii_icmr_u_t icmr; + ii_ieclr_u_t ieclr; + + BTE_PRINTK(("bte_error_handler(%p) - %d\n", err_nodepda, + smp_processor_id())); + + spin_lock_irqsave(recovery_lock, irq_flags); + + if ((err_nodepda->bte_if[0].bh_error == BTE_SUCCESS) && + (err_nodepda->bte_if[1].bh_error == BTE_SUCCESS)) { + BTE_PRINTK(("eh:%p:%d Nothing to do.\n", err_nodepda, + smp_processor_id())); + spin_unlock_irqrestore(recovery_lock, irq_flags); + return; + } + /* + * Lock all interfaces on this node to prevent new transfers + * from being queued. + */ + for (i = 0; i < BTES_PER_NODE; i++) { + if (err_nodepda->bte_if[i].cleanup_active) { + continue; + } + spin_lock(&err_nodepda->bte_if[i].spinlock); + BTE_PRINTK(("eh:%p:%d locked %d\n", err_nodepda, + smp_processor_id(), i)); + err_nodepda->bte_if[i].cleanup_active = 1; + } + + /* Determine information about our hub */ + nasid = cnodeid_to_nasid(err_nodepda->bte_if[0].bte_cnode); + + /* + * A BTE transfer can use multiple CRBs. We need to make sure + * that all the BTE CRBs are complete (or timed out) before + * attempting to clean up the error. Resetting the BTE while + * there are still BTE CRBs active will hang the BTE. + * We should look at all the CRBs to see if they are allocated + * to the BTE and see if they are still active. When none + * are active, we can continue with the cleanup. + * + * We also want to make sure that the local NI port is up. + * When a router resets the NI port can go down, while it + * goes through the LLP handshake, but then comes back up. + */ + icmr.ii_icmr_regval = REMOTE_HUB_L(nasid, IIO_ICMR); + if (icmr.ii_icmr_fld_s.i_crb_mark != 0) { + /* + * There are errors which still need to be cleaned up by + * hubiio_crb_error_handler + */ + mod_timer(recovery_timer, HZ * 5); + BTE_PRINTK(("eh:%p:%d Marked Giving up\n", err_nodepda, + smp_processor_id())); + spin_unlock_irqrestore(recovery_lock, irq_flags); + return; + } + if (icmr.ii_icmr_fld_s.i_crb_vld != 0) { + + valid_crbs = icmr.ii_icmr_fld_s.i_crb_vld; + + for (i = 0; i < IIO_NUM_CRBS; i++) { + if (!((1 << i) & valid_crbs)) { + /* This crb was not marked as valid, ignore */ + continue; + } + icrbd.ii_icrb0_d_regval = + REMOTE_HUB_L(nasid, IIO_ICRB_D(i)); + if (icrbd.d_bteop) { + mod_timer(recovery_timer, HZ * 5); + BTE_PRINTK(("eh:%p:%d Valid %d, Giving up\n", + err_nodepda, smp_processor_id(), + i)); + spin_unlock_irqrestore(recovery_lock, + irq_flags); + return; + } + } + } + + BTE_PRINTK(("eh:%p:%d Cleaning up\n", err_nodepda, smp_processor_id())); + /* Reenable both bte interfaces */ + imem.ii_imem_regval = REMOTE_HUB_L(nasid, IIO_IMEM); + imem.ii_imem_fld_s.i_b0_esd = imem.ii_imem_fld_s.i_b1_esd = 1; + REMOTE_HUB_S(nasid, IIO_IMEM, imem.ii_imem_regval); + + /* Clear BTE0/1 error bits */ + ieclr.ii_ieclr_regval = 0; + if (err_nodepda->bte_if[0].bh_error != BTE_SUCCESS) + ieclr.ii_ieclr_fld_s.i_e_bte_0 = 1; + if (err_nodepda->bte_if[1].bh_error != BTE_SUCCESS) + ieclr.ii_ieclr_fld_s.i_e_bte_1 = 1; + REMOTE_HUB_S(nasid, IIO_IECLR, ieclr.ii_ieclr_regval); + + /* Reinitialize both BTE state machines. */ + ibcr.ii_ibcr_regval = REMOTE_HUB_L(nasid, IIO_IBCR); + ibcr.ii_ibcr_fld_s.i_soft_reset = 1; + REMOTE_HUB_S(nasid, IIO_IBCR, ibcr.ii_ibcr_regval); + + for (i = 0; i < BTES_PER_NODE; i++) { + bh_error = err_nodepda->bte_if[i].bh_error; + if (bh_error != BTE_SUCCESS) { + /* There is an error which needs to be notified */ + notify = err_nodepda->bte_if[i].most_rcnt_na; + BTE_PRINTK(("cnode %d bte %d error=0x%lx\n", + err_nodepda->bte_if[i].bte_cnode, + err_nodepda->bte_if[i].bte_num, + IBLS_ERROR | (u64) bh_error)); + *notify = IBLS_ERROR | bh_error; + err_nodepda->bte_if[i].bh_error = BTE_SUCCESS; + } + + err_nodepda->bte_if[i].cleanup_active = 0; + BTE_PRINTK(("eh:%p:%d Unlocked %d\n", err_nodepda, + smp_processor_id(), i)); + spin_unlock(&err_nodepda->bte_if[i].spinlock); + } + + del_timer(recovery_timer); + + spin_unlock_irqrestore(recovery_lock, irq_flags); +} + +/* + * First part error handler. This is called whenever any error CRB interrupt + * is generated by the II. + */ +void +bte_crb_error_handler(cnodeid_t cnode, int btenum, + int crbnum, ioerror_t * ioe, int bteop) +{ + struct bteinfo_s *bte; + + + bte = &(NODEPDA(cnode)->bte_if[btenum]); + + /* + * The caller has already figured out the error type, we save that + * in the bte handle structure for the thread excercising the + * interface to consume. + */ + bte->bh_error = ioe->ie_errortype + BTEFAIL_OFFSET; + bte->bte_error_count++; + + BTE_PRINTK(("Got an error on cnode %d bte %d: HW error type 0x%x\n", + bte->bte_cnode, bte->bte_num, ioe->ie_errortype)); + bte_error_handler((unsigned long) NODEPDA(cnode)); +} + diff --git a/arch/ia64/sn/kernel/huberror.c b/arch/ia64/sn/kernel/huberror.c new file mode 100644 index 00000000000..2bdf684c506 --- /dev/null +++ b/arch/ia64/sn/kernel/huberror.c @@ -0,0 +1,201 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000,2002-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <asm/delay.h> +#include <asm/sn/sn_sal.h> +#include "ioerror.h" +#include <asm/sn/addrs.h> +#include <asm/sn/shubio.h> +#include <asm/sn/geo.h> +#include "xtalk/xwidgetdev.h" +#include "xtalk/hubdev.h" +#include <asm/sn/bte.h> + +void hubiio_crb_error_handler(struct hubdev_info *hubdev_info); +extern void bte_crb_error_handler(cnodeid_t, int, int, ioerror_t *, + int); +static irqreturn_t hub_eint_handler(int irq, void *arg, struct pt_regs *ep) +{ + struct hubdev_info *hubdev_info; + struct ia64_sal_retval ret_stuff; + nasid_t nasid; + + ret_stuff.status = 0; + ret_stuff.v0 = 0; + hubdev_info = (struct hubdev_info *)arg; + nasid = hubdev_info->hdi_nasid; + SAL_CALL_NOLOCK(ret_stuff, SN_SAL_HUB_ERROR_INTERRUPT, + (u64) nasid, 0, 0, 0, 0, 0, 0); + + if ((int)ret_stuff.v0) + panic("hubii_eint_handler(): Fatal TIO Error"); + + if (!(nasid & 1)) /* Not a TIO, handle CRB errors */ + (void)hubiio_crb_error_handler(hubdev_info); + + return IRQ_HANDLED; +} + +/* + * Free the hub CRB "crbnum" which encountered an error. + * Assumption is, error handling was successfully done, + * and we now want to return the CRB back to Hub for normal usage. + * + * In order to free the CRB, all that's needed is to de-allocate it + * + * Assumption: + * No other processor is mucking around with the hub control register. + * So, upper layer has to single thread this. + */ +void hubiio_crb_free(struct hubdev_info *hubdev_info, int crbnum) +{ + ii_icrb0_b_u_t icrbb; + + /* + * The hardware does NOT clear the mark bit, so it must get cleared + * here to be sure the error is not processed twice. + */ + icrbb.ii_icrb0_b_regval = REMOTE_HUB_L(hubdev_info->hdi_nasid, + IIO_ICRB_B(crbnum)); + icrbb.b_mark = 0; + REMOTE_HUB_S(hubdev_info->hdi_nasid, IIO_ICRB_B(crbnum), + icrbb.ii_icrb0_b_regval); + /* + * Deallocate the register wait till hub indicates it's done. + */ + REMOTE_HUB_S(hubdev_info->hdi_nasid, IIO_ICDR, (IIO_ICDR_PND | crbnum)); + while (REMOTE_HUB_L(hubdev_info->hdi_nasid, IIO_ICDR) & IIO_ICDR_PND) + udelay(1); + +} + +/* + * hubiio_crb_error_handler + * + * This routine gets invoked when a hub gets an error + * interrupt. So, the routine is running in interrupt context + * at error interrupt level. + * Action: + * It's responsible for identifying ALL the CRBs that are marked + * with error, and process them. + * + * If you find the CRB that's marked with error, map this to the + * reason it caused error, and invoke appropriate error handler. + * + * XXX Be aware of the information in the context register. + * + * NOTE: + * Use REMOTE_HUB_* macro instead of LOCAL_HUB_* so that the interrupt + * handler can be run on any node. (not necessarily the node + * corresponding to the hub that encountered error). + */ + +void hubiio_crb_error_handler(struct hubdev_info *hubdev_info) +{ + nasid_t nasid; + ii_icrb0_a_u_t icrba; /* II CRB Register A */ + ii_icrb0_b_u_t icrbb; /* II CRB Register B */ + ii_icrb0_c_u_t icrbc; /* II CRB Register C */ + ii_icrb0_d_u_t icrbd; /* II CRB Register D */ + ii_icrb0_e_u_t icrbe; /* II CRB Register D */ + int i; + int num_errors = 0; /* Num of errors handled */ + ioerror_t ioerror; + + nasid = hubdev_info->hdi_nasid; + + /* + * XXX - Add locking for any recovery actions + */ + /* + * Scan through all CRBs in the Hub, and handle the errors + * in any of the CRBs marked. + */ + for (i = 0; i < IIO_NUM_CRBS; i++) { + /* Check this crb entry to see if it is in error. */ + icrbb.ii_icrb0_b_regval = REMOTE_HUB_L(nasid, IIO_ICRB_B(i)); + + if (icrbb.b_mark == 0) { + continue; + } + + icrba.ii_icrb0_a_regval = REMOTE_HUB_L(nasid, IIO_ICRB_A(i)); + + IOERROR_INIT(&ioerror); + + /* read other CRB error registers. */ + icrbc.ii_icrb0_c_regval = REMOTE_HUB_L(nasid, IIO_ICRB_C(i)); + icrbd.ii_icrb0_d_regval = REMOTE_HUB_L(nasid, IIO_ICRB_D(i)); + icrbe.ii_icrb0_e_regval = REMOTE_HUB_L(nasid, IIO_ICRB_E(i)); + + IOERROR_SETVALUE(&ioerror, errortype, icrbb.b_ecode); + + /* Check if this error is due to BTE operation, + * and handle it separately. + */ + if (icrbd.d_bteop || + ((icrbb.b_initiator == IIO_ICRB_INIT_BTE0 || + icrbb.b_initiator == IIO_ICRB_INIT_BTE1) && + (icrbb.b_imsgtype == IIO_ICRB_IMSGT_BTE || + icrbb.b_imsgtype == IIO_ICRB_IMSGT_SN1NET))) { + + int bte_num; + + if (icrbd.d_bteop) + bte_num = icrbc.c_btenum; + else /* b_initiator bit 2 gives BTE number */ + bte_num = (icrbb.b_initiator & 0x4) >> 2; + + hubiio_crb_free(hubdev_info, i); + + bte_crb_error_handler(nasid_to_cnodeid(nasid), bte_num, + i, &ioerror, icrbd.d_bteop); + num_errors++; + continue; + } + } +} + +/* + * Function : hub_error_init + * Purpose : initialize the error handling requirements for a given hub. + * Parameters : cnode, the compact nodeid. + * Assumptions : Called only once per hub, either by a local cpu. Or by a + * remote cpu, when this hub is headless.(cpuless) + * Returns : None + */ +void hub_error_init(struct hubdev_info *hubdev_info) +{ + if (request_irq(SGI_II_ERROR, (void *)hub_eint_handler, SA_SHIRQ, + "SN_hub_error", (void *)hubdev_info)) + printk("hub_error_init: Failed to request_irq for 0x%p\n", + hubdev_info); + return; +} + + +/* + * Function : ice_error_init + * Purpose : initialize the error handling requirements for a given tio. + * Parameters : cnode, the compact nodeid. + * Assumptions : Called only once per tio. + * Returns : None + */ +void ice_error_init(struct hubdev_info *hubdev_info) +{ + if (request_irq + (SGI_TIO_ERROR, (void *)hub_eint_handler, SA_SHIRQ, "SN_TIO_error", + (void *)hubdev_info)) + printk("ice_error_init: request_irq() error hubdev_info 0x%p\n", + hubdev_info); + return; +} + diff --git a/arch/ia64/sn/kernel/idle.c b/arch/ia64/sn/kernel/idle.c new file mode 100644 index 00000000000..49d178f022b --- /dev/null +++ b/arch/ia64/sn/kernel/idle.c @@ -0,0 +1,30 @@ +/* + * 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. + * + * Copyright (c) 2001-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <asm/sn/leds.h> + +void snidle(int state) +{ + if (state) { + if (pda->idle_flag == 0) { + /* + * Turn the activity LED off. + */ + set_led_bits(0, LED_CPU_ACTIVITY); + } + + pda->idle_flag = 1; + } else { + /* + * Turn the activity LED on. + */ + set_led_bits(LED_CPU_ACTIVITY, LED_CPU_ACTIVITY); + + pda->idle_flag = 0; + } +} diff --git a/arch/ia64/sn/kernel/io_init.c b/arch/ia64/sn/kernel/io_init.c new file mode 100644 index 00000000000..001880812b7 --- /dev/null +++ b/arch/ia64/sn/kernel/io_init.c @@ -0,0 +1,411 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/bootmem.h> +#include <linux/nodemask.h> +#include <asm/sn/types.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/addrs.h> +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/pcibr_provider.h" +#include "xtalk/xwidgetdev.h" +#include <asm/sn/geo.h> +#include "xtalk/hubdev.h" +#include <asm/sn/io.h> +#include <asm/sn/simulator.h> + +char master_baseio_wid; +nasid_t master_nasid = INVALID_NASID; /* Partition Master */ + +struct slab_info { + struct hubdev_info hubdev; +}; + +struct brick { + moduleid_t id; /* Module ID of this module */ + struct slab_info slab_info[MAX_SLABS + 1]; +}; + +int sn_ioif_inited = 0; /* SN I/O infrastructure initialized? */ + +/* + * Retrieve the DMA Flush List given nasid. This list is needed + * to implement the WAR - Flush DMA data on PIO Reads. + */ +static inline uint64_t +sal_get_widget_dmaflush_list(u64 nasid, u64 widget_num, u64 address) +{ + + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, + (u64) SN_SAL_IOIF_GET_WIDGET_DMAFLUSH_LIST, + (u64) nasid, (u64) widget_num, (u64) address, 0, 0, 0, + 0); + return ret_stuff.v0; + +} + +/* + * Retrieve the hub device info structure for the given nasid. + */ +static inline uint64_t sal_get_hubdev_info(u64 handle, u64 address) +{ + + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, + (u64) SN_SAL_IOIF_GET_HUBDEV_INFO, + (u64) handle, (u64) address, 0, 0, 0, 0, 0); + return ret_stuff.v0; +} + +/* + * Retrieve the pci bus information given the bus number. + */ +static inline uint64_t sal_get_pcibus_info(u64 segment, u64 busnum, u64 address) +{ + + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, + (u64) SN_SAL_IOIF_GET_PCIBUS_INFO, + (u64) segment, (u64) busnum, (u64) address, 0, 0, 0, 0); + return ret_stuff.v0; +} + +/* + * Retrieve the pci device information given the bus and device|function number. + */ +static inline uint64_t +sal_get_pcidev_info(u64 segment, u64 bus_number, u64 devfn, u64 pci_dev, + u64 sn_irq_info) +{ + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, + (u64) SN_SAL_IOIF_GET_PCIDEV_INFO, + (u64) segment, (u64) bus_number, (u64) devfn, + (u64) pci_dev, + sn_irq_info, 0, 0); + return ret_stuff.v0; +} + +/* + * sn_alloc_pci_sysdata() - This routine allocates a pci controller + * which is expected as the pci_dev and pci_bus sysdata by the Linux + * PCI infrastructure. + */ +static inline struct pci_controller *sn_alloc_pci_sysdata(void) +{ + struct pci_controller *pci_sysdata; + + pci_sysdata = kmalloc(sizeof(*pci_sysdata), GFP_KERNEL); + if (!pci_sysdata) + BUG(); + + memset(pci_sysdata, 0, sizeof(*pci_sysdata)); + return pci_sysdata; +} + +/* + * sn_fixup_ionodes() - This routine initializes the HUB data strcuture for + * each node in the system. + */ +static void sn_fixup_ionodes(void) +{ + + struct sn_flush_device_list *sn_flush_device_list; + struct hubdev_info *hubdev; + uint64_t status; + uint64_t nasid; + int i, widget; + + for (i = 0; i < numionodes; i++) { + hubdev = (struct hubdev_info *)(NODEPDA(i)->pdinfo); + nasid = cnodeid_to_nasid(i); + status = sal_get_hubdev_info(nasid, (uint64_t) __pa(hubdev)); + if (status) + continue; + + for (widget = 0; widget <= HUB_WIDGET_ID_MAX; widget++) + hubdev->hdi_xwidget_info[widget].xwi_hubinfo = hubdev; + + if (!hubdev->hdi_flush_nasid_list.widget_p) + continue; + + hubdev->hdi_flush_nasid_list.widget_p = + kmalloc((HUB_WIDGET_ID_MAX + 1) * + sizeof(struct sn_flush_device_list *), GFP_KERNEL); + + memset(hubdev->hdi_flush_nasid_list.widget_p, 0x0, + (HUB_WIDGET_ID_MAX + 1) * + sizeof(struct sn_flush_device_list *)); + + for (widget = 0; widget <= HUB_WIDGET_ID_MAX; widget++) { + sn_flush_device_list = kmalloc(DEV_PER_WIDGET * + sizeof(struct + sn_flush_device_list), + GFP_KERNEL); + memset(sn_flush_device_list, 0x0, + DEV_PER_WIDGET * + sizeof(struct sn_flush_device_list)); + + status = + sal_get_widget_dmaflush_list(nasid, widget, + (uint64_t) + __pa + (sn_flush_device_list)); + if (status) { + kfree(sn_flush_device_list); + continue; + } + + hubdev->hdi_flush_nasid_list.widget_p[widget] = + sn_flush_device_list; + } + + if (!(i & 1)) + hub_error_init(hubdev); + else + ice_error_init(hubdev); + } + +} + +/* + * sn_pci_fixup_slot() - This routine sets up a slot's resources + * consistent with the Linux PCI abstraction layer. Resources acquired + * from our PCI provider include PIO maps to BAR space and interrupt + * objects. + */ +static void sn_pci_fixup_slot(struct pci_dev *dev) +{ + int idx; + int segment = 0; + uint64_t size; + struct sn_irq_info *sn_irq_info; + struct pci_dev *host_pci_dev; + int status = 0; + + dev->sysdata = kmalloc(sizeof(struct pcidev_info), GFP_KERNEL); + if (SN_PCIDEV_INFO(dev) <= 0) + BUG(); /* Cannot afford to run out of memory */ + memset(SN_PCIDEV_INFO(dev), 0, sizeof(struct pcidev_info)); + + sn_irq_info = kmalloc(sizeof(struct sn_irq_info), GFP_KERNEL); + if (sn_irq_info <= 0) + BUG(); /* Cannot afford to run out of memory */ + memset(sn_irq_info, 0, sizeof(struct sn_irq_info)); + + /* Call to retrieve pci device information needed by kernel. */ + status = sal_get_pcidev_info((u64) segment, (u64) dev->bus->number, + dev->devfn, + (u64) __pa(SN_PCIDEV_INFO(dev)), + (u64) __pa(sn_irq_info)); + if (status) + BUG(); /* Cannot get platform pci device information information */ + + /* Copy over PIO Mapped Addresses */ + for (idx = 0; idx <= PCI_ROM_RESOURCE; idx++) { + unsigned long start, end, addr; + + if (!SN_PCIDEV_INFO(dev)->pdi_pio_mapped_addr[idx]) + continue; + + start = dev->resource[idx].start; + end = dev->resource[idx].end; + size = end - start; + addr = SN_PCIDEV_INFO(dev)->pdi_pio_mapped_addr[idx]; + addr = ((addr << 4) >> 4) | __IA64_UNCACHED_OFFSET; + dev->resource[idx].start = addr; + dev->resource[idx].end = addr + size; + if (dev->resource[idx].flags & IORESOURCE_IO) + dev->resource[idx].parent = &ioport_resource; + else + dev->resource[idx].parent = &iomem_resource; + } + + /* set up host bus linkages */ + host_pci_dev = + pci_find_slot(SN_PCIDEV_INFO(dev)->pdi_slot_host_handle >> 32, + SN_PCIDEV_INFO(dev)-> + pdi_slot_host_handle & 0xffffffff); + SN_PCIDEV_INFO(dev)->pdi_host_pcidev_info = + SN_PCIDEV_INFO(host_pci_dev); + SN_PCIDEV_INFO(dev)->pdi_linux_pcidev = dev; + SN_PCIDEV_INFO(dev)->pdi_pcibus_info = SN_PCIBUS_BUSSOFT(dev->bus); + + /* Only set up IRQ stuff if this device has a host bus context */ + if (SN_PCIDEV_BUSSOFT(dev) && sn_irq_info->irq_irq) { + SN_PCIDEV_INFO(dev)->pdi_sn_irq_info = sn_irq_info; + dev->irq = SN_PCIDEV_INFO(dev)->pdi_sn_irq_info->irq_irq; + sn_irq_fixup(dev, sn_irq_info); + } +} + +/* + * sn_pci_controller_fixup() - This routine sets up a bus's resources + * consistent with the Linux PCI abstraction layer. + */ +static void sn_pci_controller_fixup(int segment, int busnum) +{ + int status = 0; + int nasid, cnode; + struct pci_bus *bus; + struct pci_controller *controller; + struct pcibus_bussoft *prom_bussoft_ptr; + struct hubdev_info *hubdev_info; + void *provider_soft; + + status = + sal_get_pcibus_info((u64) segment, (u64) busnum, + (u64) ia64_tpa(&prom_bussoft_ptr)); + if (status > 0) { + return; /* bus # does not exist */ + } + + prom_bussoft_ptr = __va(prom_bussoft_ptr); + controller = sn_alloc_pci_sysdata(); + /* controller non-zero is BUG'd in sn_alloc_pci_sysdata */ + + bus = pci_scan_bus(busnum, &pci_root_ops, controller); + if (bus == NULL) { + return; /* error, or bus already scanned */ + } + + /* + * Per-provider fixup. Copies the contents from prom to local + * area and links SN_PCIBUS_BUSSOFT(). + * + * Note: Provider is responsible for ensuring that prom_bussoft_ptr + * represents an asic-type that it can handle. + */ + + if (prom_bussoft_ptr->bs_asic_type == PCIIO_ASIC_TYPE_PPB) { + return; /* no further fixup necessary */ + } + + provider_soft = pcibr_bus_fixup(prom_bussoft_ptr); + if (provider_soft == NULL) { + return; /* fixup failed or not applicable */ + } + + /* + * Generic bus fixup goes here. Don't reference prom_bussoft_ptr + * after this point. + */ + + bus->sysdata = controller; + PCI_CONTROLLER(bus)->platform_data = provider_soft; + + nasid = NASID_GET(SN_PCIBUS_BUSSOFT(bus)->bs_base); + cnode = nasid_to_cnodeid(nasid); + hubdev_info = (struct hubdev_info *)(NODEPDA(cnode)->pdinfo); + SN_PCIBUS_BUSSOFT(bus)->bs_xwidget_info = + &(hubdev_info->hdi_xwidget_info[SN_PCIBUS_BUSSOFT(bus)->bs_xid]); +} + +/* + * Ugly hack to get PCI setup until we have a proper ACPI namespace. + */ + +#define PCI_BUSES_TO_SCAN 256 + +static int __init sn_pci_init(void) +{ + int i = 0; + struct pci_dev *pci_dev = NULL; + extern void sn_init_cpei_timer(void); +#ifdef CONFIG_PROC_FS + extern void register_sn_procfs(void); +#endif + + if (!ia64_platform_is("sn2") || IS_RUNNING_ON_SIMULATOR()) + return 0; + + /* + * This is needed to avoid bounce limit checks in the blk layer + */ + ia64_max_iommu_merge_mask = ~PAGE_MASK; + sn_fixup_ionodes(); + sn_irq = kmalloc(sizeof(struct sn_irq_info *) * NR_IRQS, GFP_KERNEL); + if (sn_irq <= 0) + BUG(); /* Canno afford to run out of memory. */ + memset(sn_irq, 0, sizeof(struct sn_irq_info *) * NR_IRQS); + + sn_init_cpei_timer(); + +#ifdef CONFIG_PROC_FS + register_sn_procfs(); +#endif + + for (i = 0; i < PCI_BUSES_TO_SCAN; i++) { + sn_pci_controller_fixup(0, i); + } + + /* + * Generic Linux PCI Layer has created the pci_bus and pci_dev + * structures - time for us to add our SN PLatform specific + * information. + */ + + while ((pci_dev = + pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci_dev)) != NULL) { + sn_pci_fixup_slot(pci_dev); + } + + sn_ioif_inited = 1; /* sn I/O infrastructure now initialized */ + + return 0; +} + +/* + * hubdev_init_node() - Creates the HUB data structure and link them to it's + * own NODE specific data area. + */ +void hubdev_init_node(nodepda_t * npda, cnodeid_t node) +{ + + struct hubdev_info *hubdev_info; + + if (node >= num_online_nodes()) /* Headless/memless IO nodes */ + hubdev_info = + (struct hubdev_info *)alloc_bootmem_node(NODE_DATA(0), + sizeof(struct + hubdev_info)); + else + hubdev_info = + (struct hubdev_info *)alloc_bootmem_node(NODE_DATA(node), + sizeof(struct + hubdev_info)); + npda->pdinfo = (void *)hubdev_info; + +} + +geoid_t +cnodeid_get_geoid(cnodeid_t cnode) +{ + + struct hubdev_info *hubdev; + + hubdev = (struct hubdev_info *)(NODEPDA(cnode)->pdinfo); + return hubdev->hdi_geoid; + +} + +subsys_initcall(sn_pci_init); diff --git a/arch/ia64/sn/kernel/iomv.c b/arch/ia64/sn/kernel/iomv.c new file mode 100644 index 00000000000..fec6d8b8237 --- /dev/null +++ b/arch/ia64/sn/kernel/iomv.c @@ -0,0 +1,70 @@ +/* + * 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. + * + * Copyright (C) 2000-2003 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/module.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/sn/nodepda.h> +#include <asm/sn/simulator.h> +#include <asm/sn/pda.h> +#include <asm/sn/sn_cpuid.h> +#include <asm/sn/shub_mmr.h> + +/** + * sn_io_addr - convert an in/out port to an i/o address + * @port: port to convert + * + * Legacy in/out instructions are converted to ld/st instructions + * on IA64. This routine will convert a port number into a valid + * SN i/o address. Used by sn_in*() and sn_out*(). + */ +void *sn_io_addr(unsigned long port) +{ + if (!IS_RUNNING_ON_SIMULATOR()) { + /* On sn2, legacy I/O ports don't point at anything */ + if (port < (64 * 1024)) + return NULL; + return ((void *)(port | __IA64_UNCACHED_OFFSET)); + } else { + /* but the simulator uses them... */ + unsigned long addr; + + /* + * word align port, but need more than 10 bits + * for accessing registers in bedrock local block + * (so we don't do port&0xfff) + */ + addr = (is_shub2() ? 0xc00000028c000000UL : 0xc0000087cc000000UL) | ((port >> 2) << 12); + if ((port >= 0x1f0 && port <= 0x1f7) || port == 0x3f6 || port == 0x3f7) + addr |= port; + return (void *)addr; + } +} + +EXPORT_SYMBOL(sn_io_addr); + +/** + * __sn_mmiowb - I/O space memory barrier + * + * See include/asm-ia64/io.h and Documentation/DocBook/deviceiobook.tmpl + * for details. + * + * On SN2, we wait for the PIO_WRITE_STATUS SHub register to clear. + * See PV 871084 for details about the WAR about zero value. + * + */ +void __sn_mmiowb(void) +{ + volatile unsigned long *adr = pda->pio_write_status_addr; + unsigned long val = pda->pio_write_status_val; + + while ((*adr & SH_PIO_WRITE_STATUS_PENDING_WRITE_COUNT_MASK) != val) + cpu_relax(); +} + +EXPORT_SYMBOL(__sn_mmiowb); diff --git a/arch/ia64/sn/kernel/irq.c b/arch/ia64/sn/kernel/irq.c new file mode 100644 index 00000000000..3be44724f6c --- /dev/null +++ b/arch/ia64/sn/kernel/irq.c @@ -0,0 +1,431 @@ +/* + * Platform dependent support for SGI SN + * + * 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. + * + * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved. + */ + +#include <linux/irq.h> +#include <asm/sn/intr.h> +#include <asm/sn/addrs.h> +#include <asm/sn/arch.h> +#include "xtalk/xwidgetdev.h" +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/pcibr_provider.h" +#include <asm/sn/shub_mmr.h> +#include <asm/sn/sn_sal.h> + +static void force_interrupt(int irq); +static void register_intr_pda(struct sn_irq_info *sn_irq_info); +static void unregister_intr_pda(struct sn_irq_info *sn_irq_info); + +extern int sn_force_interrupt_flag; +extern int sn_ioif_inited; +struct sn_irq_info **sn_irq; + +static inline uint64_t sn_intr_alloc(nasid_t local_nasid, int local_widget, + u64 sn_irq_info, + int req_irq, nasid_t req_nasid, + int req_slice) +{ + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, (u64) SN_SAL_IOIF_INTERRUPT, + (u64) SAL_INTR_ALLOC, (u64) local_nasid, + (u64) local_widget, (u64) sn_irq_info, (u64) req_irq, + (u64) req_nasid, (u64) req_slice); + return ret_stuff.status; +} + +static inline void sn_intr_free(nasid_t local_nasid, int local_widget, + struct sn_irq_info *sn_irq_info) +{ + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, (u64) SN_SAL_IOIF_INTERRUPT, + (u64) SAL_INTR_FREE, (u64) local_nasid, + (u64) local_widget, (u64) sn_irq_info->irq_irq, + (u64) sn_irq_info->irq_cookie, 0, 0); +} + +static unsigned int sn_startup_irq(unsigned int irq) +{ + return 0; +} + +static void sn_shutdown_irq(unsigned int irq) +{ +} + +static void sn_disable_irq(unsigned int irq) +{ +} + +static void sn_enable_irq(unsigned int irq) +{ +} + +static void sn_ack_irq(unsigned int irq) +{ + uint64_t event_occurred, mask = 0; + int nasid; + + irq = irq & 0xff; + nasid = get_nasid(); + event_occurred = + HUB_L((uint64_t *) GLOBAL_MMR_ADDR(nasid, SH_EVENT_OCCURRED)); + if (event_occurred & SH_EVENT_OCCURRED_UART_INT_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_UART_INT_SHFT); + } + if (event_occurred & SH_EVENT_OCCURRED_IPI_INT_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_IPI_INT_SHFT); + } + if (event_occurred & SH_EVENT_OCCURRED_II_INT0_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_II_INT0_SHFT); + } + if (event_occurred & SH_EVENT_OCCURRED_II_INT1_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_II_INT1_SHFT); + } + HUB_S((uint64_t *) GLOBAL_MMR_ADDR(nasid, SH_EVENT_OCCURRED_ALIAS), + mask); + __set_bit(irq, (volatile void *)pda->sn_in_service_ivecs); + + move_irq(irq); +} + +static void sn_end_irq(unsigned int irq) +{ + int nasid; + int ivec; + uint64_t event_occurred; + + ivec = irq & 0xff; + if (ivec == SGI_UART_VECTOR) { + nasid = get_nasid(); + event_occurred = HUB_L((uint64_t *) GLOBAL_MMR_ADDR + (nasid, SH_EVENT_OCCURRED)); + /* If the UART bit is set here, we may have received an + * interrupt from the UART that the driver missed. To + * make sure, we IPI ourselves to force us to look again. + */ + if (event_occurred & SH_EVENT_OCCURRED_UART_INT_MASK) { + platform_send_ipi(smp_processor_id(), SGI_UART_VECTOR, + IA64_IPI_DM_INT, 0); + } + } + __clear_bit(ivec, (volatile void *)pda->sn_in_service_ivecs); + if (sn_force_interrupt_flag) + force_interrupt(irq); +} + +static void sn_set_affinity_irq(unsigned int irq, cpumask_t mask) +{ + struct sn_irq_info *sn_irq_info = sn_irq[irq]; + struct sn_irq_info *tmp_sn_irq_info; + int cpuid, cpuphys; + nasid_t t_nasid; /* nasid to target */ + int t_slice; /* slice to target */ + + /* allocate a temp sn_irq_info struct to get new target info */ + tmp_sn_irq_info = kmalloc(sizeof(*tmp_sn_irq_info), GFP_KERNEL); + if (!tmp_sn_irq_info) + return; + + cpuid = first_cpu(mask); + cpuphys = cpu_physical_id(cpuid); + t_nasid = cpuid_to_nasid(cpuid); + t_slice = cpuid_to_slice(cpuid); + + while (sn_irq_info) { + int status; + int local_widget; + uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge; + nasid_t local_nasid = NASID_GET(bridge); + + if (!bridge) + break; /* irq is not a device interrupt */ + + if (local_nasid & 1) + local_widget = TIO_SWIN_WIDGETNUM(bridge); + else + local_widget = SWIN_WIDGETNUM(bridge); + + /* Free the old PROM sn_irq_info structure */ + sn_intr_free(local_nasid, local_widget, sn_irq_info); + + /* allocate a new PROM sn_irq_info struct */ + status = sn_intr_alloc(local_nasid, local_widget, + __pa(tmp_sn_irq_info), irq, t_nasid, + t_slice); + + if (status == 0) { + /* Update kernels sn_irq_info with new target info */ + unregister_intr_pda(sn_irq_info); + sn_irq_info->irq_cpuid = cpuid; + sn_irq_info->irq_nasid = t_nasid; + sn_irq_info->irq_slice = t_slice; + sn_irq_info->irq_xtalkaddr = + tmp_sn_irq_info->irq_xtalkaddr; + sn_irq_info->irq_cookie = tmp_sn_irq_info->irq_cookie; + register_intr_pda(sn_irq_info); + + if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type)) { + pcibr_change_devices_irq(sn_irq_info); + } + + sn_irq_info = sn_irq_info->irq_next; + +#ifdef CONFIG_SMP + set_irq_affinity_info((irq & 0xff), cpuphys, 0); +#endif + } else { + break; /* snp_affinity failed the intr_alloc */ + } + } + kfree(tmp_sn_irq_info); +} + +struct hw_interrupt_type irq_type_sn = { + "SN hub", + sn_startup_irq, + sn_shutdown_irq, + sn_enable_irq, + sn_disable_irq, + sn_ack_irq, + sn_end_irq, + sn_set_affinity_irq +}; + +unsigned int sn_local_vector_to_irq(u8 vector) +{ + return (CPU_VECTOR_TO_IRQ(smp_processor_id(), vector)); +} + +void sn_irq_init(void) +{ + int i; + irq_desc_t *base_desc = irq_desc; + + for (i = 0; i < NR_IRQS; i++) { + if (base_desc[i].handler == &no_irq_type) { + base_desc[i].handler = &irq_type_sn; + } + } +} + +static void register_intr_pda(struct sn_irq_info *sn_irq_info) +{ + int irq = sn_irq_info->irq_irq; + int cpu = sn_irq_info->irq_cpuid; + + if (pdacpu(cpu)->sn_last_irq < irq) { + pdacpu(cpu)->sn_last_irq = irq; + } + + if (pdacpu(cpu)->sn_first_irq == 0 || pdacpu(cpu)->sn_first_irq > irq) { + pdacpu(cpu)->sn_first_irq = irq; + } +} + +static void unregister_intr_pda(struct sn_irq_info *sn_irq_info) +{ + int irq = sn_irq_info->irq_irq; + int cpu = sn_irq_info->irq_cpuid; + struct sn_irq_info *tmp_irq_info; + int i, foundmatch; + + if (pdacpu(cpu)->sn_last_irq == irq) { + foundmatch = 0; + for (i = pdacpu(cpu)->sn_last_irq - 1; i; i--) { + tmp_irq_info = sn_irq[i]; + while (tmp_irq_info) { + if (tmp_irq_info->irq_cpuid == cpu) { + foundmatch++; + break; + } + tmp_irq_info = tmp_irq_info->irq_next; + } + if (foundmatch) { + break; + } + } + pdacpu(cpu)->sn_last_irq = i; + } + + if (pdacpu(cpu)->sn_first_irq == irq) { + foundmatch = 0; + for (i = pdacpu(cpu)->sn_first_irq + 1; i < NR_IRQS; i++) { + tmp_irq_info = sn_irq[i]; + while (tmp_irq_info) { + if (tmp_irq_info->irq_cpuid == cpu) { + foundmatch++; + break; + } + tmp_irq_info = tmp_irq_info->irq_next; + } + if (foundmatch) { + break; + } + } + pdacpu(cpu)->sn_first_irq = ((i == NR_IRQS) ? 0 : i); + } +} + +struct sn_irq_info *sn_irq_alloc(nasid_t local_nasid, int local_widget, int irq, + nasid_t nasid, int slice) +{ + struct sn_irq_info *sn_irq_info; + int status; + + sn_irq_info = kmalloc(sizeof(*sn_irq_info), GFP_KERNEL); + if (sn_irq_info == NULL) + return NULL; + + memset(sn_irq_info, 0x0, sizeof(*sn_irq_info)); + + status = + sn_intr_alloc(local_nasid, local_widget, __pa(sn_irq_info), irq, + nasid, slice); + + if (status) { + kfree(sn_irq_info); + return NULL; + } else { + return sn_irq_info; + } +} + +void sn_irq_free(struct sn_irq_info *sn_irq_info) +{ + uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge; + nasid_t local_nasid = NASID_GET(bridge); + int local_widget; + + if (local_nasid & 1) /* tio check */ + local_widget = TIO_SWIN_WIDGETNUM(bridge); + else + local_widget = SWIN_WIDGETNUM(bridge); + + sn_intr_free(local_nasid, local_widget, sn_irq_info); + + kfree(sn_irq_info); +} + +void sn_irq_fixup(struct pci_dev *pci_dev, struct sn_irq_info *sn_irq_info) +{ + nasid_t nasid = sn_irq_info->irq_nasid; + int slice = sn_irq_info->irq_slice; + int cpu = nasid_slice_to_cpuid(nasid, slice); + + sn_irq_info->irq_cpuid = cpu; + sn_irq_info->irq_pciioinfo = SN_PCIDEV_INFO(pci_dev); + + /* link it into the sn_irq[irq] list */ + sn_irq_info->irq_next = sn_irq[sn_irq_info->irq_irq]; + sn_irq[sn_irq_info->irq_irq] = sn_irq_info; + + (void)register_intr_pda(sn_irq_info); +} + +static void force_interrupt(int irq) +{ + struct sn_irq_info *sn_irq_info; + + if (!sn_ioif_inited) + return; + sn_irq_info = sn_irq[irq]; + while (sn_irq_info) { + if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type) && + (sn_irq_info->irq_bridge != NULL)) { + pcibr_force_interrupt(sn_irq_info); + } + sn_irq_info = sn_irq_info->irq_next; + } +} + +/* + * Check for lost interrupts. If the PIC int_status reg. says that + * an interrupt has been sent, but not handled, and the interrupt + * is not pending in either the cpu irr regs or in the soft irr regs, + * and the interrupt is not in service, then the interrupt may have + * been lost. Force an interrupt on that pin. It is possible that + * the interrupt is in flight, so we may generate a spurious interrupt, + * but we should never miss a real lost interrupt. + */ +static void sn_check_intr(int irq, struct sn_irq_info *sn_irq_info) +{ + uint64_t regval; + int irr_reg_num; + int irr_bit; + uint64_t irr_reg; + struct pcidev_info *pcidev_info; + struct pcibus_info *pcibus_info; + + pcidev_info = (struct pcidev_info *)sn_irq_info->irq_pciioinfo; + if (!pcidev_info) + return; + + pcibus_info = + (struct pcibus_info *)pcidev_info->pdi_host_pcidev_info-> + pdi_pcibus_info; + regval = pcireg_intr_status_get(pcibus_info); + + irr_reg_num = irq_to_vector(irq) / 64; + irr_bit = irq_to_vector(irq) % 64; + switch (irr_reg_num) { + case 0: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR0); + break; + case 1: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR1); + break; + case 2: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR2); + break; + case 3: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR3); + break; + } + if (!test_bit(irr_bit, &irr_reg)) { + if (!test_bit(irq, pda->sn_soft_irr)) { + if (!test_bit(irq, pda->sn_in_service_ivecs)) { + regval &= 0xff; + if (sn_irq_info->irq_int_bit & regval & + sn_irq_info->irq_last_intr) { + regval &= + ~(sn_irq_info-> + irq_int_bit & regval); + pcibr_force_interrupt(sn_irq_info); + } + } + } + } + sn_irq_info->irq_last_intr = regval; +} + +void sn_lb_int_war_check(void) +{ + int i; + + if (!sn_ioif_inited || pda->sn_first_irq == 0) + return; + for (i = pda->sn_first_irq; i <= pda->sn_last_irq; i++) { + struct sn_irq_info *sn_irq_info = sn_irq[i]; + while (sn_irq_info) { + /* Only call for PCI bridges that are fully initialized. */ + if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type) && + (sn_irq_info->irq_bridge != NULL)) { + sn_check_intr(i, sn_irq_info); + } + sn_irq_info = sn_irq_info->irq_next; + } + } +} diff --git a/arch/ia64/sn/kernel/klconflib.c b/arch/ia64/sn/kernel/klconflib.c new file mode 100644 index 00000000000..0f11a3299cd --- /dev/null +++ b/arch/ia64/sn/kernel/klconflib.c @@ -0,0 +1,108 @@ +/* + * 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. + * + * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <asm/sn/types.h> +#include <asm/sn/module.h> +#include <asm/sn/l1.h> + +char brick_types[MAX_BRICK_TYPES + 1] = "cri.xdpn%#=vo^kjbf890123456789..."; +/* + * Format a module id for printing. + * + * There are three possible formats: + * + * MODULE_FORMAT_BRIEF is the brief 6-character format, including + * the actual brick-type as recorded in the + * moduleid_t, eg. 002c15 for a C-brick, or + * 101#17 for a PX-brick. + * + * MODULE_FORMAT_LONG is the hwgraph format, eg. rack/002/bay/15 + * of rack/101/bay/17 (note that the brick + * type does not appear in this format). + * + * MODULE_FORMAT_LCD is like MODULE_FORMAT_BRIEF, except that it + * ensures that the module id provided appears + * exactly as it would on the LCD display of + * the corresponding brick, eg. still 002c15 + * for a C-brick, but 101p17 for a PX-brick. + * + * maule (9/13/04): Removed top-level check for (fmt == MODULE_FORMAT_LCD) + * making MODULE_FORMAT_LCD equivalent to MODULE_FORMAT_BRIEF. It was + * decided that all callers should assume the returned string should be what + * is displayed on the brick L1 LCD. + */ +void +format_module_id(char *buffer, moduleid_t m, int fmt) +{ + int rack, position; + unsigned char brickchar; + + rack = MODULE_GET_RACK(m); + brickchar = MODULE_GET_BTCHAR(m); + + /* Be sure we use the same brick type character as displayed + * on the brick's LCD + */ + switch (brickchar) + { + case L1_BRICKTYPE_GA: + case L1_BRICKTYPE_OPUS_TIO: + brickchar = L1_BRICKTYPE_C; + break; + + case L1_BRICKTYPE_PX: + case L1_BRICKTYPE_PE: + case L1_BRICKTYPE_PA: + case L1_BRICKTYPE_SA: /* we can move this to the "I's" later + * if that makes more sense + */ + brickchar = L1_BRICKTYPE_P; + break; + + case L1_BRICKTYPE_IX: + case L1_BRICKTYPE_IA: + + brickchar = L1_BRICKTYPE_I; + break; + } + + position = MODULE_GET_BPOS(m); + + if ((fmt == MODULE_FORMAT_BRIEF) || (fmt == MODULE_FORMAT_LCD)) { + /* Brief module number format, eg. 002c15 */ + + /* Decompress the rack number */ + *buffer++ = '0' + RACK_GET_CLASS(rack); + *buffer++ = '0' + RACK_GET_GROUP(rack); + *buffer++ = '0' + RACK_GET_NUM(rack); + + /* Add the brick type */ + *buffer++ = brickchar; + } + else if (fmt == MODULE_FORMAT_LONG) { + /* Fuller hwgraph format, eg. rack/002/bay/15 */ + + strcpy(buffer, "rack" "/"); buffer += strlen(buffer); + + *buffer++ = '0' + RACK_GET_CLASS(rack); + *buffer++ = '0' + RACK_GET_GROUP(rack); + *buffer++ = '0' + RACK_GET_NUM(rack); + + strcpy(buffer, "/" "bay" "/"); buffer += strlen(buffer); + } + + /* Add the bay position, using at least two digits */ + if (position < 10) + *buffer++ = '0'; + sprintf(buffer, "%d", position); + +} diff --git a/arch/ia64/sn/kernel/machvec.c b/arch/ia64/sn/kernel/machvec.c new file mode 100644 index 00000000000..02bb9155840 --- /dev/null +++ b/arch/ia64/sn/kernel/machvec.c @@ -0,0 +1,11 @@ +/* + * 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. + * + * Copyright (c) 2002-2003 Silicon Graphics, Inc. All Rights Reserved. + */ + +#define MACHVEC_PLATFORM_NAME sn2 +#define MACHVEC_PLATFORM_HEADER <asm/machvec_sn2.h> +#include <asm/machvec_init.h> diff --git a/arch/ia64/sn/kernel/mca.c b/arch/ia64/sn/kernel/mca.c new file mode 100644 index 00000000000..857774bb2c9 --- /dev/null +++ b/arch/ia64/sn/kernel/mca.c @@ -0,0 +1,135 @@ +/* + * 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. + * + * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/vmalloc.h> +#include <asm/mca.h> +#include <asm/sal.h> +#include <asm/sn/sn_sal.h> + +/* + * Interval for calling SAL to poll for errors that do NOT cause error + * interrupts. SAL will raise a CPEI if any errors are present that + * need to be logged. + */ +#define CPEI_INTERVAL (5*HZ) + +struct timer_list sn_cpei_timer; +void sn_init_cpei_timer(void); + +/* Printing oemdata from mca uses data that is not passed through SAL, it is + * global. Only one user at a time. + */ +static DECLARE_MUTEX(sn_oemdata_mutex); +static u8 **sn_oemdata; +static u64 *sn_oemdata_size, sn_oemdata_bufsize; + +/* + * print_hook + * + * This function is the callback routine that SAL calls to log error + * info for platform errors. buf is appended to sn_oemdata, resizing as + * required. + */ +static int print_hook(const char *fmt, ...) +{ + char buf[400]; + int len; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + len = strlen(buf); + while (*sn_oemdata_size + len + 1 > sn_oemdata_bufsize) { + u8 *newbuf = vmalloc(sn_oemdata_bufsize += 1000); + if (!newbuf) { + printk(KERN_ERR "%s: unable to extend sn_oemdata\n", + __FUNCTION__); + return 0; + } + memcpy(newbuf, *sn_oemdata, *sn_oemdata_size); + vfree(*sn_oemdata); + *sn_oemdata = newbuf; + } + memcpy(*sn_oemdata + *sn_oemdata_size, buf, len + 1); + *sn_oemdata_size += len; + return 0; +} + +static void sn_cpei_handler(int irq, void *devid, struct pt_regs *regs) +{ + /* + * this function's sole purpose is to call SAL when we receive + * a CE interrupt from SHUB or when the timer routine decides + * we need to call SAL to check for CEs. + */ + + /* CALL SAL_LOG_CE */ + + ia64_sn_plat_cpei_handler(); +} + +static void sn_cpei_timer_handler(unsigned long dummy) +{ + sn_cpei_handler(-1, NULL, NULL); + mod_timer(&sn_cpei_timer, jiffies + CPEI_INTERVAL); +} + +void sn_init_cpei_timer(void) +{ + init_timer(&sn_cpei_timer); + sn_cpei_timer.expires = jiffies + CPEI_INTERVAL; + sn_cpei_timer.function = sn_cpei_timer_handler; + add_timer(&sn_cpei_timer); +} + +static int +sn_platform_plat_specific_err_print(const u8 * sect_header, u8 ** oemdata, + u64 * oemdata_size) +{ + down(&sn_oemdata_mutex); + sn_oemdata = oemdata; + sn_oemdata_size = oemdata_size; + sn_oemdata_bufsize = 0; + ia64_sn_plat_specific_err_print(print_hook, (char *)sect_header); + up(&sn_oemdata_mutex); + return 0; +} + +/* Callback when userspace salinfo wants to decode oem data via the platform + * kernel and/or prom. + */ +int sn_salinfo_platform_oemdata(const u8 *sect_header, u8 **oemdata, u64 *oemdata_size) +{ + efi_guid_t guid = *(efi_guid_t *)sect_header; + int valid = 0; + *oemdata_size = 0; + vfree(*oemdata); + *oemdata = NULL; + if (efi_guidcmp(guid, SAL_PLAT_SPECIFIC_ERR_SECT_GUID) == 0) { + sal_log_plat_specific_err_info_t *psei = (sal_log_plat_specific_err_info_t *)sect_header; + valid = psei->valid.oem_data; + } else if (efi_guidcmp(guid, SAL_PLAT_MEM_DEV_ERR_SECT_GUID) == 0) { + sal_log_mem_dev_err_info_t *mdei = (sal_log_mem_dev_err_info_t *)sect_header; + valid = mdei->valid.oem_data; + } + if (valid) + return sn_platform_plat_specific_err_print(sect_header, oemdata, oemdata_size); + else + return 0; +} + +static int __init sn_salinfo_init(void) +{ + salinfo_platform_oemdata = &sn_salinfo_platform_oemdata; + return 0; +} + +module_init(sn_salinfo_init) diff --git a/arch/ia64/sn/kernel/setup.c b/arch/ia64/sn/kernel/setup.c new file mode 100644 index 00000000000..f0306b516af --- /dev/null +++ b/arch/ia64/sn/kernel/setup.c @@ -0,0 +1,621 @@ +/* + * 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. + * + * Copyright (C) 1999,2001-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/kdev_t.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/console.h> +#include <linux/timex.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/serial.h> +#include <linux/irq.h> +#include <linux/bootmem.h> +#include <linux/mmzone.h> +#include <linux/interrupt.h> +#include <linux/acpi.h> +#include <linux/compiler.h> +#include <linux/sched.h> +#include <linux/root_dev.h> +#include <linux/nodemask.h> + +#include <asm/io.h> +#include <asm/sal.h> +#include <asm/machvec.h> +#include <asm/system.h> +#include <asm/processor.h> +#include <asm/sn/arch.h> +#include <asm/sn/addrs.h> +#include <asm/sn/pda.h> +#include <asm/sn/nodepda.h> +#include <asm/sn/sn_cpuid.h> +#include <asm/sn/simulator.h> +#include <asm/sn/leds.h> +#include <asm/sn/bte.h> +#include <asm/sn/shub_mmr.h> +#include <asm/sn/clksupport.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/geo.h> +#include "xtalk/xwidgetdev.h" +#include "xtalk/hubdev.h" +#include <asm/sn/klconfig.h> + + +DEFINE_PER_CPU(struct pda_s, pda_percpu); + +#define MAX_PHYS_MEMORY (1UL << 49) /* 1 TB */ + +lboard_t *root_lboard[MAX_COMPACT_NODES]; + +extern void bte_init_node(nodepda_t *, cnodeid_t); + +extern void sn_timer_init(void); +extern unsigned long last_time_offset; +extern void (*ia64_mark_idle) (int); +extern void snidle(int); +extern unsigned char acpi_kbd_controller_present; + +unsigned long sn_rtc_cycles_per_second; +EXPORT_SYMBOL(sn_rtc_cycles_per_second); + +DEFINE_PER_CPU(struct sn_hub_info_s, __sn_hub_info); +EXPORT_PER_CPU_SYMBOL(__sn_hub_info); + +partid_t sn_partid = -1; +EXPORT_SYMBOL(sn_partid); +char sn_system_serial_number_string[128]; +EXPORT_SYMBOL(sn_system_serial_number_string); +u64 sn_partition_serial_number; +EXPORT_SYMBOL(sn_partition_serial_number); +u8 sn_partition_id; +EXPORT_SYMBOL(sn_partition_id); +u8 sn_system_size; +EXPORT_SYMBOL(sn_system_size); +u8 sn_sharing_domain_size; +EXPORT_SYMBOL(sn_sharing_domain_size); +u8 sn_coherency_id; +EXPORT_SYMBOL(sn_coherency_id); +u8 sn_region_size; +EXPORT_SYMBOL(sn_region_size); + +short physical_node_map[MAX_PHYSNODE_ID]; + +EXPORT_SYMBOL(physical_node_map); + +int numionodes; + +static void sn_init_pdas(char **); +static void scan_for_ionodes(void); + +static nodepda_t *nodepdaindr[MAX_COMPACT_NODES]; + +/* + * The format of "screen_info" is strange, and due to early i386-setup + * code. This is just enough to make the console code think we're on a + * VGA color display. + */ +struct screen_info sn_screen_info = { + .orig_x = 0, + .orig_y = 0, + .orig_video_mode = 3, + .orig_video_cols = 80, + .orig_video_ega_bx = 3, + .orig_video_lines = 25, + .orig_video_isVGA = 1, + .orig_video_points = 16 +}; + +/* + * This is here so we can use the CMOS detection in ide-probe.c to + * determine what drives are present. In theory, we don't need this + * as the auto-detection could be done via ide-probe.c:do_probe() but + * in practice that would be much slower, which is painful when + * running in the simulator. Note that passing zeroes in DRIVE_INFO + * is sufficient (the IDE driver will autodetect the drive geometry). + */ +#ifdef CONFIG_IA64_GENERIC +extern char drive_info[4 * 16]; +#else +char drive_info[4 * 16]; +#endif + +/* + * Get nasid of current cpu early in boot before nodepda is initialized + */ +static int +boot_get_nasid(void) +{ + int nasid; + + if (ia64_sn_get_sapic_info(get_sapicid(), &nasid, NULL, NULL)) + BUG(); + return nasid; +} + +/* + * This routine can only be used during init, since + * smp_boot_data is an init data structure. + * We have to use smp_boot_data.cpu_phys_id to find + * the physical id of the processor because the normal + * cpu_physical_id() relies on data structures that + * may not be initialized yet. + */ + +static int __init pxm_to_nasid(int pxm) +{ + int i; + int nid; + + nid = pxm_to_nid_map[pxm]; + for (i = 0; i < num_node_memblks; i++) { + if (node_memblk[i].nid == nid) { + return NASID_GET(node_memblk[i].start_paddr); + } + } + return -1; +} + +/** + * early_sn_setup - early setup routine for SN platforms + * + * Sets up an initial console to aid debugging. Intended primarily + * for bringup. See start_kernel() in init/main.c. + */ + +void __init early_sn_setup(void) +{ + efi_system_table_t *efi_systab; + efi_config_table_t *config_tables; + struct ia64_sal_systab *sal_systab; + struct ia64_sal_desc_entry_point *ep; + char *p; + int i, j; + + /* + * Parse enough of the SAL tables to locate the SAL entry point. Since, console + * IO on SN2 is done via SAL calls, early_printk won't work without this. + * + * This code duplicates some of the ACPI table parsing that is in efi.c & sal.c. + * Any changes to those file may have to be made hereas well. + */ + efi_systab = (efi_system_table_t *) __va(ia64_boot_param->efi_systab); + config_tables = __va(efi_systab->tables); + for (i = 0; i < efi_systab->nr_tables; i++) { + if (efi_guidcmp(config_tables[i].guid, SAL_SYSTEM_TABLE_GUID) == + 0) { + sal_systab = __va(config_tables[i].table); + p = (char *)(sal_systab + 1); + for (j = 0; j < sal_systab->entry_count; j++) { + if (*p == SAL_DESC_ENTRY_POINT) { + ep = (struct ia64_sal_desc_entry_point + *)p; + ia64_sal_handler_init(__va + (ep->sal_proc), + __va(ep->gp)); + return; + } + p += SAL_DESC_SIZE(*p); + } + } + } + /* Uh-oh, SAL not available?? */ + printk(KERN_ERR "failed to find SAL entry point\n"); +} + +extern int platform_intr_list[]; +extern nasid_t master_nasid; +static int shub_1_1_found __initdata; + +/* + * sn_check_for_wars + * + * Set flag for enabling shub specific wars + */ + +static inline int __init is_shub_1_1(int nasid) +{ + unsigned long id; + int rev; + + if (is_shub2()) + return 0; + id = REMOTE_HUB_L(nasid, SH1_SHUB_ID); + rev = (id & SH1_SHUB_ID_REVISION_MASK) >> SH1_SHUB_ID_REVISION_SHFT; + return rev <= 2; +} + +static void __init sn_check_for_wars(void) +{ + int cnode; + + if (is_shub2()) { + /* none yet */ + } else { + for_each_online_node(cnode) { + if (is_shub_1_1(cnodeid_to_nasid(cnode))) + sn_hub_info->shub_1_1_found = 1; + } + } +} + +/** + * sn_setup - SN platform setup routine + * @cmdline_p: kernel command line + * + * Handles platform setup for SN machines. This includes determining + * the RTC frequency (via a SAL call), initializing secondary CPUs, and + * setting up per-node data areas. The console is also initialized here. + */ +void __init sn_setup(char **cmdline_p) +{ + long status, ticks_per_sec, drift; + int pxm; + int major = sn_sal_rev_major(), minor = sn_sal_rev_minor(); + extern void sn_cpu_init(void); + + /* + * If the generic code has enabled vga console support - lets + * get rid of it again. This is a kludge for the fact that ACPI + * currtently has no way of informing us if legacy VGA is available + * or not. + */ +#if defined(CONFIG_VT) && defined(CONFIG_VGA_CONSOLE) + if (conswitchp == &vga_con) { + printk(KERN_DEBUG "SGI: Disabling VGA console\n"); +#ifdef CONFIG_DUMMY_CONSOLE + conswitchp = &dummy_con; +#else + conswitchp = NULL; +#endif /* CONFIG_DUMMY_CONSOLE */ + } +#endif /* def(CONFIG_VT) && def(CONFIG_VGA_CONSOLE) */ + + MAX_DMA_ADDRESS = PAGE_OFFSET + MAX_PHYS_MEMORY; + + memset(physical_node_map, -1, sizeof(physical_node_map)); + for (pxm = 0; pxm < MAX_PXM_DOMAINS; pxm++) + if (pxm_to_nid_map[pxm] != -1) + physical_node_map[pxm_to_nasid(pxm)] = + pxm_to_nid_map[pxm]; + + /* + * Old PROMs do not provide an ACPI FADT. Disable legacy keyboard + * support here so we don't have to listen to failed keyboard probe + * messages. + */ + if ((major < 2 || (major == 2 && minor <= 9)) && + acpi_kbd_controller_present) { + printk(KERN_INFO "Disabling legacy keyboard support as prom " + "is too old and doesn't provide FADT\n"); + acpi_kbd_controller_present = 0; + } + + printk("SGI SAL version %x.%02x\n", major, minor); + + /* + * Confirm the SAL we're running on is recent enough... + */ + if ((major < SN_SAL_MIN_MAJOR) || (major == SN_SAL_MIN_MAJOR && + minor < SN_SAL_MIN_MINOR)) { + printk(KERN_ERR "This kernel needs SGI SAL version >= " + "%x.%02x\n", SN_SAL_MIN_MAJOR, SN_SAL_MIN_MINOR); + panic("PROM version too old\n"); + } + + master_nasid = boot_get_nasid(); + + status = + ia64_sal_freq_base(SAL_FREQ_BASE_REALTIME_CLOCK, &ticks_per_sec, + &drift); + if (status != 0 || ticks_per_sec < 100000) { + printk(KERN_WARNING + "unable to determine platform RTC clock frequency, guessing.\n"); + /* PROM gives wrong value for clock freq. so guess */ + sn_rtc_cycles_per_second = 1000000000000UL / 30000UL; + } else + sn_rtc_cycles_per_second = ticks_per_sec; + + platform_intr_list[ACPI_INTERRUPT_CPEI] = IA64_CPE_VECTOR; + + /* + * we set the default root device to /dev/hda + * to make simulation easy + */ + ROOT_DEV = Root_HDA1; + + /* + * Create the PDAs and NODEPDAs for all the cpus. + */ + sn_init_pdas(cmdline_p); + + ia64_mark_idle = &snidle; + + /* + * For the bootcpu, we do this here. All other cpus will make the + * call as part of cpu_init in slave cpu initialization. + */ + sn_cpu_init(); + +#ifdef CONFIG_SMP + init_smp_config(); +#endif + screen_info = sn_screen_info; + + sn_timer_init(); +} + +/** + * sn_init_pdas - setup node data areas + * + * One time setup for Node Data Area. Called by sn_setup(). + */ +static void __init sn_init_pdas(char **cmdline_p) +{ + cnodeid_t cnode; + + memset(pda->cnodeid_to_nasid_table, -1, + sizeof(pda->cnodeid_to_nasid_table)); + for_each_online_node(cnode) + pda->cnodeid_to_nasid_table[cnode] = + pxm_to_nasid(nid_to_pxm_map[cnode]); + + numionodes = num_online_nodes(); + scan_for_ionodes(); + + /* + * Allocate & initalize the nodepda for each node. + */ + for_each_online_node(cnode) { + nodepdaindr[cnode] = + alloc_bootmem_node(NODE_DATA(cnode), sizeof(nodepda_t)); + memset(nodepdaindr[cnode], 0, sizeof(nodepda_t)); + memset(nodepdaindr[cnode]->phys_cpuid, -1, + sizeof(nodepdaindr[cnode]->phys_cpuid)); + } + + /* + * Allocate & initialize nodepda for TIOs. For now, put them on node 0. + */ + for (cnode = num_online_nodes(); cnode < numionodes; cnode++) { + nodepdaindr[cnode] = + alloc_bootmem_node(NODE_DATA(0), sizeof(nodepda_t)); + memset(nodepdaindr[cnode], 0, sizeof(nodepda_t)); + } + + /* + * Now copy the array of nodepda pointers to each nodepda. + */ + for (cnode = 0; cnode < numionodes; cnode++) + memcpy(nodepdaindr[cnode]->pernode_pdaindr, nodepdaindr, + sizeof(nodepdaindr)); + + /* + * Set up IO related platform-dependent nodepda fields. + * The following routine actually sets up the hubinfo struct + * in nodepda. + */ + for_each_online_node(cnode) { + bte_init_node(nodepdaindr[cnode], cnode); + } + + /* + * Initialize the per node hubdev. This includes IO Nodes and + * headless/memless nodes. + */ + for (cnode = 0; cnode < numionodes; cnode++) { + hubdev_init_node(nodepdaindr[cnode], cnode); + } +} + +/** + * sn_cpu_init - initialize per-cpu data areas + * @cpuid: cpuid of the caller + * + * Called during cpu initialization on each cpu as it starts. + * Currently, initializes the per-cpu data area for SNIA. + * Also sets up a few fields in the nodepda. Also known as + * platform_cpu_init() by the ia64 machvec code. + */ +void __init sn_cpu_init(void) +{ + int cpuid; + int cpuphyid; + int nasid; + int subnode; + int slice; + int cnode; + int i; + static int wars_have_been_checked; + + memset(pda, 0, sizeof(pda)); + if (ia64_sn_get_sn_info(0, &sn_hub_info->shub2, &sn_hub_info->nasid_bitmask, &sn_hub_info->nasid_shift, + &sn_system_size, &sn_sharing_domain_size, &sn_partition_id, + &sn_coherency_id, &sn_region_size)) + BUG(); + sn_hub_info->as_shift = sn_hub_info->nasid_shift - 2; + + /* + * The boot cpu makes this call again after platform initialization is + * complete. + */ + if (nodepdaindr[0] == NULL) + return; + + cpuid = smp_processor_id(); + cpuphyid = get_sapicid(); + + if (ia64_sn_get_sapic_info(cpuphyid, &nasid, &subnode, &slice)) + BUG(); + + for (i=0; i < MAX_NUMNODES; i++) { + if (nodepdaindr[i]) { + nodepdaindr[i]->phys_cpuid[cpuid].nasid = nasid; + nodepdaindr[i]->phys_cpuid[cpuid].slice = slice; + nodepdaindr[i]->phys_cpuid[cpuid].subnode = subnode; + } + } + + cnode = nasid_to_cnodeid(nasid); + + pda->p_nodepda = nodepdaindr[cnode]; + pda->led_address = + (typeof(pda->led_address)) (LED0 + (slice << LED_CPU_SHIFT)); + pda->led_state = LED_ALWAYS_SET; + pda->hb_count = HZ / 2; + pda->hb_state = 0; + pda->idle_flag = 0; + + if (cpuid != 0) { + memcpy(pda->cnodeid_to_nasid_table, + pdacpu(0)->cnodeid_to_nasid_table, + sizeof(pda->cnodeid_to_nasid_table)); + } + + /* + * Check for WARs. + * Only needs to be done once, on BSP. + * Has to be done after loop above, because it uses pda.cnodeid_to_nasid_table[i]. + * Has to be done before assignment below. + */ + if (!wars_have_been_checked) { + sn_check_for_wars(); + wars_have_been_checked = 1; + } + sn_hub_info->shub_1_1_found = shub_1_1_found; + + /* + * Set up addresses of PIO/MEM write status registers. + */ + { + u64 pio1[] = {SH1_PIO_WRITE_STATUS_0, 0, SH1_PIO_WRITE_STATUS_1, 0}; + u64 pio2[] = {SH2_PIO_WRITE_STATUS_0, SH2_PIO_WRITE_STATUS_1, + SH2_PIO_WRITE_STATUS_2, SH2_PIO_WRITE_STATUS_3}; + u64 *pio; + pio = is_shub1() ? pio1 : pio2; + pda->pio_write_status_addr = (volatile unsigned long *) LOCAL_MMR_ADDR(pio[slice]); + pda->pio_write_status_val = is_shub1() ? SH_PIO_WRITE_STATUS_PENDING_WRITE_COUNT_MASK : 0; + } + + /* + * WAR addresses for SHUB 1.x. + */ + if (local_node_data->active_cpu_count++ == 0 && is_shub1()) { + int buddy_nasid; + buddy_nasid = + cnodeid_to_nasid(numa_node_id() == + num_online_nodes() - 1 ? 0 : numa_node_id() + 1); + pda->pio_shub_war_cam_addr = + (volatile unsigned long *)GLOBAL_MMR_ADDR(nasid, + SH1_PI_CAM_CONTROL); + } +} + +/* + * Scan klconfig for ionodes. Add the nasids to the + * physical_node_map and the pda and increment numionodes. + */ + +static void __init scan_for_ionodes(void) +{ + int nasid = 0; + lboard_t *brd; + + /* Setup ionodes with memory */ + for (nasid = 0; nasid < MAX_PHYSNODE_ID; nasid += 2) { + char *klgraph_header; + cnodeid_t cnodeid; + + if (physical_node_map[nasid] == -1) + continue; + + cnodeid = -1; + klgraph_header = __va(ia64_sn_get_klconfig_addr(nasid)); + if (!klgraph_header) { + if (IS_RUNNING_ON_SIMULATOR()) + continue; + BUG(); /* All nodes must have klconfig tables! */ + } + cnodeid = nasid_to_cnodeid(nasid); + root_lboard[cnodeid] = (lboard_t *) + NODE_OFFSET_TO_LBOARD((nasid), + ((kl_config_hdr_t + *) (klgraph_header))-> + ch_board_info); + } + + /* Scan headless/memless IO Nodes. */ + for (nasid = 0; nasid < MAX_PHYSNODE_ID; nasid += 2) { + /* if there's no nasid, don't try to read the klconfig on the node */ + if (physical_node_map[nasid] == -1) + continue; + brd = find_lboard_any((lboard_t *) + root_lboard[nasid_to_cnodeid(nasid)], + KLTYPE_SNIA); + if (brd) { + brd = KLCF_NEXT_ANY(brd); /* Skip this node's lboard */ + if (!brd) + continue; + } + + brd = find_lboard_any(brd, KLTYPE_SNIA); + + while (brd) { + pda->cnodeid_to_nasid_table[numionodes] = + brd->brd_nasid; + physical_node_map[brd->brd_nasid] = numionodes; + root_lboard[numionodes] = brd; + numionodes++; + brd = KLCF_NEXT_ANY(brd); + if (!brd) + break; + + brd = find_lboard_any(brd, KLTYPE_SNIA); + } + } + + /* Scan for TIO nodes. */ + for (nasid = 0; nasid < MAX_PHYSNODE_ID; nasid += 2) { + /* if there's no nasid, don't try to read the klconfig on the node */ + if (physical_node_map[nasid] == -1) + continue; + brd = find_lboard_any((lboard_t *) + root_lboard[nasid_to_cnodeid(nasid)], + KLTYPE_TIO); + while (brd) { + pda->cnodeid_to_nasid_table[numionodes] = + brd->brd_nasid; + physical_node_map[brd->brd_nasid] = numionodes; + root_lboard[numionodes] = brd; + numionodes++; + brd = KLCF_NEXT_ANY(brd); + if (!brd) + break; + + brd = find_lboard_any(brd, KLTYPE_TIO); + } + } + +} + +int +nasid_slice_to_cpuid(int nasid, int slice) +{ + long cpu; + + for (cpu=0; cpu < NR_CPUS; cpu++) + if (nodepda->phys_cpuid[cpu].nasid == nasid && nodepda->phys_cpuid[cpu].slice == slice) + return cpu; + + return -1; +} diff --git a/arch/ia64/sn/kernel/sn2/Makefile b/arch/ia64/sn/kernel/sn2/Makefile new file mode 100644 index 00000000000..170bde4549d --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/Makefile @@ -0,0 +1,13 @@ +# arch/ia64/sn/kernel/sn2/Makefile +# +# 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. +# +# Copyright (C) 1999,2001-2002 Silicon Graphics, Inc. All rights reserved. +# +# sn2 specific kernel files +# + +obj-y += cache.o io.o ptc_deadlock.o sn2_smp.o sn_proc_fs.o \ + prominfo_proc.o timer.o timer_interrupt.o sn_hwperf.o diff --git a/arch/ia64/sn/kernel/sn2/cache.c b/arch/ia64/sn/kernel/sn2/cache.c new file mode 100644 index 00000000000..bc3cfa17cd0 --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/cache.c @@ -0,0 +1,34 @@ +/* + * 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. + * + * Copyright (C) 2001-2003 Silicon Graphics, Inc. All rights reserved. + * + */ +#include <linux/module.h> +#include <asm/pgalloc.h> + +/** + * sn_flush_all_caches - flush a range of address from all caches (incl. L4) + * @flush_addr: identity mapped region 7 address to start flushing + * @bytes: number of bytes to flush + * + * Flush a range of addresses from all caches including L4. + * All addresses fully or partially contained within + * @flush_addr to @flush_addr + @bytes are flushed + * from the all caches. + */ +void +sn_flush_all_caches(long flush_addr, long bytes) +{ + flush_icache_range(flush_addr, flush_addr+bytes); + /* + * The last call may have returned before the caches + * were actually flushed, so we call it again to make + * sure. + */ + flush_icache_range(flush_addr, flush_addr+bytes); + mb(); +} +EXPORT_SYMBOL(sn_flush_all_caches); diff --git a/arch/ia64/sn/kernel/sn2/io.c b/arch/ia64/sn/kernel/sn2/io.c new file mode 100644 index 00000000000..a12c0586de3 --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/io.c @@ -0,0 +1,101 @@ +/* + * 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. + * + * Copyright (C) 2003 Silicon Graphics, Inc. All rights reserved. + * + * The generic kernel requires function pointers to these routines, so + * we wrap the inlines from asm/ia64/sn/sn2/io.h here. + */ + +#include <asm/sn/io.h> + +#ifdef CONFIG_IA64_GENERIC + +#undef __sn_inb +#undef __sn_inw +#undef __sn_inl +#undef __sn_outb +#undef __sn_outw +#undef __sn_outl +#undef __sn_readb +#undef __sn_readw +#undef __sn_readl +#undef __sn_readq +#undef __sn_readb_relaxed +#undef __sn_readw_relaxed +#undef __sn_readl_relaxed +#undef __sn_readq_relaxed + +unsigned int __sn_inb(unsigned long port) +{ + return ___sn_inb(port); +} + +unsigned int __sn_inw(unsigned long port) +{ + return ___sn_inw(port); +} + +unsigned int __sn_inl(unsigned long port) +{ + return ___sn_inl(port); +} + +void __sn_outb(unsigned char val, unsigned long port) +{ + ___sn_outb(val, port); +} + +void __sn_outw(unsigned short val, unsigned long port) +{ + ___sn_outw(val, port); +} + +void __sn_outl(unsigned int val, unsigned long port) +{ + ___sn_outl(val, port); +} + +unsigned char __sn_readb(void __iomem *addr) +{ + return ___sn_readb(addr); +} + +unsigned short __sn_readw(void __iomem *addr) +{ + return ___sn_readw(addr); +} + +unsigned int __sn_readl(void __iomem *addr) +{ + return ___sn_readl(addr); +} + +unsigned long __sn_readq(void __iomem *addr) +{ + return ___sn_readq(addr); +} + +unsigned char __sn_readb_relaxed(void __iomem *addr) +{ + return ___sn_readb_relaxed(addr); +} + +unsigned short __sn_readw_relaxed(void __iomem *addr) +{ + return ___sn_readw_relaxed(addr); +} + +unsigned int __sn_readl_relaxed(void __iomem *addr) +{ + return ___sn_readl_relaxed(addr); +} + +unsigned long __sn_readq_relaxed(void __iomem *addr) +{ + return ___sn_readq_relaxed(addr); +} + +#endif diff --git a/arch/ia64/sn/kernel/sn2/prominfo_proc.c b/arch/ia64/sn/kernel/sn2/prominfo_proc.c new file mode 100644 index 00000000000..81c63b2f8ae --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/prominfo_proc.c @@ -0,0 +1,279 @@ +/* + * 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. + * + * Copyright (C) 1999,2001-2004 Silicon Graphics, Inc. All Rights Reserved. + * + * Module to export the system's Firmware Interface Tables, including + * PROM revision numbers and banners, in /proc + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/nodemask.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/sn_cpuid.h> +#include <asm/sn/addrs.h> + +MODULE_DESCRIPTION("PROM version reporting for /proc"); +MODULE_AUTHOR("Chad Talbott"); +MODULE_LICENSE("GPL"); + +/* Standard Intel FIT entry types */ +#define FIT_ENTRY_FIT_HEADER 0x00 /* FIT header entry */ +#define FIT_ENTRY_PAL_B 0x01 /* PAL_B entry */ +/* Entries 0x02 through 0x0D reserved by Intel */ +#define FIT_ENTRY_PAL_A_PROC 0x0E /* Processor-specific PAL_A entry */ +#define FIT_ENTRY_PAL_A 0x0F /* PAL_A entry, same as... */ +#define FIT_ENTRY_PAL_A_GEN 0x0F /* ...Generic PAL_A entry */ +#define FIT_ENTRY_UNUSED 0x7F /* Unused (reserved by Intel?) */ +/* OEM-defined entries range from 0x10 to 0x7E. */ +#define FIT_ENTRY_SAL_A 0x10 /* SAL_A entry */ +#define FIT_ENTRY_SAL_B 0x11 /* SAL_B entry */ +#define FIT_ENTRY_SALRUNTIME 0x12 /* SAL runtime entry */ +#define FIT_ENTRY_EFI 0x1F /* EFI entry */ +#define FIT_ENTRY_FPSWA 0x20 /* embedded fpswa entry */ +#define FIT_ENTRY_VMLINUX 0x21 /* embedded vmlinux entry */ + +#define FIT_MAJOR_SHIFT (32 + 8) +#define FIT_MAJOR_MASK ((1 << 8) - 1) +#define FIT_MINOR_SHIFT 32 +#define FIT_MINOR_MASK ((1 << 8) - 1) + +#define FIT_MAJOR(q) \ + ((unsigned) ((q) >> FIT_MAJOR_SHIFT) & FIT_MAJOR_MASK) +#define FIT_MINOR(q) \ + ((unsigned) ((q) >> FIT_MINOR_SHIFT) & FIT_MINOR_MASK) + +#define FIT_TYPE_SHIFT (32 + 16) +#define FIT_TYPE_MASK ((1 << 7) - 1) + +#define FIT_TYPE(q) \ + ((unsigned) ((q) >> FIT_TYPE_SHIFT) & FIT_TYPE_MASK) + +struct fit_type_map_t { + unsigned char type; + const char *name; +}; + +static const struct fit_type_map_t fit_entry_types[] = { + {FIT_ENTRY_FIT_HEADER, "FIT Header"}, + {FIT_ENTRY_PAL_A_GEN, "Generic PAL_A"}, + {FIT_ENTRY_PAL_A_PROC, "Processor-specific PAL_A"}, + {FIT_ENTRY_PAL_A, "PAL_A"}, + {FIT_ENTRY_PAL_B, "PAL_B"}, + {FIT_ENTRY_SAL_A, "SAL_A"}, + {FIT_ENTRY_SAL_B, "SAL_B"}, + {FIT_ENTRY_SALRUNTIME, "SAL runtime"}, + {FIT_ENTRY_EFI, "EFI"}, + {FIT_ENTRY_VMLINUX, "Embedded Linux"}, + {FIT_ENTRY_FPSWA, "Embedded FPSWA"}, + {FIT_ENTRY_UNUSED, "Unused"}, + {0xff, "Error"}, +}; + +static const char *fit_type_name(unsigned char type) +{ + struct fit_type_map_t const *mapp; + + for (mapp = fit_entry_types; mapp->type != 0xff; mapp++) + if (type == mapp->type) + return mapp->name; + + if ((type > FIT_ENTRY_PAL_A) && (type < FIT_ENTRY_UNUSED)) + return "OEM type"; + if ((type > FIT_ENTRY_PAL_B) && (type < FIT_ENTRY_PAL_A)) + return "Reserved"; + + return "Unknown type"; +} + +static int +get_fit_entry(unsigned long nasid, int index, unsigned long *fentry, + char *banner, int banlen) +{ + return ia64_sn_get_fit_compt(nasid, index, fentry, banner, banlen); +} + + +/* + * These two routines display the FIT table for each node. + */ +static int dump_fit_entry(char *page, unsigned long *fentry) +{ + unsigned type; + + type = FIT_TYPE(fentry[1]); + return sprintf(page, "%02x %-25s %x.%02x %016lx %u\n", + type, + fit_type_name(type), + FIT_MAJOR(fentry[1]), FIT_MINOR(fentry[1]), + fentry[0], + /* mult by sixteen to get size in bytes */ + (unsigned)(fentry[1] & 0xffffff) * 16); +} + + +/* + * We assume that the fit table will be small enough that we can print + * the whole thing into one page. (This is true for our default 16kB + * pages -- each entry is about 60 chars wide when printed.) I read + * somewhere that the maximum size of the FIT is 128 entries, so we're + * OK except for 4kB pages (and no one is going to do that on SN + * anyway). + */ +static int +dump_fit(char *page, unsigned long nasid) +{ + unsigned long fentry[2]; + int index; + char *p; + + p = page; + for (index=0;;index++) { + BUG_ON(index * 60 > PAGE_SIZE); + if (get_fit_entry(nasid, index, fentry, NULL, 0)) + break; + p += dump_fit_entry(p, fentry); + } + + return p - page; +} + +static int +dump_version(char *page, unsigned long nasid) +{ + unsigned long fentry[2]; + char banner[128]; + int index; + int len; + + for (index = 0; ; index++) { + if (get_fit_entry(nasid, index, fentry, banner, + sizeof(banner))) + return 0; + if (FIT_TYPE(fentry[1]) == FIT_ENTRY_SAL_A) + break; + } + + len = sprintf(page, "%x.%02x\n", FIT_MAJOR(fentry[1]), + FIT_MINOR(fentry[1])); + page += len; + + if (banner[0]) + len += snprintf(page, PAGE_SIZE-len, "%s\n", banner); + + return len; +} + +/* same as in proc_misc.c */ +static int +proc_calc_metrics(char *page, char **start, off_t off, int count, int *eof, + int len) +{ + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int +read_version_entry(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int len = 0; + + /* data holds the NASID of the node */ + len = dump_version(page, (unsigned long)data); + len = proc_calc_metrics(page, start, off, count, eof, len); + return len; +} + +static int +read_fit_entry(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int len = 0; + + /* data holds the NASID of the node */ + len = dump_fit(page, (unsigned long)data); + len = proc_calc_metrics(page, start, off, count, eof, len); + + return len; +} + +/* module entry points */ +int __init prominfo_init(void); +void __exit prominfo_exit(void); + +module_init(prominfo_init); +module_exit(prominfo_exit); + +static struct proc_dir_entry **proc_entries; +static struct proc_dir_entry *sgi_prominfo_entry; + +#define NODE_NAME_LEN 11 + +int __init prominfo_init(void) +{ + struct proc_dir_entry **entp; + struct proc_dir_entry *p; + cnodeid_t cnodeid; + unsigned long nasid; + char name[NODE_NAME_LEN]; + + if (!ia64_platform_is("sn2")) + return 0; + + proc_entries = kmalloc(num_online_nodes() * sizeof(struct proc_dir_entry *), + GFP_KERNEL); + + sgi_prominfo_entry = proc_mkdir("sgi_prominfo", NULL); + + entp = proc_entries; + for_each_online_node(cnodeid) { + sprintf(name, "node%d", cnodeid); + *entp = proc_mkdir(name, sgi_prominfo_entry); + nasid = cnodeid_to_nasid(cnodeid); + p = create_proc_read_entry( + "fit", 0, *entp, read_fit_entry, + (void *)nasid); + if (p) + p->owner = THIS_MODULE; + p = create_proc_read_entry( + "version", 0, *entp, read_version_entry, + (void *)nasid); + if (p) + p->owner = THIS_MODULE; + entp++; + } + + return 0; +} + +void __exit prominfo_exit(void) +{ + struct proc_dir_entry **entp; + unsigned cnodeid; + char name[NODE_NAME_LEN]; + + entp = proc_entries; + for_each_online_node(cnodeid) { + remove_proc_entry("fit", *entp); + remove_proc_entry("version", *entp); + sprintf(name, "node%d", cnodeid); + remove_proc_entry(name, sgi_prominfo_entry); + entp++; + } + remove_proc_entry("sgi_prominfo", NULL); + kfree(proc_entries); +} diff --git a/arch/ia64/sn/kernel/sn2/ptc_deadlock.S b/arch/ia64/sn/kernel/sn2/ptc_deadlock.S new file mode 100644 index 00000000000..7947312801e --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/ptc_deadlock.S @@ -0,0 +1,82 @@ +/* + * 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. + * + * Copyright (C) 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <asm/sn/shub_mmr.h> + +#define DEADLOCKBIT SH_PIO_WRITE_STATUS_WRITE_DEADLOCK_SHFT +#define WRITECOUNTMASK SH_PIO_WRITE_STATUS_PENDING_WRITE_COUNT_MASK +#define ALIAS_OFFSET (SH1_PIO_WRITE_STATUS_0_ALIAS-SH1_PIO_WRITE_STATUS_0) + + + .global sn2_ptc_deadlock_recovery_core + .proc sn2_ptc_deadlock_recovery_core + +sn2_ptc_deadlock_recovery_core: + .regstk 6,0,0,0 + + ptc0 = in0 + data0 = in1 + ptc1 = in2 + data1 = in3 + piowc = in4 + zeroval = in5 + piowcphy = r30 + psrsave = r2 + scr1 = r16 + scr2 = r17 + mask = r18 + + + extr.u piowcphy=piowc,0,61;; // Convert piowc to uncached physical address + dep piowcphy=-1,piowcphy,63,1 + movl mask=WRITECOUNTMASK + +1: + add scr2=ALIAS_OFFSET,piowc // Address of WRITE_STATUS alias register + mov scr1=7;; // Clear DEADLOCK, WRITE_ERROR, MULTI_WRITE_ERROR + st8.rel [scr2]=scr1;; + +5: ld8.acq scr1=[piowc];; // Wait for PIOs to complete. + and scr2=scr1,mask;; // mask of writecount bits + cmp.ne p6,p0=zeroval,scr2 +(p6) br.cond.sptk 5b + + + + ////////////// BEGIN PHYSICAL MODE //////////////////// + mov psrsave=psr // Disable IC (no PMIs) + rsm psr.i | psr.dt | psr.ic;; + srlz.i;; + + st8.rel [ptc0]=data0 // Write PTC0 & wait for completion. + +5: ld8.acq scr1=[piowcphy];; // Wait for PIOs to complete. + and scr2=scr1,mask;; // mask of writecount bits + cmp.ne p6,p0=zeroval,scr2 +(p6) br.cond.sptk 5b;; + + tbit.nz p8,p7=scr1,DEADLOCKBIT;;// Test for DEADLOCK +(p7) cmp.ne p7,p0=r0,ptc1;; // Test for non-null ptc1 + +(p7) st8.rel [ptc1]=data1;; // Now write PTC1. + +5: ld8.acq scr1=[piowcphy];; // Wait for PIOs to complete. + and scr2=scr1,mask;; // mask of writecount bits + cmp.ne p6,p0=zeroval,scr2 +(p6) br.cond.sptk 5b + + tbit.nz p8,p0=scr1,DEADLOCKBIT;;// Test for DEADLOCK + + mov psr.l=psrsave;; // Reenable IC + srlz.i;; + ////////////// END PHYSICAL MODE //////////////////// + +(p8) br.cond.spnt 1b;; // Repeat if DEADLOCK occurred. + + br.ret.sptk rp + .endp sn2_ptc_deadlock_recovery_core diff --git a/arch/ia64/sn/kernel/sn2/sn2_smp.c b/arch/ia64/sn/kernel/sn2/sn2_smp.c new file mode 100644 index 00000000000..7af05a7ac74 --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/sn2_smp.c @@ -0,0 +1,295 @@ +/* + * SN2 Platform specific SMP Support + * + * 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. + * + * Copyright (C) 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/threads.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mmzone.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/nodemask.h> + +#include <asm/processor.h> +#include <asm/irq.h> +#include <asm/sal.h> +#include <asm/system.h> +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/smp.h> +#include <asm/tlb.h> +#include <asm/numa.h> +#include <asm/hw_irq.h> +#include <asm/current.h> +#include <asm/sn/sn_cpuid.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/addrs.h> +#include <asm/sn/shub_mmr.h> +#include <asm/sn/nodepda.h> +#include <asm/sn/rw_mmr.h> + +void sn2_ptc_deadlock_recovery(volatile unsigned long *, unsigned long data0, + volatile unsigned long *, unsigned long data1); + +static __cacheline_aligned DEFINE_SPINLOCK(sn2_global_ptc_lock); + +static unsigned long sn2_ptc_deadlock_count; + +static inline unsigned long wait_piowc(void) +{ + volatile unsigned long *piows, zeroval; + unsigned long ws; + + piows = pda->pio_write_status_addr; + zeroval = pda->pio_write_status_val; + do { + cpu_relax(); + } while (((ws = *piows) & SH_PIO_WRITE_STATUS_PENDING_WRITE_COUNT_MASK) != zeroval); + return ws; +} + +void sn_tlb_migrate_finish(struct mm_struct *mm) +{ + if (mm == current->mm) + flush_tlb_mm(mm); +} + +/** + * sn2_global_tlb_purge - globally purge translation cache of virtual address range + * @start: start of virtual address range + * @end: end of virtual address range + * @nbits: specifies number of bytes to purge per instruction (num = 1<<(nbits & 0xfc)) + * + * Purges the translation caches of all processors of the given virtual address + * range. + * + * Note: + * - cpu_vm_mask is a bit mask that indicates which cpus have loaded the context. + * - cpu_vm_mask is converted into a nodemask of the nodes containing the + * cpus in cpu_vm_mask. + * - if only one bit is set in cpu_vm_mask & it is the current cpu, + * then only the local TLB needs to be flushed. This flushing can be done + * using ptc.l. This is the common case & avoids the global spinlock. + * - if multiple cpus have loaded the context, then flushing has to be + * done with ptc.g/MMRs under protection of the global ptc_lock. + */ + +void +sn2_global_tlb_purge(unsigned long start, unsigned long end, + unsigned long nbits) +{ + int i, shub1, cnode, mynasid, cpu, lcpu = 0, nasid, flushed = 0; + volatile unsigned long *ptc0, *ptc1; + unsigned long flags = 0, data0 = 0, data1 = 0; + struct mm_struct *mm = current->active_mm; + short nasids[MAX_NUMNODES], nix; + nodemask_t nodes_flushed; + + nodes_clear(nodes_flushed); + i = 0; + + for_each_cpu_mask(cpu, mm->cpu_vm_mask) { + cnode = cpu_to_node(cpu); + node_set(cnode, nodes_flushed); + lcpu = cpu; + i++; + } + + preempt_disable(); + + if (likely(i == 1 && lcpu == smp_processor_id())) { + do { + ia64_ptcl(start, nbits << 2); + start += (1UL << nbits); + } while (start < end); + ia64_srlz_i(); + preempt_enable(); + return; + } + + if (atomic_read(&mm->mm_users) == 1) { + flush_tlb_mm(mm); + preempt_enable(); + return; + } + + nix = 0; + for_each_node_mask(cnode, nodes_flushed) + nasids[nix++] = cnodeid_to_nasid(cnode); + + shub1 = is_shub1(); + if (shub1) { + data0 = (1UL << SH1_PTC_0_A_SHFT) | + (nbits << SH1_PTC_0_PS_SHFT) | + ((ia64_get_rr(start) >> 8) << SH1_PTC_0_RID_SHFT) | + (1UL << SH1_PTC_0_START_SHFT); + ptc0 = (long *)GLOBAL_MMR_PHYS_ADDR(0, SH1_PTC_0); + ptc1 = (long *)GLOBAL_MMR_PHYS_ADDR(0, SH1_PTC_1); + } else { + data0 = (1UL << SH2_PTC_A_SHFT) | + (nbits << SH2_PTC_PS_SHFT) | + (1UL << SH2_PTC_START_SHFT); + ptc0 = (long *)GLOBAL_MMR_PHYS_ADDR(0, SH2_PTC + + ((ia64_get_rr(start) >> 8) << SH2_PTC_RID_SHFT) ); + ptc1 = NULL; + } + + + mynasid = get_nasid(); + + spin_lock_irqsave(&sn2_global_ptc_lock, flags); + + do { + if (shub1) + data1 = start | (1UL << SH1_PTC_1_START_SHFT); + else + data0 = (data0 & ~SH2_PTC_ADDR_MASK) | (start & SH2_PTC_ADDR_MASK); + for (i = 0; i < nix; i++) { + nasid = nasids[i]; + if (unlikely(nasid == mynasid)) { + ia64_ptcga(start, nbits << 2); + ia64_srlz_i(); + } else { + ptc0 = CHANGE_NASID(nasid, ptc0); + if (ptc1) + ptc1 = CHANGE_NASID(nasid, ptc1); + pio_atomic_phys_write_mmrs(ptc0, data0, ptc1, + data1); + flushed = 1; + } + } + + if (flushed + && (wait_piowc() & + SH_PIO_WRITE_STATUS_WRITE_DEADLOCK_MASK)) { + sn2_ptc_deadlock_recovery(ptc0, data0, ptc1, data1); + } + + start += (1UL << nbits); + + } while (start < end); + + spin_unlock_irqrestore(&sn2_global_ptc_lock, flags); + + preempt_enable(); +} + +/* + * sn2_ptc_deadlock_recovery + * + * Recover from PTC deadlocks conditions. Recovery requires stepping thru each + * TLB flush transaction. The recovery sequence is somewhat tricky & is + * coded in assembly language. + */ +void sn2_ptc_deadlock_recovery(volatile unsigned long *ptc0, unsigned long data0, + volatile unsigned long *ptc1, unsigned long data1) +{ + extern void sn2_ptc_deadlock_recovery_core(volatile unsigned long *, unsigned long, + volatile unsigned long *, unsigned long, volatile unsigned long *, unsigned long); + int cnode, mycnode, nasid; + volatile unsigned long *piows; + volatile unsigned long zeroval; + + sn2_ptc_deadlock_count++; + + piows = pda->pio_write_status_addr; + zeroval = pda->pio_write_status_val; + + mycnode = numa_node_id(); + + for_each_online_node(cnode) { + if (is_headless_node(cnode) || cnode == mycnode) + continue; + nasid = cnodeid_to_nasid(cnode); + ptc0 = CHANGE_NASID(nasid, ptc0); + if (ptc1) + ptc1 = CHANGE_NASID(nasid, ptc1); + sn2_ptc_deadlock_recovery_core(ptc0, data0, ptc1, data1, piows, zeroval); + } +} + +/** + * sn_send_IPI_phys - send an IPI to a Nasid and slice + * @nasid: nasid to receive the interrupt (may be outside partition) + * @physid: physical cpuid to receive the interrupt. + * @vector: command to send + * @delivery_mode: delivery mechanism + * + * Sends an IPI (interprocessor interrupt) to the processor specified by + * @physid + * + * @delivery_mode can be one of the following + * + * %IA64_IPI_DM_INT - pend an interrupt + * %IA64_IPI_DM_PMI - pend a PMI + * %IA64_IPI_DM_NMI - pend an NMI + * %IA64_IPI_DM_INIT - pend an INIT interrupt + */ +void sn_send_IPI_phys(int nasid, long physid, int vector, int delivery_mode) +{ + long val; + unsigned long flags = 0; + volatile long *p; + + p = (long *)GLOBAL_MMR_PHYS_ADDR(nasid, SH_IPI_INT); + val = (1UL << SH_IPI_INT_SEND_SHFT) | + (physid << SH_IPI_INT_PID_SHFT) | + ((long)delivery_mode << SH_IPI_INT_TYPE_SHFT) | + ((long)vector << SH_IPI_INT_IDX_SHFT) | + (0x000feeUL << SH_IPI_INT_BASE_SHFT); + + mb(); + if (enable_shub_wars_1_1()) { + spin_lock_irqsave(&sn2_global_ptc_lock, flags); + } + pio_phys_write_mmr(p, val); + if (enable_shub_wars_1_1()) { + wait_piowc(); + spin_unlock_irqrestore(&sn2_global_ptc_lock, flags); + } + +} + +EXPORT_SYMBOL(sn_send_IPI_phys); + +/** + * sn2_send_IPI - send an IPI to a processor + * @cpuid: target of the IPI + * @vector: command to send + * @delivery_mode: delivery mechanism + * @redirect: redirect the IPI? + * + * Sends an IPI (InterProcessor Interrupt) to the processor specified by + * @cpuid. @vector specifies the command to send, while @delivery_mode can + * be one of the following + * + * %IA64_IPI_DM_INT - pend an interrupt + * %IA64_IPI_DM_PMI - pend a PMI + * %IA64_IPI_DM_NMI - pend an NMI + * %IA64_IPI_DM_INIT - pend an INIT interrupt + */ +void sn2_send_IPI(int cpuid, int vector, int delivery_mode, int redirect) +{ + long physid; + int nasid; + + physid = cpu_physical_id(cpuid); + nasid = cpuid_to_nasid(cpuid); + + /* the following is used only when starting cpus at boot time */ + if (unlikely(nasid == -1)) + ia64_sn_get_sapic_info(physid, &nasid, NULL, NULL); + + sn_send_IPI_phys(nasid, physid, vector, delivery_mode); +} diff --git a/arch/ia64/sn/kernel/sn2/sn_hwperf.c b/arch/ia64/sn/kernel/sn2/sn_hwperf.c new file mode 100644 index 00000000000..197356460ee --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/sn_hwperf.c @@ -0,0 +1,690 @@ +/* + * 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. + * + * Copyright (C) 2004-2005 Silicon Graphics, Inc. All rights reserved. + * + * SGI Altix topology and hardware performance monitoring API. + * Mark Goodwin <markgw@sgi.com>. + * + * Creates /proc/sgi_sn/sn_topology (read-only) to export + * info about Altix nodes, routers, CPUs and NumaLink + * interconnection/topology. + * + * Also creates a dynamic misc device named "sn_hwperf" + * that supports an ioctl interface to call down into SAL + * to discover hw objects, topology and to read/write + * memory mapped registers, e.g. for performance monitoring. + * The "sn_hwperf" device is registered only after the procfs + * file is first opened, i.e. only if/when it's needed. + * + * This API is used by SGI Performance Co-Pilot and other + * tools, see http://oss.sgi.com/projects/pcp + */ + +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/seq_file.h> +#include <linux/miscdevice.h> +#include <linux/cpumask.h> +#include <linux/smp_lock.h> +#include <linux/nodemask.h> +#include <asm/processor.h> +#include <asm/topology.h> +#include <asm/smp.h> +#include <asm/semaphore.h> +#include <asm/segment.h> +#include <asm/uaccess.h> +#include <asm/sal.h> +#include <asm/sn/io.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/module.h> +#include <asm/sn/geo.h> +#include <asm/sn/sn2/sn_hwperf.h> + +static void *sn_hwperf_salheap = NULL; +static int sn_hwperf_obj_cnt = 0; +static nasid_t sn_hwperf_master_nasid = INVALID_NASID; +static int sn_hwperf_init(void); +static DECLARE_MUTEX(sn_hwperf_init_mutex); + +static int sn_hwperf_enum_objects(int *nobj, struct sn_hwperf_object_info **ret) +{ + int e; + u64 sz; + struct sn_hwperf_object_info *objbuf = NULL; + + if ((e = sn_hwperf_init()) < 0) { + printk("sn_hwperf_init failed: err %d\n", e); + goto out; + } + + sz = sn_hwperf_obj_cnt * sizeof(struct sn_hwperf_object_info); + if ((objbuf = (struct sn_hwperf_object_info *) vmalloc(sz)) == NULL) { + printk("sn_hwperf_enum_objects: vmalloc(%d) failed\n", (int)sz); + e = -ENOMEM; + goto out; + } + + e = ia64_sn_hwperf_op(sn_hwperf_master_nasid, SN_HWPERF_ENUM_OBJECTS, + 0, sz, (u64) objbuf, 0, 0, NULL); + if (e != SN_HWPERF_OP_OK) { + e = -EINVAL; + vfree(objbuf); + } + +out: + *nobj = sn_hwperf_obj_cnt; + *ret = objbuf; + return e; +} + +static int sn_hwperf_geoid_to_cnode(char *location) +{ + int cnode; + geoid_t geoid; + moduleid_t module_id; + char type; + int rack, slot, slab; + int this_rack, this_slot, this_slab; + + if (sscanf(location, "%03d%c%02d#%d", &rack, &type, &slot, &slab) != 4) + return -1; + + for (cnode = 0; cnode < numionodes; cnode++) { + geoid = cnodeid_get_geoid(cnode); + module_id = geo_module(geoid); + this_rack = MODULE_GET_RACK(module_id); + this_slot = MODULE_GET_BPOS(module_id); + this_slab = geo_slab(geoid); + if (rack == this_rack && slot == this_slot && slab == this_slab) + break; + } + + return cnode < numionodes ? cnode : -1; +} + +static int sn_hwperf_obj_to_cnode(struct sn_hwperf_object_info * obj) +{ + if (!obj->sn_hwp_this_part) + return -1; + return sn_hwperf_geoid_to_cnode(obj->location); +} + +static int sn_hwperf_generic_ordinal(struct sn_hwperf_object_info *obj, + struct sn_hwperf_object_info *objs) +{ + int ordinal; + struct sn_hwperf_object_info *p; + + for (ordinal=0, p=objs; p != obj; p++) { + if (SN_HWPERF_FOREIGN(p)) + continue; + if (SN_HWPERF_SAME_OBJTYPE(p, obj)) + ordinal++; + } + + return ordinal; +} + +static const char *slabname_node = "node"; /* SHub asic */ +static const char *slabname_ionode = "ionode"; /* TIO asic */ +static const char *slabname_router = "router"; /* NL3R or NL4R */ +static const char *slabname_other = "other"; /* unknown asic */ + +static const char *sn_hwperf_get_slabname(struct sn_hwperf_object_info *obj, + struct sn_hwperf_object_info *objs, int *ordinal) +{ + int isnode; + const char *slabname = slabname_other; + + if ((isnode = SN_HWPERF_IS_NODE(obj)) || SN_HWPERF_IS_IONODE(obj)) { + slabname = isnode ? slabname_node : slabname_ionode; + *ordinal = sn_hwperf_obj_to_cnode(obj); + } + else { + *ordinal = sn_hwperf_generic_ordinal(obj, objs); + if (SN_HWPERF_IS_ROUTER(obj)) + slabname = slabname_router; + } + + return slabname; +} + +static int sn_topology_show(struct seq_file *s, void *d) +{ + int sz; + int pt; + int e; + int i; + int j; + const char *slabname; + int ordinal; + cpumask_t cpumask; + char slice; + struct cpuinfo_ia64 *c; + struct sn_hwperf_port_info *ptdata; + struct sn_hwperf_object_info *p; + struct sn_hwperf_object_info *obj = d; /* this object */ + struct sn_hwperf_object_info *objs = s->private; /* all objects */ + + if (obj == objs) { + seq_printf(s, "# sn_topology version 1\n"); + seq_printf(s, "# objtype ordinal location partition" + " [attribute value [, ...]]\n"); + } + + if (SN_HWPERF_FOREIGN(obj)) { + /* private in another partition: not interesting */ + return 0; + } + + for (i = 0; obj->name[i]; i++) { + if (obj->name[i] == ' ') + obj->name[i] = '_'; + } + + slabname = sn_hwperf_get_slabname(obj, objs, &ordinal); + seq_printf(s, "%s %d %s %s asic %s", slabname, ordinal, obj->location, + obj->sn_hwp_this_part ? "local" : "shared", obj->name); + + if (!SN_HWPERF_IS_NODE(obj) && !SN_HWPERF_IS_IONODE(obj)) + seq_putc(s, '\n'); + else { + seq_printf(s, ", nasid 0x%x", cnodeid_to_nasid(ordinal)); + for (i=0; i < numionodes; i++) { + seq_printf(s, i ? ":%d" : ", dist %d", + node_distance(ordinal, i)); + } + seq_putc(s, '\n'); + + /* + * CPUs on this node, if any + */ + cpumask = node_to_cpumask(ordinal); + for_each_online_cpu(i) { + if (cpu_isset(i, cpumask)) { + slice = 'a' + cpuid_to_slice(i); + c = cpu_data(i); + seq_printf(s, "cpu %d %s%c local" + " freq %luMHz, arch ia64", + i, obj->location, slice, + c->proc_freq / 1000000); + for_each_online_cpu(j) { + seq_printf(s, j ? ":%d" : ", dist %d", + node_distance( + cpuid_to_cnodeid(i), + cpuid_to_cnodeid(j))); + } + seq_putc(s, '\n'); + } + } + } + + if (obj->ports) { + /* + * numalink ports + */ + sz = obj->ports * sizeof(struct sn_hwperf_port_info); + if ((ptdata = vmalloc(sz)) == NULL) + return -ENOMEM; + e = ia64_sn_hwperf_op(sn_hwperf_master_nasid, + SN_HWPERF_ENUM_PORTS, obj->id, sz, + (u64) ptdata, 0, 0, NULL); + if (e != SN_HWPERF_OP_OK) + return -EINVAL; + for (ordinal=0, p=objs; p != obj; p++) { + if (!SN_HWPERF_FOREIGN(p)) + ordinal += p->ports; + } + for (pt = 0; pt < obj->ports; pt++) { + for (p = objs, i = 0; i < sn_hwperf_obj_cnt; i++, p++) { + if (ptdata[pt].conn_id == p->id) { + break; + } + } + seq_printf(s, "numalink %d %s-%d", + ordinal+pt, obj->location, ptdata[pt].port); + + if (i >= sn_hwperf_obj_cnt) { + /* no connection */ + seq_puts(s, " local endpoint disconnected" + ", protocol unknown\n"); + continue; + } + + if (obj->sn_hwp_this_part && p->sn_hwp_this_part) + /* both ends local to this partition */ + seq_puts(s, " local"); + else if (!obj->sn_hwp_this_part && !p->sn_hwp_this_part) + /* both ends of the link in foreign partiton */ + seq_puts(s, " foreign"); + else + /* link straddles a partition */ + seq_puts(s, " shared"); + + /* + * Unlikely, but strictly should query the LLP config + * registers because an NL4R can be configured to run + * NL3 protocol, even when not talking to an NL3 router. + * Ditto for node-node. + */ + seq_printf(s, " endpoint %s-%d, protocol %s\n", + p->location, ptdata[pt].conn_port, + (SN_HWPERF_IS_NL3ROUTER(obj) || + SN_HWPERF_IS_NL3ROUTER(p)) ? "LLP3" : "LLP4"); + } + vfree(ptdata); + } + + return 0; +} + +static void *sn_topology_start(struct seq_file *s, loff_t * pos) +{ + struct sn_hwperf_object_info *objs = s->private; + + if (*pos < sn_hwperf_obj_cnt) + return (void *)(objs + *pos); + + return NULL; +} + +static void *sn_topology_next(struct seq_file *s, void *v, loff_t * pos) +{ + ++*pos; + return sn_topology_start(s, pos); +} + +static void sn_topology_stop(struct seq_file *m, void *v) +{ + return; +} + +/* + * /proc/sgi_sn/sn_topology, read-only using seq_file + */ +static struct seq_operations sn_topology_seq_ops = { + .start = sn_topology_start, + .next = sn_topology_next, + .stop = sn_topology_stop, + .show = sn_topology_show +}; + +struct sn_hwperf_op_info { + u64 op; + struct sn_hwperf_ioctl_args *a; + void *p; + int *v0; + int ret; +}; + +static void sn_hwperf_call_sal(void *info) +{ + struct sn_hwperf_op_info *op_info = info; + int r; + + r = ia64_sn_hwperf_op(sn_hwperf_master_nasid, op_info->op, + op_info->a->arg, op_info->a->sz, + (u64) op_info->p, 0, 0, op_info->v0); + op_info->ret = r; +} + +static int sn_hwperf_op_cpu(struct sn_hwperf_op_info *op_info) +{ + u32 cpu; + u32 use_ipi; + int r = 0; + cpumask_t save_allowed; + + cpu = (op_info->a->arg & SN_HWPERF_ARG_CPU_MASK) >> 32; + use_ipi = op_info->a->arg & SN_HWPERF_ARG_USE_IPI_MASK; + op_info->a->arg &= SN_HWPERF_ARG_OBJID_MASK; + + if (cpu != SN_HWPERF_ARG_ANY_CPU) { + if (cpu >= num_online_cpus() || !cpu_online(cpu)) { + r = -EINVAL; + goto out; + } + } + + if (cpu == SN_HWPERF_ARG_ANY_CPU || cpu == get_cpu()) { + /* don't care, or already on correct cpu */ + sn_hwperf_call_sal(op_info); + } + else { + if (use_ipi) { + /* use an interprocessor interrupt to call SAL */ + smp_call_function_single(cpu, sn_hwperf_call_sal, + op_info, 1, 1); + } + else { + /* migrate the task before calling SAL */ + save_allowed = current->cpus_allowed; + set_cpus_allowed(current, cpumask_of_cpu(cpu)); + sn_hwperf_call_sal(op_info); + set_cpus_allowed(current, save_allowed); + } + } + r = op_info->ret; + +out: + return r; +} + +/* map SAL hwperf error code to system error code */ +static int sn_hwperf_map_err(int hwperf_err) +{ + int e; + + switch(hwperf_err) { + case SN_HWPERF_OP_OK: + e = 0; + break; + + case SN_HWPERF_OP_NOMEM: + e = -ENOMEM; + break; + + case SN_HWPERF_OP_NO_PERM: + e = -EPERM; + break; + + case SN_HWPERF_OP_IO_ERROR: + e = -EIO; + break; + + case SN_HWPERF_OP_BUSY: + case SN_HWPERF_OP_RECONFIGURE: + e = -EAGAIN; + break; + + case SN_HWPERF_OP_INVAL: + default: + e = -EINVAL; + break; + } + + return e; +} + +/* + * ioctl for "sn_hwperf" misc device + */ +static int +sn_hwperf_ioctl(struct inode *in, struct file *fp, u32 op, u64 arg) +{ + struct sn_hwperf_ioctl_args a; + struct cpuinfo_ia64 *cdata; + struct sn_hwperf_object_info *objs; + struct sn_hwperf_object_info *cpuobj; + struct sn_hwperf_op_info op_info; + void *p = NULL; + int nobj; + char slice; + int node; + int r; + int v0; + int i; + int j; + + unlock_kernel(); + + /* only user requests are allowed here */ + if ((op & SN_HWPERF_OP_MASK) < 10) { + r = -EINVAL; + goto error; + } + r = copy_from_user(&a, (const void __user *)arg, + sizeof(struct sn_hwperf_ioctl_args)); + if (r != 0) { + r = -EFAULT; + goto error; + } + + /* + * Allocate memory to hold a kernel copy of the user buffer. The + * buffer contents are either copied in or out (or both) of user + * space depending on the flags encoded in the requested operation. + */ + if (a.ptr) { + p = vmalloc(a.sz); + if (!p) { + r = -ENOMEM; + goto error; + } + } + + if (op & SN_HWPERF_OP_MEM_COPYIN) { + r = copy_from_user(p, (const void __user *)a.ptr, a.sz); + if (r != 0) { + r = -EFAULT; + goto error; + } + } + + switch (op) { + case SN_HWPERF_GET_CPU_INFO: + if (a.sz == sizeof(u64)) { + /* special case to get size needed */ + *(u64 *) p = (u64) num_online_cpus() * + sizeof(struct sn_hwperf_object_info); + } else + if (a.sz < num_online_cpus() * sizeof(struct sn_hwperf_object_info)) { + r = -ENOMEM; + goto error; + } else + if ((r = sn_hwperf_enum_objects(&nobj, &objs)) == 0) { + memset(p, 0, a.sz); + for (i = 0; i < nobj; i++) { + node = sn_hwperf_obj_to_cnode(objs + i); + for_each_online_cpu(j) { + if (node != cpu_to_node(j)) + continue; + cpuobj = (struct sn_hwperf_object_info *) p + j; + slice = 'a' + cpuid_to_slice(j); + cdata = cpu_data(j); + cpuobj->id = j; + snprintf(cpuobj->name, + sizeof(cpuobj->name), + "CPU %luMHz %s", + cdata->proc_freq / 1000000, + cdata->vendor); + snprintf(cpuobj->location, + sizeof(cpuobj->location), + "%s%c", objs[i].location, + slice); + } + } + + vfree(objs); + } + break; + + case SN_HWPERF_GET_NODE_NASID: + if (a.sz != sizeof(u64) || + (node = a.arg) < 0 || node >= numionodes) { + r = -EINVAL; + goto error; + } + *(u64 *)p = (u64)cnodeid_to_nasid(node); + break; + + case SN_HWPERF_GET_OBJ_NODE: + if (a.sz != sizeof(u64) || a.arg < 0) { + r = -EINVAL; + goto error; + } + if ((r = sn_hwperf_enum_objects(&nobj, &objs)) == 0) { + if (a.arg >= nobj) { + r = -EINVAL; + vfree(objs); + goto error; + } + if (objs[(i = a.arg)].id != a.arg) { + for (i = 0; i < nobj; i++) { + if (objs[i].id == a.arg) + break; + } + } + if (i == nobj) { + r = -EINVAL; + vfree(objs); + goto error; + } + *(u64 *)p = (u64)sn_hwperf_obj_to_cnode(objs + i); + vfree(objs); + } + break; + + case SN_HWPERF_GET_MMRS: + case SN_HWPERF_SET_MMRS: + case SN_HWPERF_OBJECT_DISTANCE: + op_info.p = p; + op_info.a = &a; + op_info.v0 = &v0; + op_info.op = op; + r = sn_hwperf_op_cpu(&op_info); + if (r) { + r = sn_hwperf_map_err(r); + goto error; + } + break; + + default: + /* all other ops are a direct SAL call */ + r = ia64_sn_hwperf_op(sn_hwperf_master_nasid, op, + a.arg, a.sz, (u64) p, 0, 0, &v0); + if (r) { + r = sn_hwperf_map_err(r); + goto error; + } + a.v0 = v0; + break; + } + + if (op & SN_HWPERF_OP_MEM_COPYOUT) { + r = copy_to_user((void __user *)a.ptr, p, a.sz); + if (r != 0) { + r = -EFAULT; + goto error; + } + } + +error: + vfree(p); + + lock_kernel(); + return r; +} + +static struct file_operations sn_hwperf_fops = { + .ioctl = sn_hwperf_ioctl, +}; + +static struct miscdevice sn_hwperf_dev = { + MISC_DYNAMIC_MINOR, + "sn_hwperf", + &sn_hwperf_fops +}; + +static int sn_hwperf_init(void) +{ + u64 v; + int salr; + int e = 0; + + /* single threaded, once-only initialization */ + down(&sn_hwperf_init_mutex); + if (sn_hwperf_salheap) { + up(&sn_hwperf_init_mutex); + return e; + } + + /* + * The PROM code needs a fixed reference node. For convenience the + * same node as the console I/O is used. + */ + sn_hwperf_master_nasid = (nasid_t) ia64_sn_get_console_nasid(); + + /* + * Request the needed size and install the PROM scratch area. + * The PROM keeps various tracking bits in this memory area. + */ + salr = ia64_sn_hwperf_op(sn_hwperf_master_nasid, + (u64) SN_HWPERF_GET_HEAPSIZE, 0, + (u64) sizeof(u64), (u64) &v, 0, 0, NULL); + if (salr != SN_HWPERF_OP_OK) { + e = -EINVAL; + goto out; + } + + if ((sn_hwperf_salheap = vmalloc(v)) == NULL) { + e = -ENOMEM; + goto out; + } + salr = ia64_sn_hwperf_op(sn_hwperf_master_nasid, + SN_HWPERF_INSTALL_HEAP, 0, v, + (u64) sn_hwperf_salheap, 0, 0, NULL); + if (salr != SN_HWPERF_OP_OK) { + e = -EINVAL; + goto out; + } + + salr = ia64_sn_hwperf_op(sn_hwperf_master_nasid, + SN_HWPERF_OBJECT_COUNT, 0, + sizeof(u64), (u64) &v, 0, 0, NULL); + if (salr != SN_HWPERF_OP_OK) { + e = -EINVAL; + goto out; + } + sn_hwperf_obj_cnt = (int)v; + +out: + if (e < 0 && sn_hwperf_salheap) { + vfree(sn_hwperf_salheap); + sn_hwperf_salheap = NULL; + sn_hwperf_obj_cnt = 0; + } + + if (!e) { + /* + * Register a dynamic misc device for ioctl. Platforms + * supporting hotplug will create /dev/sn_hwperf, else + * user can to look up the minor number in /proc/misc. + */ + if ((e = misc_register(&sn_hwperf_dev)) != 0) { + printk(KERN_ERR "sn_hwperf_init: misc register " + "for \"sn_hwperf\" failed, err %d\n", e); + } + } + + up(&sn_hwperf_init_mutex); + return e; +} + +int sn_topology_open(struct inode *inode, struct file *file) +{ + int e; + struct seq_file *seq; + struct sn_hwperf_object_info *objbuf; + int nobj; + + if ((e = sn_hwperf_enum_objects(&nobj, &objbuf)) == 0) { + e = seq_open(file, &sn_topology_seq_ops); + seq = file->private_data; + seq->private = objbuf; + } + + return e; +} + +int sn_topology_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq = file->private_data; + + vfree(seq->private); + return seq_release(inode, file); +} diff --git a/arch/ia64/sn/kernel/sn2/sn_proc_fs.c b/arch/ia64/sn/kernel/sn2/sn_proc_fs.c new file mode 100644 index 00000000000..6a80fca807b --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/sn_proc_fs.c @@ -0,0 +1,149 @@ +/* + * 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. + * + * Copyright (C) 2000-2004 Silicon Graphics, Inc. All rights reserved. + */ +#include <linux/config.h> +#include <asm/uaccess.h> + +#ifdef CONFIG_PROC_FS +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/sn/sn_sal.h> + +static int partition_id_show(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", sn_local_partid()); + return 0; +} + +static int partition_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, partition_id_show, NULL); +} + +static int system_serial_number_show(struct seq_file *s, void *p) +{ + seq_printf(s, "%s\n", sn_system_serial_number()); + return 0; +} + +static int system_serial_number_open(struct inode *inode, struct file *file) +{ + return single_open(file, system_serial_number_show, NULL); +} + +static int licenseID_show(struct seq_file *s, void *p) +{ + seq_printf(s, "0x%lx\n", sn_partition_serial_number_val()); + return 0; +} + +static int licenseID_open(struct inode *inode, struct file *file) +{ + return single_open(file, licenseID_show, NULL); +} + +/* + * Enable forced interrupt by default. + * When set, the sn interrupt handler writes the force interrupt register on + * the bridge chip. The hardware will then send an interrupt message if the + * interrupt line is active. This mimics a level sensitive interrupt. + */ +int sn_force_interrupt_flag = 1; + +static int sn_force_interrupt_show(struct seq_file *s, void *p) +{ + seq_printf(s, "Force interrupt is %s\n", + sn_force_interrupt_flag ? "enabled" : "disabled"); + return 0; +} + +static ssize_t sn_force_interrupt_write_proc(struct file *file, + const char __user *buffer, size_t count, loff_t *data) +{ + char val; + + if (copy_from_user(&val, buffer, 1)) + return -EFAULT; + + sn_force_interrupt_flag = (val == '0') ? 0 : 1; + return count; +} + +static int sn_force_interrupt_open(struct inode *inode, struct file *file) +{ + return single_open(file, sn_force_interrupt_show, NULL); +} + +static int coherence_id_show(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", partition_coherence_id()); + + return 0; +} + +static int coherence_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, coherence_id_show, NULL); +} + +static struct proc_dir_entry *sn_procfs_create_entry( + const char *name, struct proc_dir_entry *parent, + int (*openfunc)(struct inode *, struct file *), + int (*releasefunc)(struct inode *, struct file *)) +{ + struct proc_dir_entry *e = create_proc_entry(name, 0444, parent); + + if (e) { + e->proc_fops = (struct file_operations *)kmalloc( + sizeof(struct file_operations), GFP_KERNEL); + if (e->proc_fops) { + memset(e->proc_fops, 0, sizeof(struct file_operations)); + e->proc_fops->open = openfunc; + e->proc_fops->read = seq_read; + e->proc_fops->llseek = seq_lseek; + e->proc_fops->release = releasefunc; + } + } + + return e; +} + +/* /proc/sgi_sn/sn_topology uses seq_file, see sn_hwperf.c */ +extern int sn_topology_open(struct inode *, struct file *); +extern int sn_topology_release(struct inode *, struct file *); + +void register_sn_procfs(void) +{ + static struct proc_dir_entry *sgi_proc_dir = NULL; + struct proc_dir_entry *e; + + BUG_ON(sgi_proc_dir != NULL); + if (!(sgi_proc_dir = proc_mkdir("sgi_sn", NULL))) + return; + + sn_procfs_create_entry("partition_id", sgi_proc_dir, + partition_id_open, single_release); + + sn_procfs_create_entry("system_serial_number", sgi_proc_dir, + system_serial_number_open, single_release); + + sn_procfs_create_entry("licenseID", sgi_proc_dir, + licenseID_open, single_release); + + e = sn_procfs_create_entry("sn_force_interrupt", sgi_proc_dir, + sn_force_interrupt_open, single_release); + if (e) + e->proc_fops->write = sn_force_interrupt_write_proc; + + sn_procfs_create_entry("coherence_id", sgi_proc_dir, + coherence_id_open, single_release); + + sn_procfs_create_entry("sn_topology", sgi_proc_dir, + sn_topology_open, sn_topology_release); +} + +#endif /* CONFIG_PROC_FS */ diff --git a/arch/ia64/sn/kernel/sn2/timer.c b/arch/ia64/sn/kernel/sn2/timer.c new file mode 100644 index 00000000000..deb9baf4d47 --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/timer.c @@ -0,0 +1,36 @@ +/* + * linux/arch/ia64/sn/kernel/sn2/timer.c + * + * Copyright (C) 2003 Silicon Graphics, Inc. + * Copyright (C) 2003 Hewlett-Packard Co + * David Mosberger <davidm@hpl.hp.com>: updated for new timer-interpolation infrastructure + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/interrupt.h> + +#include <asm/hw_irq.h> +#include <asm/system.h> + +#include <asm/sn/leds.h> +#include <asm/sn/shub_mmr.h> +#include <asm/sn/clksupport.h> + +extern unsigned long sn_rtc_cycles_per_second; + +static struct time_interpolator sn2_interpolator = { + .drift = -1, + .shift = 10, + .mask = (1LL << 55) - 1, + .source = TIME_SOURCE_MMIO64 +}; + +void __init sn_timer_init(void) +{ + sn2_interpolator.frequency = sn_rtc_cycles_per_second; + sn2_interpolator.addr = RTC_COUNTER_ADDR; + register_time_interpolator(&sn2_interpolator); +} diff --git a/arch/ia64/sn/kernel/sn2/timer_interrupt.c b/arch/ia64/sn/kernel/sn2/timer_interrupt.c new file mode 100644 index 00000000000..cde7375390b --- /dev/null +++ b/arch/ia64/sn/kernel/sn2/timer_interrupt.c @@ -0,0 +1,63 @@ +/* + * + * + * Copyright (c) 2003 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan + */ + +#include <linux/interrupt.h> +#include <asm/sn/pda.h> +#include <asm/sn/leds.h> + +extern void sn_lb_int_war_check(void); +extern irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +#define SN_LB_INT_WAR_INTERVAL 100 + +void sn_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* LED blinking */ + if (!pda->hb_count--) { + pda->hb_count = HZ / 2; + set_led_bits(pda->hb_state ^= + LED_CPU_HEARTBEAT, LED_CPU_HEARTBEAT); + } + + if (enable_shub_wars_1_1()) { + /* Bugfix code for SHUB 1.1 */ + if (pda->pio_shub_war_cam_addr) + *pda->pio_shub_war_cam_addr = 0x8000000000000010UL; + } + if (pda->sn_lb_int_war_ticks == 0) + sn_lb_int_war_check(); + pda->sn_lb_int_war_ticks++; + if (pda->sn_lb_int_war_ticks >= SN_LB_INT_WAR_INTERVAL) + pda->sn_lb_int_war_ticks = 0; +} diff --git a/arch/ia64/sn/pci/Makefile b/arch/ia64/sn/pci/Makefile new file mode 100644 index 00000000000..b5dca0097a8 --- /dev/null +++ b/arch/ia64/sn/pci/Makefile @@ -0,0 +1,10 @@ +# +# 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. +# +# Copyright (C) 2000-2004 Silicon Graphics, Inc. All Rights Reserved. +# +# Makefile for the sn pci general routines. + +obj-y := pci_dma.o pcibr/ diff --git a/arch/ia64/sn/pci/pci_dma.c b/arch/ia64/sn/pci/pci_dma.c new file mode 100644 index 00000000000..f680824f819 --- /dev/null +++ b/arch/ia64/sn/pci/pci_dma.c @@ -0,0 +1,363 @@ +/* + * 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. + * + * Copyright (C) 2000,2002-2005 Silicon Graphics, Inc. All rights reserved. + * + * Routines for PCI DMA mapping. See Documentation/DMA-API.txt for + * a description of how these routines should be used. + */ + +#include <linux/module.h> +#include <asm/dma.h> +#include <asm/sn/sn_sal.h> +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/pcibr_provider.h" + +#define SG_ENT_VIRT_ADDRESS(sg) (page_address((sg)->page) + (sg)->offset) +#define SG_ENT_PHYS_ADDRESS(SG) virt_to_phys(SG_ENT_VIRT_ADDRESS(SG)) + +/** + * sn_dma_supported - test a DMA mask + * @dev: device to test + * @mask: DMA mask to test + * + * Return whether the given PCI device DMA address mask can be supported + * properly. For example, if your device can only drive the low 24-bits + * during PCI bus mastering, then you would pass 0x00ffffff as the mask to + * this function. Of course, SN only supports devices that have 32 or more + * address bits when using the PMU. + */ +int sn_dma_supported(struct device *dev, u64 mask) +{ + BUG_ON(dev->bus != &pci_bus_type); + + if (mask < 0x7fffffff) + return 0; + return 1; +} +EXPORT_SYMBOL(sn_dma_supported); + +/** + * sn_dma_set_mask - set the DMA mask + * @dev: device to set + * @dma_mask: new mask + * + * Set @dev's DMA mask if the hw supports it. + */ +int sn_dma_set_mask(struct device *dev, u64 dma_mask) +{ + BUG_ON(dev->bus != &pci_bus_type); + + if (!sn_dma_supported(dev, dma_mask)) + return 0; + + *dev->dma_mask = dma_mask; + return 1; +} +EXPORT_SYMBOL(sn_dma_set_mask); + +/** + * sn_dma_alloc_coherent - allocate memory for coherent DMA + * @dev: device to allocate for + * @size: size of the region + * @dma_handle: DMA (bus) address + * @flags: memory allocation flags + * + * dma_alloc_coherent() returns a pointer to a memory region suitable for + * coherent DMA traffic to/from a PCI device. On SN platforms, this means + * that @dma_handle will have the %PCIIO_DMA_CMD flag set. + * + * This interface is usually used for "command" streams (e.g. the command + * queue for a SCSI controller). See Documentation/DMA-API.txt for + * more information. + */ +void *sn_dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t * dma_handle, int flags) +{ + void *cpuaddr; + unsigned long phys_addr; + struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev)); + + BUG_ON(dev->bus != &pci_bus_type); + + /* + * Allocate the memory. + * FIXME: We should be doing alloc_pages_node for the node closest + * to the PCI device. + */ + if (!(cpuaddr = (void *)__get_free_pages(GFP_ATOMIC, get_order(size)))) + return NULL; + + memset(cpuaddr, 0x0, size); + + /* physical addr. of the memory we just got */ + phys_addr = __pa(cpuaddr); + + /* + * 64 bit address translations should never fail. + * 32 bit translations can fail if there are insufficient mapping + * resources. + */ + + *dma_handle = pcibr_dma_map(pcidev_info, phys_addr, size, + SN_PCIDMA_CONSISTENT); + if (!*dma_handle) { + printk(KERN_ERR "%s: out of ATEs\n", __FUNCTION__); + free_pages((unsigned long)cpuaddr, get_order(size)); + return NULL; + } + + return cpuaddr; +} +EXPORT_SYMBOL(sn_dma_alloc_coherent); + +/** + * sn_pci_free_coherent - free memory associated with coherent DMAable region + * @dev: device to free for + * @size: size to free + * @cpu_addr: kernel virtual address to free + * @dma_handle: DMA address associated with this region + * + * Frees the memory allocated by dma_alloc_coherent(), potentially unmapping + * any associated IOMMU mappings. + */ +void sn_dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, + dma_addr_t dma_handle) +{ + struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev)); + + BUG_ON(dev->bus != &pci_bus_type); + + pcibr_dma_unmap(pcidev_info, dma_handle, 0); + free_pages((unsigned long)cpu_addr, get_order(size)); +} +EXPORT_SYMBOL(sn_dma_free_coherent); + +/** + * sn_dma_map_single - map a single page for DMA + * @dev: device to map for + * @cpu_addr: kernel virtual address of the region to map + * @size: size of the region + * @direction: DMA direction + * + * Map the region pointed to by @cpu_addr for DMA and return the + * DMA address. + * + * We map this to the one step pcibr_dmamap_trans interface rather than + * the two step pcibr_dmamap_alloc/pcibr_dmamap_addr because we have + * no way of saving the dmamap handle from the alloc to later free + * (which is pretty much unacceptable). + * + * TODO: simplify our interface; + * figure out how to save dmamap handle so can use two step. + */ +dma_addr_t sn_dma_map_single(struct device *dev, void *cpu_addr, size_t size, + int direction) +{ + dma_addr_t dma_addr; + unsigned long phys_addr; + struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev)); + + BUG_ON(dev->bus != &pci_bus_type); + + phys_addr = __pa(cpu_addr); + dma_addr = pcibr_dma_map(pcidev_info, phys_addr, size, 0); + if (!dma_addr) { + printk(KERN_ERR "%s: out of ATEs\n", __FUNCTION__); + return 0; + } + return dma_addr; +} +EXPORT_SYMBOL(sn_dma_map_single); + +/** + * sn_dma_unmap_single - unamp a DMA mapped page + * @dev: device to sync + * @dma_addr: DMA address to sync + * @size: size of region + * @direction: DMA direction + * + * This routine is supposed to sync the DMA region specified + * by @dma_handle into the coherence domain. On SN, we're always cache + * coherent, so we just need to free any ATEs associated with this mapping. + */ +void sn_dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, + int direction) +{ + struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev)); + + BUG_ON(dev->bus != &pci_bus_type); + pcibr_dma_unmap(pcidev_info, dma_addr, direction); +} +EXPORT_SYMBOL(sn_dma_unmap_single); + +/** + * sn_dma_unmap_sg - unmap a DMA scatterlist + * @dev: device to unmap + * @sg: scatterlist to unmap + * @nhwentries: number of scatterlist entries + * @direction: DMA direction + * + * Unmap a set of streaming mode DMA translations. + */ +void sn_dma_unmap_sg(struct device *dev, struct scatterlist *sg, + int nhwentries, int direction) +{ + int i; + struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev)); + + BUG_ON(dev->bus != &pci_bus_type); + + for (i = 0; i < nhwentries; i++, sg++) { + pcibr_dma_unmap(pcidev_info, sg->dma_address, direction); + sg->dma_address = (dma_addr_t) NULL; + sg->dma_length = 0; + } +} +EXPORT_SYMBOL(sn_dma_unmap_sg); + +/** + * sn_dma_map_sg - map a scatterlist for DMA + * @dev: device to map for + * @sg: scatterlist to map + * @nhwentries: number of entries + * @direction: direction of the DMA transaction + * + * Maps each entry of @sg for DMA. + */ +int sn_dma_map_sg(struct device *dev, struct scatterlist *sg, int nhwentries, + int direction) +{ + unsigned long phys_addr; + struct scatterlist *saved_sg = sg; + struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev)); + int i; + + BUG_ON(dev->bus != &pci_bus_type); + + /* + * Setup a DMA address for each entry in the scatterlist. + */ + for (i = 0; i < nhwentries; i++, sg++) { + phys_addr = SG_ENT_PHYS_ADDRESS(sg); + sg->dma_address = pcibr_dma_map(pcidev_info, phys_addr, + sg->length, 0); + + if (!sg->dma_address) { + printk(KERN_ERR "%s: out of ATEs\n", __FUNCTION__); + + /* + * Free any successfully allocated entries. + */ + if (i > 0) + sn_dma_unmap_sg(dev, saved_sg, i, direction); + return 0; + } + + sg->dma_length = sg->length; + } + + return nhwentries; +} +EXPORT_SYMBOL(sn_dma_map_sg); + +void sn_dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, + size_t size, int direction) +{ + BUG_ON(dev->bus != &pci_bus_type); +} +EXPORT_SYMBOL(sn_dma_sync_single_for_cpu); + +void sn_dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, + size_t size, int direction) +{ + BUG_ON(dev->bus != &pci_bus_type); +} +EXPORT_SYMBOL(sn_dma_sync_single_for_device); + +void sn_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, + int nelems, int direction) +{ + BUG_ON(dev->bus != &pci_bus_type); +} +EXPORT_SYMBOL(sn_dma_sync_sg_for_cpu); + +void sn_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, + int nelems, int direction) +{ + BUG_ON(dev->bus != &pci_bus_type); +} +EXPORT_SYMBOL(sn_dma_sync_sg_for_device); + +int sn_dma_mapping_error(dma_addr_t dma_addr) +{ + return 0; +} +EXPORT_SYMBOL(sn_dma_mapping_error); + +char *sn_pci_get_legacy_mem(struct pci_bus *bus) +{ + if (!SN_PCIBUS_BUSSOFT(bus)) + return ERR_PTR(-ENODEV); + + return (char *)(SN_PCIBUS_BUSSOFT(bus)->bs_legacy_mem | __IA64_UNCACHED_OFFSET); +} + +int sn_pci_legacy_read(struct pci_bus *bus, u16 port, u32 *val, u8 size) +{ + unsigned long addr; + int ret; + + if (!SN_PCIBUS_BUSSOFT(bus)) + return -ENODEV; + + addr = SN_PCIBUS_BUSSOFT(bus)->bs_legacy_io | __IA64_UNCACHED_OFFSET; + addr += port; + + ret = ia64_sn_probe_mem(addr, (long)size, (void *)val); + + if (ret == 2) + return -EINVAL; + + if (ret == 1) + *val = -1; + + return size; +} + +int sn_pci_legacy_write(struct pci_bus *bus, u16 port, u32 val, u8 size) +{ + int ret = size; + unsigned long paddr; + unsigned long *addr; + + if (!SN_PCIBUS_BUSSOFT(bus)) { + ret = -ENODEV; + goto out; + } + + /* Put the phys addr in uncached space */ + paddr = SN_PCIBUS_BUSSOFT(bus)->bs_legacy_io | __IA64_UNCACHED_OFFSET; + paddr += port; + addr = (unsigned long *)paddr; + + switch (size) { + case 1: + *(volatile u8 *)(addr) = (u8)(val); + break; + case 2: + *(volatile u16 *)(addr) = (u16)(val); + break; + case 4: + *(volatile u32 *)(addr) = (u32)(val); + break; + default: + ret = -EINVAL; + break; + } + out: + return ret; +} diff --git a/arch/ia64/sn/pci/pcibr/Makefile b/arch/ia64/sn/pci/pcibr/Makefile new file mode 100644 index 00000000000..1850c4a94c4 --- /dev/null +++ b/arch/ia64/sn/pci/pcibr/Makefile @@ -0,0 +1,11 @@ +# +# 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. +# +# Copyright (C) 2002-2004 Silicon Graphics, Inc. All Rights Reserved. +# +# Makefile for the sn2 io routines. + +obj-y += pcibr_dma.o pcibr_reg.o \ + pcibr_ate.o pcibr_provider.o diff --git a/arch/ia64/sn/pci/pcibr/pcibr_ate.c b/arch/ia64/sn/pci/pcibr/pcibr_ate.c new file mode 100644 index 00000000000..9d6854666f9 --- /dev/null +++ b/arch/ia64/sn/pci/pcibr/pcibr_ate.c @@ -0,0 +1,188 @@ +/* + * 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. + * + * Copyright (C) 2001-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/types.h> +#include <asm/sn/sn_sal.h> +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/pcibr_provider.h" + +int pcibr_invalidate_ate = 0; /* by default don't invalidate ATE on free */ + +/* + * mark_ate: Mark the ate as either free or inuse. + */ +static void mark_ate(struct ate_resource *ate_resource, int start, int number, + uint64_t value) +{ + + uint64_t *ate = ate_resource->ate; + int index; + int length = 0; + + for (index = start; length < number; index++, length++) + ate[index] = value; + +} + +/* + * find_free_ate: Find the first free ate index starting from the given + * index for the desired consequtive count. + */ +static int find_free_ate(struct ate_resource *ate_resource, int start, + int count) +{ + + uint64_t *ate = ate_resource->ate; + int index; + int start_free; + + for (index = start; index < ate_resource->num_ate;) { + if (!ate[index]) { + int i; + int free; + free = 0; + start_free = index; /* Found start free ate */ + for (i = start_free; i < ate_resource->num_ate; i++) { + if (!ate[i]) { /* This is free */ + if (++free == count) + return start_free; + } else { + index = i + 1; + break; + } + } + } else + index++; /* Try next ate */ + } + + return -1; +} + +/* + * free_ate_resource: Free the requested number of ATEs. + */ +static inline void free_ate_resource(struct ate_resource *ate_resource, + int start) +{ + + mark_ate(ate_resource, start, ate_resource->ate[start], 0); + if ((ate_resource->lowest_free_index > start) || + (ate_resource->lowest_free_index < 0)) + ate_resource->lowest_free_index = start; + +} + +/* + * alloc_ate_resource: Allocate the requested number of ATEs. + */ +static inline int alloc_ate_resource(struct ate_resource *ate_resource, + int ate_needed) +{ + + int start_index; + + /* + * Check for ate exhaustion. + */ + if (ate_resource->lowest_free_index < 0) + return -1; + + /* + * Find the required number of free consequtive ates. + */ + start_index = + find_free_ate(ate_resource, ate_resource->lowest_free_index, + ate_needed); + if (start_index >= 0) + mark_ate(ate_resource, start_index, ate_needed, ate_needed); + + ate_resource->lowest_free_index = + find_free_ate(ate_resource, ate_resource->lowest_free_index, 1); + + return start_index; +} + +/* + * Allocate "count" contiguous Bridge Address Translation Entries + * on the specified bridge to be used for PCI to XTALK mappings. + * Indices in rm map range from 1..num_entries. Indicies returned + * to caller range from 0..num_entries-1. + * + * Return the start index on success, -1 on failure. + */ +int pcibr_ate_alloc(struct pcibus_info *pcibus_info, int count) +{ + int status = 0; + uint64_t flag; + + flag = pcibr_lock(pcibus_info); + status = alloc_ate_resource(&pcibus_info->pbi_int_ate_resource, count); + + if (status < 0) { + /* Failed to allocate */ + pcibr_unlock(pcibus_info, flag); + return -1; + } + + pcibr_unlock(pcibus_info, flag); + + return status; +} + +/* + * Setup an Address Translation Entry as specified. Use either the Bridge + * internal maps or the external map RAM, as appropriate. + */ +static inline uint64_t *pcibr_ate_addr(struct pcibus_info *pcibus_info, + int ate_index) +{ + if (ate_index < pcibus_info->pbi_int_ate_size) { + return pcireg_int_ate_addr(pcibus_info, ate_index); + } + panic("pcibr_ate_addr: invalid ate_index 0x%x", ate_index); +} + +/* + * Update the ate. + */ +void inline +ate_write(struct pcibus_info *pcibus_info, int ate_index, int count, + volatile uint64_t ate) +{ + while (count-- > 0) { + if (ate_index < pcibus_info->pbi_int_ate_size) { + pcireg_int_ate_set(pcibus_info, ate_index, ate); + } else { + panic("ate_write: invalid ate_index 0x%x", ate_index); + } + ate_index++; + ate += IOPGSIZE; + } + + pcireg_tflush_get(pcibus_info); /* wait until Bridge PIO complete */ +} + +void pcibr_ate_free(struct pcibus_info *pcibus_info, int index) +{ + + volatile uint64_t ate; + int count; + uint64_t flags; + + if (pcibr_invalidate_ate) { + /* For debugging purposes, clear the valid bit in the ATE */ + ate = *pcibr_ate_addr(pcibus_info, index); + count = pcibus_info->pbi_int_ate_resource.ate[index]; + ate_write(pcibus_info, index, count, (ate & ~PCI32_ATE_V)); + } + + flags = pcibr_lock(pcibus_info); + free_ate_resource(&pcibus_info->pbi_int_ate_resource, index); + pcibr_unlock(pcibus_info, flags); +} diff --git a/arch/ia64/sn/pci/pcibr/pcibr_dma.c b/arch/ia64/sn/pci/pcibr/pcibr_dma.c new file mode 100644 index 00000000000..b1d66ac065c --- /dev/null +++ b/arch/ia64/sn/pci/pcibr/pcibr_dma.c @@ -0,0 +1,379 @@ +/* + * 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. + * + * Copyright (C) 2001-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/pci.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/geo.h> +#include "xtalk/xwidgetdev.h" +#include "xtalk/hubdev.h" +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/tiocp.h" +#include "pci/pic.h" +#include "pci/pcibr_provider.h" +#include "pci/tiocp.h" +#include "tio.h" +#include <asm/sn/addrs.h> + +extern int sn_ioif_inited; + +/* ===================================================================== + * DMA MANAGEMENT + * + * The Bridge ASIC provides three methods of doing DMA: via a "direct map" + * register available in 32-bit PCI space (which selects a contiguous 2G + * address space on some other widget), via "direct" addressing via 64-bit + * PCI space (all destination information comes from the PCI address, + * including transfer attributes), and via a "mapped" region that allows + * a bunch of different small mappings to be established with the PMU. + * + * For efficiency, we most prefer to use the 32bit direct mapping facility, + * since it requires no resource allocations. The advantage of using the + * PMU over the 64-bit direct is that single-cycle PCI addressing can be + * used; the advantage of using 64-bit direct over PMU addressing is that + * we do not have to allocate entries in the PMU. + */ + +static uint64_t +pcibr_dmamap_ate32(struct pcidev_info *info, + uint64_t paddr, size_t req_size, uint64_t flags) +{ + + struct pcidev_info *pcidev_info = info->pdi_host_pcidev_info; + struct pcibus_info *pcibus_info = (struct pcibus_info *)pcidev_info-> + pdi_pcibus_info; + uint8_t internal_device = (PCI_SLOT(pcidev_info->pdi_host_pcidev_info-> + pdi_linux_pcidev->devfn)) - 1; + int ate_count; + int ate_index; + uint64_t ate_flags = flags | PCI32_ATE_V; + uint64_t ate; + uint64_t pci_addr; + uint64_t xio_addr; + uint64_t offset; + + /* PIC in PCI-X mode does not supports 32bit PageMap mode */ + if (IS_PIC_SOFT(pcibus_info) && IS_PCIX(pcibus_info)) { + return 0; + } + + /* Calculate the number of ATEs needed. */ + if (!(MINIMAL_ATE_FLAG(paddr, req_size))) { + ate_count = IOPG((IOPGSIZE - 1) /* worst case start offset */ + +req_size /* max mapping bytes */ + - 1) + 1; /* round UP */ + } else { /* assume requested target is page aligned */ + ate_count = IOPG(req_size /* max mapping bytes */ + - 1) + 1; /* round UP */ + } + + /* Get the number of ATEs required. */ + ate_index = pcibr_ate_alloc(pcibus_info, ate_count); + if (ate_index < 0) + return 0; + + /* In PCI-X mode, Prefetch not supported */ + if (IS_PCIX(pcibus_info)) + ate_flags &= ~(PCI32_ATE_PREF); + + xio_addr = + IS_PIC_SOFT(pcibus_info) ? PHYS_TO_DMA(paddr) : + PHYS_TO_TIODMA(paddr); + offset = IOPGOFF(xio_addr); + ate = ate_flags | (xio_addr - offset); + + /* If PIC, put the targetid in the ATE */ + if (IS_PIC_SOFT(pcibus_info)) { + ate |= (pcibus_info->pbi_hub_xid << PIC_ATE_TARGETID_SHFT); + } + ate_write(pcibus_info, ate_index, ate_count, ate); + + /* + * Set up the DMA mapped Address. + */ + pci_addr = PCI32_MAPPED_BASE + offset + IOPGSIZE * ate_index; + + /* + * If swap was set in device in pcibr_endian_set() + * we need to turn swapping on. + */ + if (pcibus_info->pbi_devreg[internal_device] & PCIBR_DEV_SWAP_DIR) + ATE_SWAP_ON(pci_addr); + + return pci_addr; +} + +static uint64_t +pcibr_dmatrans_direct64(struct pcidev_info * info, uint64_t paddr, + uint64_t dma_attributes) +{ + struct pcibus_info *pcibus_info = (struct pcibus_info *) + ((info->pdi_host_pcidev_info)->pdi_pcibus_info); + uint64_t pci_addr; + + /* Translate to Crosstalk View of Physical Address */ + pci_addr = (IS_PIC_SOFT(pcibus_info) ? PHYS_TO_DMA(paddr) : + PHYS_TO_TIODMA(paddr)) | dma_attributes; + + /* Handle Bus mode */ + if (IS_PCIX(pcibus_info)) + pci_addr &= ~PCI64_ATTR_PREF; + + /* Handle Bridge Chipset differences */ + if (IS_PIC_SOFT(pcibus_info)) { + pci_addr |= + ((uint64_t) pcibus_info-> + pbi_hub_xid << PIC_PCI64_ATTR_TARG_SHFT); + } else + pci_addr |= TIOCP_PCI64_CMDTYPE_MEM; + + /* If PCI mode, func zero uses VCHAN0, every other func uses VCHAN1 */ + if (!IS_PCIX(pcibus_info) && PCI_FUNC(info->pdi_linux_pcidev->devfn)) + pci_addr |= PCI64_ATTR_VIRTUAL; + + return pci_addr; + +} + +static uint64_t +pcibr_dmatrans_direct32(struct pcidev_info * info, + uint64_t paddr, size_t req_size, uint64_t flags) +{ + + struct pcidev_info *pcidev_info = info->pdi_host_pcidev_info; + struct pcibus_info *pcibus_info = (struct pcibus_info *)pcidev_info-> + pdi_pcibus_info; + uint64_t xio_addr; + + uint64_t xio_base; + uint64_t offset; + uint64_t endoff; + + if (IS_PCIX(pcibus_info)) { + return 0; + } + + xio_addr = IS_PIC_SOFT(pcibus_info) ? PHYS_TO_DMA(paddr) : + PHYS_TO_TIODMA(paddr); + + xio_base = pcibus_info->pbi_dir_xbase; + offset = xio_addr - xio_base; + endoff = req_size + offset; + if ((req_size > (1ULL << 31)) || /* Too Big */ + (xio_addr < xio_base) || /* Out of range for mappings */ + (endoff > (1ULL << 31))) { /* Too Big */ + return 0; + } + + return PCI32_DIRECT_BASE | offset; + +} + +/* + * Wrapper routine for free'ing DMA maps + * DMA mappings for Direct 64 and 32 do not have any DMA maps. + */ +void +pcibr_dma_unmap(struct pcidev_info *pcidev_info, dma_addr_t dma_handle, + int direction) +{ + struct pcibus_info *pcibus_info = (struct pcibus_info *)pcidev_info-> + pdi_pcibus_info; + + if (IS_PCI32_MAPPED(dma_handle)) { + int ate_index; + + ate_index = + IOPG((ATE_SWAP_OFF(dma_handle) - PCI32_MAPPED_BASE)); + pcibr_ate_free(pcibus_info, ate_index); + } +} + +/* + * On SN systems there is a race condition between a PIO read response and + * DMA's. In rare cases, the read response may beat the DMA, causing the + * driver to think that data in memory is complete and meaningful. This code + * eliminates that race. This routine is called by the PIO read routines + * after doing the read. For PIC this routine then forces a fake interrupt + * on another line, which is logically associated with the slot that the PIO + * is addressed to. It then spins while watching the memory location that + * the interrupt is targetted to. When the interrupt response arrives, we + * are sure that the DMA has landed in memory and it is safe for the driver + * to proceed. For TIOCP use the Device(x) Write Request Buffer Flush + * Bridge register since it ensures the data has entered the coherence domain, + * unlike the PIC Device(x) Write Request Buffer Flush register. + */ + +void sn_dma_flush(uint64_t addr) +{ + nasid_t nasid; + int is_tio; + int wid_num; + int i, j; + int bwin; + uint64_t flags; + struct hubdev_info *hubinfo; + volatile struct sn_flush_device_list *p; + struct sn_flush_nasid_entry *flush_nasid_list; + + if (!sn_ioif_inited) + return; + + nasid = NASID_GET(addr); + if (-1 == nasid_to_cnodeid(nasid)) + return; + + hubinfo = (NODEPDA(nasid_to_cnodeid(nasid)))->pdinfo; + + if (!hubinfo) { + BUG(); + } + is_tio = (nasid & 1); + if (is_tio) { + wid_num = TIO_SWIN_WIDGETNUM(addr); + bwin = TIO_BWIN_WINDOWNUM(addr); + } else { + wid_num = SWIN_WIDGETNUM(addr); + bwin = BWIN_WINDOWNUM(addr); + } + + flush_nasid_list = &hubinfo->hdi_flush_nasid_list; + if (flush_nasid_list->widget_p == NULL) + return; + if (bwin > 0) { + uint64_t itte = flush_nasid_list->iio_itte[bwin]; + + if (is_tio) { + wid_num = (itte >> TIO_ITTE_WIDGET_SHIFT) & + TIO_ITTE_WIDGET_MASK; + } else { + wid_num = (itte >> IIO_ITTE_WIDGET_SHIFT) & + IIO_ITTE_WIDGET_MASK; + } + } + if (flush_nasid_list->widget_p == NULL) + return; + if (flush_nasid_list->widget_p[wid_num] == NULL) + return; + p = &flush_nasid_list->widget_p[wid_num][0]; + + /* find a matching BAR */ + for (i = 0; i < DEV_PER_WIDGET; i++) { + for (j = 0; j < PCI_ROM_RESOURCE; j++) { + if (p->sfdl_bar_list[j].start == 0) + break; + if (addr >= p->sfdl_bar_list[j].start + && addr <= p->sfdl_bar_list[j].end) + break; + } + if (j < PCI_ROM_RESOURCE && p->sfdl_bar_list[j].start != 0) + break; + p++; + } + + /* if no matching BAR, return without doing anything. */ + if (i == DEV_PER_WIDGET) + return; + + /* + * For TIOCP use the Device(x) Write Request Buffer Flush Bridge + * register since it ensures the data has entered the coherence + * domain, unlike PIC + */ + if (is_tio) { + uint32_t tio_id = REMOTE_HUB_L(nasid, TIO_NODE_ID); + uint32_t revnum = XWIDGET_PART_REV_NUM(tio_id); + + /* TIOCP BRINGUP WAR (PV907516): Don't write buffer flush reg */ + if ((1 << XWIDGET_PART_REV_NUM_REV(revnum)) & PV907516) { + return; + } else { + pcireg_wrb_flush_get(p->sfdl_pcibus_info, + (p->sfdl_slot - 1)); + } + } else { + spin_lock_irqsave(&((struct sn_flush_device_list *)p)-> + sfdl_flush_lock, flags); + + p->sfdl_flush_value = 0; + + /* force an interrupt. */ + *(volatile uint32_t *)(p->sfdl_force_int_addr) = 1; + + /* wait for the interrupt to come back. */ + while (*(p->sfdl_flush_addr) != 0x10f) ; + + /* okay, everything is synched up. */ + spin_unlock_irqrestore((spinlock_t *)&p->sfdl_flush_lock, flags); + } + return; +} + +/* + * Wrapper DMA interface. Called from pci_dma.c routines. + */ + +uint64_t +pcibr_dma_map(struct pcidev_info * pcidev_info, unsigned long phys_addr, + size_t size, unsigned int flags) +{ + dma_addr_t dma_handle; + struct pci_dev *pcidev = pcidev_info->pdi_linux_pcidev; + + if (flags & SN_PCIDMA_CONSISTENT) { + /* sn_pci_alloc_consistent interfaces */ + if (pcidev->dev.coherent_dma_mask == ~0UL) { + dma_handle = + pcibr_dmatrans_direct64(pcidev_info, phys_addr, + PCI64_ATTR_BAR); + } else { + dma_handle = + (dma_addr_t) pcibr_dmamap_ate32(pcidev_info, + phys_addr, size, + PCI32_ATE_BAR); + } + } else { + /* map_sg/map_single interfaces */ + + /* SN cannot support DMA addresses smaller than 32 bits. */ + if (pcidev->dma_mask < 0x7fffffff) { + return 0; + } + + if (pcidev->dma_mask == ~0UL) { + /* + * Handle the most common case: 64 bit cards. This + * call should always succeed. + */ + + dma_handle = + pcibr_dmatrans_direct64(pcidev_info, phys_addr, + PCI64_ATTR_PREF); + } else { + /* Handle 32-63 bit cards via direct mapping */ + dma_handle = + pcibr_dmatrans_direct32(pcidev_info, phys_addr, + size, 0); + if (!dma_handle) { + /* + * It is a 32 bit card and we cannot do direct mapping, + * so we use an ATE. + */ + + dma_handle = + pcibr_dmamap_ate32(pcidev_info, phys_addr, + size, PCI32_ATE_PREF); + } + } + } + + return dma_handle; +} + +EXPORT_SYMBOL(sn_dma_flush); diff --git a/arch/ia64/sn/pci/pcibr/pcibr_provider.c b/arch/ia64/sn/pci/pcibr/pcibr_provider.c new file mode 100644 index 00000000000..92bd278cf7f --- /dev/null +++ b/arch/ia64/sn/pci/pcibr/pcibr_provider.c @@ -0,0 +1,170 @@ +/* + * 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. + * + * Copyright (C) 2001-2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <asm/sn/sn_sal.h> +#include "xtalk/xwidgetdev.h" +#include <asm/sn/geo.h> +#include "xtalk/hubdev.h" +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/pcibr_provider.h" +#include <asm/sn/addrs.h> + + +static int sal_pcibr_error_interrupt(struct pcibus_info *soft) +{ + struct ia64_sal_retval ret_stuff; + uint64_t busnum; + int segment; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + segment = 0; + busnum = soft->pbi_buscommon.bs_persist_busnum; + SAL_CALL_NOLOCK(ret_stuff, + (u64) SN_SAL_IOIF_ERROR_INTERRUPT, + (u64) segment, (u64) busnum, 0, 0, 0, 0, 0); + + return (int)ret_stuff.v0; +} + +/* + * PCI Bridge Error interrupt handler. Gets invoked whenever a PCI + * bridge sends an error interrupt. + */ +static irqreturn_t +pcibr_error_intr_handler(int irq, void *arg, struct pt_regs *regs) +{ + struct pcibus_info *soft = (struct pcibus_info *)arg; + + if (sal_pcibr_error_interrupt(soft) < 0) { + panic("pcibr_error_intr_handler(): Fatal Bridge Error"); + } + return IRQ_HANDLED; +} + +void * +pcibr_bus_fixup(struct pcibus_bussoft *prom_bussoft) +{ + int nasid, cnode, j; + struct hubdev_info *hubdev_info; + struct pcibus_info *soft; + struct sn_flush_device_list *sn_flush_device_list; + + if (! IS_PCI_BRIDGE_ASIC(prom_bussoft->bs_asic_type)) { + return NULL; + } + + /* + * Allocate kernel bus soft and copy from prom. + */ + + soft = kmalloc(sizeof(struct pcibus_info), GFP_KERNEL); + if (!soft) { + return NULL; + } + + memcpy(soft, prom_bussoft, sizeof(struct pcibus_info)); + soft->pbi_buscommon.bs_base = + (((u64) soft->pbi_buscommon. + bs_base << 4) >> 4) | __IA64_UNCACHED_OFFSET; + + spin_lock_init(&soft->pbi_lock); + + /* + * register the bridge's error interrupt handler + */ + if (request_irq(SGI_PCIBR_ERROR, (void *)pcibr_error_intr_handler, + SA_SHIRQ, "PCIBR error", (void *)(soft))) { + printk(KERN_WARNING + "pcibr cannot allocate interrupt for error handler\n"); + } + + /* + * Update the Bridge with the "kernel" pagesize + */ + if (PAGE_SIZE < 16384) { + pcireg_control_bit_clr(soft, PCIBR_CTRL_PAGE_SIZE); + } else { + pcireg_control_bit_set(soft, PCIBR_CTRL_PAGE_SIZE); + } + + nasid = NASID_GET(soft->pbi_buscommon.bs_base); + cnode = nasid_to_cnodeid(nasid); + hubdev_info = (struct hubdev_info *)(NODEPDA(cnode)->pdinfo); + + if (hubdev_info->hdi_flush_nasid_list.widget_p) { + sn_flush_device_list = hubdev_info->hdi_flush_nasid_list. + widget_p[(int)soft->pbi_buscommon.bs_xid]; + if (sn_flush_device_list) { + for (j = 0; j < DEV_PER_WIDGET; + j++, sn_flush_device_list++) { + if (sn_flush_device_list->sfdl_slot == -1) + continue; + if (sn_flush_device_list-> + sfdl_persistent_busnum == + soft->pbi_buscommon.bs_persist_busnum) + sn_flush_device_list->sfdl_pcibus_info = + soft; + } + } + } + + /* Setup the PMU ATE map */ + soft->pbi_int_ate_resource.lowest_free_index = 0; + soft->pbi_int_ate_resource.ate = + kmalloc(soft->pbi_int_ate_size * sizeof(uint64_t), GFP_KERNEL); + memset(soft->pbi_int_ate_resource.ate, 0, + (soft->pbi_int_ate_size * sizeof(uint64_t))); + + return soft; +} + +void pcibr_force_interrupt(struct sn_irq_info *sn_irq_info) +{ + struct pcidev_info *pcidev_info; + struct pcibus_info *pcibus_info; + int bit = sn_irq_info->irq_int_bit; + + pcidev_info = (struct pcidev_info *)sn_irq_info->irq_pciioinfo; + if (pcidev_info) { + pcibus_info = + (struct pcibus_info *)pcidev_info->pdi_host_pcidev_info-> + pdi_pcibus_info; + pcireg_force_intr_set(pcibus_info, bit); + } +} + +void pcibr_change_devices_irq(struct sn_irq_info *sn_irq_info) +{ + struct pcidev_info *pcidev_info; + struct pcibus_info *pcibus_info; + int bit = sn_irq_info->irq_int_bit; + uint64_t xtalk_addr = sn_irq_info->irq_xtalkaddr; + + pcidev_info = (struct pcidev_info *)sn_irq_info->irq_pciioinfo; + if (pcidev_info) { + pcibus_info = + (struct pcibus_info *)pcidev_info->pdi_host_pcidev_info-> + pdi_pcibus_info; + + /* Disable the device's IRQ */ + pcireg_intr_enable_bit_clr(pcibus_info, bit); + + /* Change the device's IRQ */ + pcireg_intr_addr_addr_set(pcibus_info, bit, xtalk_addr); + + /* Re-enable the device's IRQ */ + pcireg_intr_enable_bit_set(pcibus_info, bit); + + pcibr_force_interrupt(sn_irq_info); + } +} diff --git a/arch/ia64/sn/pci/pcibr/pcibr_reg.c b/arch/ia64/sn/pci/pcibr/pcibr_reg.c new file mode 100644 index 00000000000..74a74a7d2a1 --- /dev/null +++ b/arch/ia64/sn/pci/pcibr/pcibr_reg.c @@ -0,0 +1,282 @@ +/* + * 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. + * + * Copyright (C) 2004 Silicon Graphics, Inc. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/interrupt.h> +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/tiocp.h" +#include "pci/pic.h" +#include "pci/pcibr_provider.h" + +union br_ptr { + struct tiocp tio; + struct pic pic; +}; + +/* + * Control Register Access -- Read/Write 0000_0020 + */ +void pcireg_control_bit_clr(struct pcibus_info *pcibus_info, uint64_t bits) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_control &= ~bits; + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_wid_control &= ~bits; + break; + default: + panic + ("pcireg_control_bit_clr: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +void pcireg_control_bit_set(struct pcibus_info *pcibus_info, uint64_t bits) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_control |= bits; + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_wid_control |= bits; + break; + default: + panic + ("pcireg_control_bit_set: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +/* + * PCI/PCIX Target Flush Register Access -- Read Only 0000_0050 + */ +uint64_t pcireg_tflush_get(struct pcibus_info *pcibus_info) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + uint64_t ret = 0; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ret = ptr->tio.cp_tflush; + break; + case PCIBR_BRIDGETYPE_PIC: + ret = ptr->pic.p_wid_tflush; + break; + default: + panic + ("pcireg_tflush_get: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } + + /* Read of the Target Flush should always return zero */ + if (ret != 0) + panic("pcireg_tflush_get:Target Flush failed\n"); + + return ret; +} + +/* + * Interrupt Status Register Access -- Read Only 0000_0100 + */ +uint64_t pcireg_intr_status_get(struct pcibus_info * pcibus_info) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + uint64_t ret = 0; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ret = ptr->tio.cp_int_status; + break; + case PCIBR_BRIDGETYPE_PIC: + ret = ptr->pic.p_int_status; + break; + default: + panic + ("pcireg_intr_status_get: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } + return ret; +} + +/* + * Interrupt Enable Register Access -- Read/Write 0000_0108 + */ +void pcireg_intr_enable_bit_clr(struct pcibus_info *pcibus_info, uint64_t bits) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_int_enable &= ~bits; + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_int_enable &= ~bits; + break; + default: + panic + ("pcireg_intr_enable_bit_clr: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +void pcireg_intr_enable_bit_set(struct pcibus_info *pcibus_info, uint64_t bits) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_int_enable |= bits; + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_int_enable |= bits; + break; + default: + panic + ("pcireg_intr_enable_bit_set: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +/* + * Intr Host Address Register (int_addr) -- Read/Write 0000_0130 - 0000_0168 + */ +void pcireg_intr_addr_addr_set(struct pcibus_info *pcibus_info, int int_n, + uint64_t addr) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_int_addr[int_n] &= ~TIOCP_HOST_INTR_ADDR; + ptr->tio.cp_int_addr[int_n] |= + (addr & TIOCP_HOST_INTR_ADDR); + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_int_addr[int_n] &= ~PIC_HOST_INTR_ADDR; + ptr->pic.p_int_addr[int_n] |= + (addr & PIC_HOST_INTR_ADDR); + break; + default: + panic + ("pcireg_intr_addr_addr_get: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +/* + * Force Interrupt Register Access -- Write Only 0000_01C0 - 0000_01F8 + */ +void pcireg_force_intr_set(struct pcibus_info *pcibus_info, int int_n) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_force_pin[int_n] = 1; + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_force_pin[int_n] = 1; + break; + default: + panic + ("pcireg_force_intr_set: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +/* + * Device(x) Write Buffer Flush Reg Access -- Read Only 0000_0240 - 0000_0258 + */ +uint64_t pcireg_wrb_flush_get(struct pcibus_info *pcibus_info, int device) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + uint64_t ret = 0; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ret = ptr->tio.cp_wr_req_buf[device]; + break; + case PCIBR_BRIDGETYPE_PIC: + ret = ptr->pic.p_wr_req_buf[device]; + break; + default: + panic("pcireg_wrb_flush_get: unknown bridgetype bridge 0x%p", (void *)ptr); + } + + } + /* Read of the Write Buffer Flush should always return zero */ + return ret; +} + +void pcireg_int_ate_set(struct pcibus_info *pcibus_info, int ate_index, + uint64_t val) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ptr->tio.cp_int_ate_ram[ate_index] = (uint64_t) val; + break; + case PCIBR_BRIDGETYPE_PIC: + ptr->pic.p_int_ate_ram[ate_index] = (uint64_t) val; + break; + default: + panic + ("pcireg_int_ate_set: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } +} + +uint64_t *pcireg_int_ate_addr(struct pcibus_info *pcibus_info, int ate_index) +{ + union br_ptr *ptr = (union br_ptr *)pcibus_info->pbi_buscommon.bs_base; + uint64_t *ret = (uint64_t *) 0; + + if (pcibus_info) { + switch (pcibus_info->pbi_bridge_type) { + case PCIBR_BRIDGETYPE_TIOCP: + ret = + (uint64_t *) & (ptr->tio.cp_int_ate_ram[ate_index]); + break; + case PCIBR_BRIDGETYPE_PIC: + ret = + (uint64_t *) & (ptr->pic.p_int_ate_ram[ate_index]); + break; + default: + panic + ("pcireg_int_ate_addr: unknown bridgetype bridge 0x%p", + (void *)ptr); + } + } + return ret; +} |