/* $Id: ml_SN_intr.c,v 1.1 2002/02/28 17:31:25 marcelo Exp $ * * 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 Silicon Graphics, Inc. All Rights Reserved. */ /* * intr.c- * This file contains all of the routines necessary to set up and * handle interrupts on an IPXX board. */ #ident "$Revision: 1.1 $" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern irqpda_t *irqpdaindr[]; extern cnodeid_t master_node_get(devfs_handle_t vhdl); extern nasid_t master_nasid; // Initialize some shub registers for interrupts, both IO and error. void intr_init_vecblk( nodepda_t *npda, cnodeid_t node, int sn) { int nasid = cnodeid_to_nasid(node); nasid_t console_nasid; sh_ii_int0_config_u_t ii_int_config; cpuid_t cpu; cpuid_t cpu0, cpu1; nodepda_t *lnodepda; sh_ii_int0_enable_u_t ii_int_enable; sh_local_int0_config_u_t local_int_config; sh_local_int0_enable_u_t local_int_enable; sh_fsb_system_agent_config_u_t fsb_system_agent; sh_int_node_id_config_u_t node_id_config; int is_console; console_nasid = get_console_nasid(); if (console_nasid < 0) { console_nasid = master_nasid; } is_console = nasid == console_nasid; if (is_headless_node(node) ) { int cnode; struct ia64_sal_retval ret_stuff; // retarget all interrupts on this node to the master node. node_id_config.sh_int_node_id_config_regval = 0; node_id_config.sh_int_node_id_config_s.node_id = master_nasid; node_id_config.sh_int_node_id_config_s.id_sel = 1; HUB_S( (unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_INT_NODE_ID_CONFIG), node_id_config.sh_int_node_id_config_regval); cnode = nasid_to_cnodeid(master_nasid); lnodepda = NODEPDA(cnode); cpu = lnodepda->node_first_cpu; cpu = cpu_physical_id(cpu); SAL_CALL(ret_stuff, SN_SAL_REGISTER_CE, nasid, cpu, master_nasid,0,0,0,0); if (ret_stuff.status < 0) { printk("%s: SN_SAL_REGISTER_CE SAL_CALL failed\n",__FUNCTION__); } } else { lnodepda = NODEPDA(node); cpu = lnodepda->node_first_cpu; cpu = cpu_physical_id(cpu); } // Get the physical id's of the cpu's on this node. cpu0 = id_eid_to_cpu_physical_id(nasid, 0); cpu1 = id_eid_to_cpu_physical_id(nasid, 1); HUB_S( (unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_PI_ERROR_MASK), 0); HUB_S( (unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_PI_CRBP_ERROR_MASK), 0); // The II_INT_CONFIG register for cpu 0. ii_int_config.sh_ii_int0_config_s.type = 0; ii_int_config.sh_ii_int0_config_s.agt = 0; ii_int_config.sh_ii_int0_config_s.pid = cpu0; ii_int_config.sh_ii_int0_config_s.base = 0; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_II_INT0_CONFIG), ii_int_config.sh_ii_int0_config_regval); // The II_INT_CONFIG register for cpu 1. ii_int_config.sh_ii_int0_config_s.type = 0; ii_int_config.sh_ii_int0_config_s.agt = 0; ii_int_config.sh_ii_int0_config_s.pid = cpu1; ii_int_config.sh_ii_int0_config_s.base = 0; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_II_INT1_CONFIG), ii_int_config.sh_ii_int0_config_regval); // Enable interrupts for II_INT0 and 1. ii_int_enable.sh_ii_int0_enable_s.ii_enable = 1; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_II_INT0_ENABLE), ii_int_enable.sh_ii_int0_enable_regval); HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_II_INT1_ENABLE), ii_int_enable.sh_ii_int0_enable_regval); // init error regs // LOCAL_INT0 is for the UART only. local_int_config.sh_local_int0_config_s.type = 0; local_int_config.sh_local_int0_config_s.agt = 0; local_int_config.sh_local_int0_config_s.pid = cpu; local_int_config.sh_local_int0_config_s.base = 0; local_int_config.sh_local_int0_config_s.idx = SGI_UART_VECTOR; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT0_CONFIG), local_int_config.sh_local_int0_config_regval); // LOCAL_INT1 is for all hardware errors. // It will send a BERR, which will result in an MCA. local_int_config.sh_local_int0_config_s.idx = 0; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT1_CONFIG), local_int_config.sh_local_int0_config_regval); // Clear the LOCAL_INT_ENABLE register. local_int_enable.sh_local_int0_enable_regval = 0; if (is_console) { // Enable the UART interrupt. Only applies to the console nasid. local_int_enable.sh_local_int0_enable_s.uart_int = 1; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT0_ENABLE), local_int_enable.sh_local_int0_enable_regval); } // Enable all the error interrupts. local_int_enable.sh_local_int0_enable_s.uart_int = 0; local_int_enable.sh_local_int0_enable_s.pi_hw_int = 1; local_int_enable.sh_local_int0_enable_s.md_hw_int = 1; local_int_enable.sh_local_int0_enable_s.xn_hw_int = 1; local_int_enable.sh_local_int0_enable_s.lb_hw_int = 1; local_int_enable.sh_local_int0_enable_s.ii_hw_int = 1; local_int_enable.sh_local_int0_enable_s.pi_uce_int = 1; local_int_enable.sh_local_int0_enable_s.md_uce_int = 1; local_int_enable.sh_local_int0_enable_s.xn_uce_int = 1; local_int_enable.sh_local_int0_enable_s.system_shutdown_int = 1; local_int_enable.sh_local_int0_enable_s.l1_nmi_int = 1; local_int_enable.sh_local_int0_enable_s.stop_clock = 1; // Send BERR, rather than an interrupt, for shub errors. local_int_config.sh_local_int0_config_s.agt = 1; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT1_CONFIG), local_int_config.sh_local_int0_config_regval); HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT1_ENABLE), local_int_enable.sh_local_int0_enable_regval); // Make sure BERR is enabled. fsb_system_agent.sh_fsb_system_agent_config_regval = HUB_L( (unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_FSB_SYSTEM_AGENT_CONFIG) ); fsb_system_agent.sh_fsb_system_agent_config_s.berr_assert_en = 1; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_FSB_SYSTEM_AGENT_CONFIG), fsb_system_agent.sh_fsb_system_agent_config_regval); // Set LOCAL_INT2 to field CEs local_int_enable.sh_local_int0_enable_regval = 0; local_int_config.sh_local_int0_config_s.agt = 0; local_int_config.sh_local_int0_config_s.idx = SGI_SHUB_ERROR_VECTOR; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT2_CONFIG), local_int_config.sh_local_int0_config_regval); local_int_enable.sh_local_int0_enable_s.pi_ce_int = 1; local_int_enable.sh_local_int0_enable_s.md_ce_int = 1; local_int_enable.sh_local_int0_enable_s.xn_ce_int = 1; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT2_ENABLE), local_int_enable.sh_local_int0_enable_regval); // Make sure all the rest of the LOCAL_INT regs are disabled. local_int_enable.sh_local_int0_enable_regval = 0; HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT3_ENABLE), local_int_enable.sh_local_int0_enable_regval); HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT4_ENABLE), local_int_enable.sh_local_int0_enable_regval); HUB_S((unsigned long *)GLOBAL_MMR_ADDR(nasid, SH_LOCAL_INT5_ENABLE), local_int_enable.sh_local_int0_enable_regval); } // (Un)Reserve an irq on this cpu. static int do_intr_reserve_level(cpuid_t cpu, int bit, int reserve) { int i; irqpda_t *irqs = irqpdaindr[cpu]; if (reserve) { if (bit < 0) { for (i = IA64_SN2_FIRST_DEVICE_VECTOR; i <= IA64_SN2_LAST_DEVICE_VECTOR; i++) { if (irqs->irq_flags[i] == 0) { bit = i; break; } } } if (bit < 0) { return -1; } if (irqs->irq_flags[bit] & SN2_IRQ_RESERVED) { return -1; } else { irqs->num_irq_used++; irqs->irq_flags[bit] |= SN2_IRQ_RESERVED; return bit; } } else { if (irqs->irq_flags[bit] & SN2_IRQ_RESERVED) { irqs->num_irq_used--; irqs->irq_flags[bit] &= ~SN2_IRQ_RESERVED; return bit; } else { return -1; } } } int intr_reserve_level(cpuid_t cpu, int bit, int resflags, devfs_handle_t owner_dev, char *name) { return(do_intr_reserve_level(cpu, bit, 1)); } void intr_unreserve_level(cpuid_t cpu, int bit) { (void)do_intr_reserve_level(cpu, bit, 0); } // Mark an irq on this cpu as (dis)connected. static int do_intr_connect_level(cpuid_t cpu, int bit, int connect) { irqpda_t *irqs = irqpdaindr[cpu]; if (connect) { if (irqs->irq_flags[bit] & SN2_IRQ_CONNECTED) { return -1; } else { irqs->irq_flags[bit] |= SN2_IRQ_CONNECTED; return bit; } } else { if (irqs->irq_flags[bit] & SN2_IRQ_CONNECTED) { irqs->irq_flags[bit] &= ~SN2_IRQ_CONNECTED; return bit; } else { return -1; } } return(bit); } int intr_connect_level(cpuid_t cpu, int bit, ilvl_t is, intr_func_t intr_prefunc) { return(do_intr_connect_level(cpu, bit, 1)); } int intr_disconnect_level(cpuid_t cpu, int bit) { return(do_intr_connect_level(cpu, bit, 0)); } // Choose a cpu on this node. // We choose the one with the least number of int's assigned to it. static cpuid_t do_intr_cpu_choose(cnodeid_t cnode) { cpuid_t cpu, best_cpu = CPU_NONE; int slice, min_count = 1000; irqpda_t *irqs; for (slice = 0; slice < CPUS_PER_NODE; slice++) { int intrs; cpu = cnode_slice_to_cpuid(cnode, slice); if (cpu == CPU_NONE) { continue; } if (!cpu_enabled(cpu)) { continue; } irqs = irqpdaindr[cpu]; intrs = irqs->num_irq_used; if (min_count > intrs) { min_count = intrs; best_cpu = cpu; } } return best_cpu; } static cpuid_t intr_cpu_choose_from_node(cnodeid_t cnode) { return(do_intr_cpu_choose(cnode)); } // See if we can use this cpu/vect. static cpuid_t intr_bit_reserve_test(cpuid_t cpu, int favor_subnode, cnodeid_t cnode, int req_bit, int resflags, devfs_handle_t owner_dev, char *name, int *resp_bit) { ASSERT( (cpu == CPU_NONE) || (cnode == CNODEID_NONE) ); if (cnode != CNODEID_NONE) { cpu = intr_cpu_choose_from_node(cnode); } if (cpu != CPU_NONE) { *resp_bit = do_intr_reserve_level(cpu, req_bit, 1); if (*resp_bit >= 0) { return(cpu); } } return CPU_NONE; } // Find the node to assign for this interrupt. cpuid_t intr_heuristic(devfs_handle_t dev, device_desc_t dev_desc, int req_bit, int resflags, devfs_handle_t owner_dev, char *name, int *resp_bit) { cpuid_t cpuid; cnodeid_t candidate = -1; devfs_handle_t pconn_vhdl; pcibr_soft_t pcibr_soft; /* SN2 + pcibr addressing limitation */ /* Due to this limitation, all interrupts from a given bridge must go to the name node.*/ /* This limitation does not exist on PIC. */ if ( (hwgraph_edge_get(dev, EDGE_LBL_PCI, &pconn_vhdl) == GRAPH_SUCCESS) && ( (pcibr_soft = pcibr_soft_get(pconn_vhdl) ) != NULL) ) { if (pcibr_soft->bsi_err_intr) { candidate = cpuid_to_cnodeid( ((hub_intr_t)pcibr_soft->bsi_err_intr)->i_cpuid); } } if (candidate >= 0) { // The node was chosen already when we assigned the error interrupt. cpuid = intr_bit_reserve_test(CPU_NONE, 0, candidate, req_bit, 0, owner_dev, name, resp_bit); } else { // Need to choose one. Try the controlling c-brick first. cpuid = intr_bit_reserve_test(CPU_NONE, 0, master_node_get(dev), req_bit, 0, owner_dev, name, resp_bit); } if (cpuid != CPU_NONE) { return cpuid; } if (candidate >= 0) { printk("Cannot target interrupt to target node (%d).\n",candidate); return CPU_NONE; } else { printk("Cannot target interrupt to closest node (%d) 0x%p\n", master_node_get(dev), (void *)owner_dev); } // We couldn't put it on the closest node. Try to find another one. // Do a stupid round-robin assignment of the node. { static cnodeid_t last_node = -1; if (last_node >= numnodes) last_node = 0; for (candidate = last_node + 1; candidate != last_node; candidate++) { if (candidate == numnodes) candidate = 0; cpuid = intr_bit_reserve_test(CPU_NONE, 0, candidate, req_bit, 0, owner_dev, name, resp_bit); if (cpuid != CPU_NONE) { return cpuid; } } } printk("cannot target interrupt: 0x%p\n",(void *)owner_dev); return CPU_NONE; }