/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include kmutex_t idn_xf_mutex; /* to serialize hardware access */ /* * This data structure is referenced during the cross-call * update of the CICs. The semaphore is used for synchronization * when waiting for completion of the respective operation. * We want IDNCIC_TIMEOUT ticks for all the cpus to check-in * before we bail out and fail the operation. */ #define IDNCIC_TIMEOUT (30*hz) #define IDNCIC_TIMECHK (hz/3) #define IDNCIC_UNKNOWN 0 #define IDNCIC_OK 1 /* values for xf_errcic */ #define IDNCIC_ERR 2 #define IDNCIC_BUSY 3 #ifdef DEBUG #define NCICREGS 3 /* smmask, smbar, smlar */ #define CICREG_SMMASK 0 #define CICREG_SMBAR 1 #define CICREG_SMLAR 2 #define RESET_CIC_HISTORY() \ (xf_cicboards = xf_cicbuses = 0, \ bzero(xf_cicregs, sizeof (xf_cicregs))) #define UPDATE_CIC_HISTORY(reg, brd, bus, val) \ (BOARDSET_ADD(xf_cicboards, (brd)), \ BOARDSET_ADD(xf_cicbuses, (bus)), \ xf_cicregs[brd][bus][reg] = (val)) #define DUMP_CIC_HISTORY() \ { \ if (idn_debug & IDNDBG_XF) { \ int _bd, _bs; \ procname_t _proc = "dump_cic_history"; \ for (_bd = 0; _bd < MAX_BOARDS; _bd++) { \ if (!BOARD_IN_SET(xf_cicboards, _bd)) \ continue; \ for (_bs = 0; _bs < MAX_ABUSES; _bs++) { \ if (!BOARD_IN_SET(xf_cicbuses, _bs)) \ continue; \ printf("%s: (bd.bs = %d.%d) m/b/l = " \ "%x/%x/%x\n", _proc, _bd, _bs, \ xf_cicregs[_bd][_bs][CICREG_SMMASK], \ xf_cicregs[_bd][_bs][CICREG_SMBAR], \ xf_cicregs[_bd][_bs][CICREG_SMLAR]); \ } \ } \ DEBUG_DELAY(); \ } \ } /* * Globally updated during CIC reg updates. Everybody has * a unique location, so no concern about updates stepping * on each other. */ static ushort_t xf_cicboards, xf_cicbuses; static uint_t xf_cicregs[MAX_BOARDS][MAX_ABUSES][NCICREGS]; #else /* DEBUG */ #define RESET_CIC_HISTORY() #define UPDATE_CIC_HISTORY(reg, brd, bus, val) #define DUMP_CIC_HISTORY() #endif /* DEBUG */ struct idnxf_cic_info { /* protected by idn_xf_mutex */ /* 0 */ short xf_abus_mask; /* 2 */ boardset_t xf_boardset; /* 4 */ uint_t xf_smbase; /* 8 */ uint_t xf_smlimit; /* c */ int xf_doadd; /* 10 */ int xf_count; /* atomically updated */ /* 14 */ time_t xf_start_time; /* 18 */ kcondvar_t xf_cv; /* 1a */ short xf_errtimer; /* 1c */ int xf_errcnt; /* atomically updated */ /* 20 */ uchar_t xf_errcic[MAX_BOARDS][MAX_ABUSES]; /* 60 */ kmutex_t xf_mutex; }; /* sizeof = 0x68 = 104 (26X) */ static struct idnxf_cic_info idnxf_cic_info; #ifdef DEBUG static uint_t o_idn_debug; #endif /* DEBUG */ int idn_check_cpu_per_board = 1; static int pc_prep_cic_buffer(int cpuid, uint_t cicdata); static int cic_write_sm_mask(int board, int bus, boardset_t sm_mask); static int cic_write_sm_bar(int board, int bus, uint_t sm_bar); static int cic_write_sm_lar(int board, int bus, uint_t sm_lar); static int cic_get_smmask_bit(void); static int pc_write_madr(pda_handle_t ph, int lboard, int rboard, uint_t madr); static boardset_t get_boardset(pda_handle_t ph, int *nboards); static int verify_smregs(int brd, int bus, boardset_t smmask, uint_t smbase, uint_t smlimit); static void idnxf_shmem_wakeup(void *arg); static void idnxf_shmem_update_one(uint64_t arg1, uint64_t arg2); static int idnxf_shmem_update_all(pda_handle_t ph, boardset_t boardset, uint_t smbase, uint_t smlimit, int doadd); #define PHYSIO_ST(paddr, val) (stphysio((paddr), (val))) #define PHYSIO_LD(paddr) (ldphysio(paddr)) #define PHYSIO_STH(paddr, val) (sthphysio((paddr), (val))) #define PHYSIO_LDH(paddr) (ldhphysio(paddr)) #ifdef DEBUG #define DEBUG_DELAY() (drv_usecwait(5000)) /* 5 ms */ #else /* DEBUG */ #define DEBUG_DELAY() #endif /* DEBUG */ /* * --------------------------------------------------------------------- */ boardset_t cic_read_domain_mask(int board, int bus) { u_longlong_t csr_addr; boardset_t domain_mask; procname_t proc = "cic_read_domain_mask"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_DOMAIN_MASK_ADDR, bus); PR_XF("%s: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); domain_mask = (boardset_t)PHYSIO_LDH(csr_addr); return (domain_mask); } boardset_t cic_read_sm_mask(int board, int bus) { u_longlong_t csr_addr; boardset_t sm_mask; procname_t proc = "cic_read_sm_mask"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_MASK_ADDR, bus); PR_XF("%s: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); sm_mask = (boardset_t)PHYSIO_LDH(csr_addr); return (sm_mask); } static int cic_write_sm_mask(int board, int bus, boardset_t sm_mask) { u_longlong_t csr_addr; int cnt; procname_t proc = "cic_write_sm_mask"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); sm_mask &= 0xffff; /* * Before we can write to the CIC, we need to set * up the CIC write data buffer in the PC. */ if (pc_prep_cic_buffer(CPU->cpu_id, (uint_t)sm_mask) < 0) return (-1); /* * Now we can write to the CIC. */ csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_MASK_ADDR, bus); PR_XF("%s: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); PR_XF("%s: writing sm_mask = 0x%x\n", proc, (ushort_t)sm_mask); UPDATE_CIC_HISTORY(CICREG_SMMASK, board, bus, sm_mask); PHYSIO_STH(csr_addr, (ushort_t)sm_mask); /* * Read back for verification. */ for (cnt = 0; (PHYSIO_LDH(csr_addr) != sm_mask) && (cnt < 10); cnt++) ; return ((cnt == 10) ? -1 : 0); } uint_t cic_read_sm_bar(int board, int bus) { u_longlong_t csr_addr; uint_t sm_bar; procname_t proc = "cic_read_sm_bar"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_BAR_MSB_ADDR, bus); PR_XF("%s:MSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); sm_bar = (uint_t)PHYSIO_LDH(csr_addr); sm_bar <<= 16; csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_BAR_LSB_ADDR, bus); PR_XF("%s:LSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); sm_bar |= (uint_t)PHYSIO_LDH(csr_addr); return (sm_bar); } static int cic_write_sm_bar(int board, int bus, uint_t sm_bar) { int cnt; u_longlong_t csr_addr; uint_t sm_bar_lsb, sm_bar_msb; procname_t proc = "cic_write_sm_bar"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); sm_bar_lsb = sm_bar & 0xffff; sm_bar_msb = (sm_bar >> 16) & 0xffff; /* * Before we can write to the CIC, we need to set * up the CIC write data buffer in the PC. */ if (pc_prep_cic_buffer(CPU->cpu_id, sm_bar_msb) < 0) return (-1); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_BAR_MSB_ADDR, bus); PR_XF("%s:MSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); PR_XF("%s:MSB: sm_bar[31:16] = 0x%x\n", proc, (ushort_t)sm_bar_msb); UPDATE_CIC_HISTORY(CICREG_SMBAR, board, bus, sm_bar); PHYSIO_STH(csr_addr, (ushort_t)sm_bar_msb); for (cnt = 0; ((uint_t)PHYSIO_LDH(csr_addr) != sm_bar_msb) && (cnt < 10); cnt++) ; if (cnt == 10) { cmn_err(CE_WARN, "IDN: 500: failed to write sm_bar (msb) (0x%x)", (uint_t)sm_bar_msb); return (-1); } /* * Now to LSB portion. */ if (pc_prep_cic_buffer(CPU->cpu_id, sm_bar_lsb) < 0) return (-1); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_BAR_LSB_ADDR, bus); PR_XF("%s:LSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); PR_XF("%s:LSB: sm_bar[15:0] = 0x%x\n", proc, (ushort_t)sm_bar_lsb); PHYSIO_STH(csr_addr, (ushort_t)sm_bar_lsb); for (cnt = 0; ((uint_t)PHYSIO_LDH(csr_addr) != sm_bar_lsb) && (cnt < 10); cnt++) ; if (cnt == 10) { cmn_err(CE_WARN, "IDN: 500: failed to write sm_bar (lsb) (0x%x)", (uint_t)sm_bar_lsb); return (-1); } return (0); } uint_t cic_read_sm_lar(int board, int bus) { u_longlong_t csr_addr; uint_t sm_lar; procname_t proc = "cic_read_sm_lar"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_LAR_MSB_ADDR, bus); PR_XF("%s:MSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); sm_lar = (uint_t)PHYSIO_LDH(csr_addr); sm_lar <<= 16; csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_LAR_LSB_ADDR, bus); PR_XF("%s:LSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); sm_lar |= (uint_t)PHYSIO_LDH(csr_addr); return (sm_lar); } static int cic_write_sm_lar(int board, int bus, uint_t sm_lar) { int cnt; u_longlong_t csr_addr; uint_t sm_lar_lsb, sm_lar_msb; procname_t proc = "cic_write_sm_lar"; ASSERT(CPUID_TO_BOARDID(CPU->cpu_id) == board); sm_lar_lsb = sm_lar & 0xffff; sm_lar_msb = (sm_lar >> 16) & 0xffff; /* * Before we can write to the CIC, we need to set * up the CIC write data buffer in the PC. */ if (pc_prep_cic_buffer(CPU->cpu_id, sm_lar_msb) < 0) return (-1); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_LAR_MSB_ADDR, bus); PR_XF("%s:MSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); PR_XF("%s:MSB: sm_lar[31:16] = 0x%x\n", proc, (ushort_t)sm_lar_msb); UPDATE_CIC_HISTORY(CICREG_SMLAR, board, bus, sm_lar); PHYSIO_STH(csr_addr, (ushort_t)sm_lar_msb); for (cnt = 0; ((uint_t)PHYSIO_LDH(csr_addr) != sm_lar_msb) && (cnt < 10); cnt++) ; if (cnt == 10) { cmn_err(CE_WARN, "IDN: 501: failed to write sm_lar (msb) (0x%x)", (uint_t)sm_lar_msb); return (-1); } /* * Now to LSB portion. */ if (pc_prep_cic_buffer(CPU->cpu_id, sm_lar_lsb) < 0) return (-1); csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_SM_LAR_LSB_ADDR, bus); PR_XF("%s:LSB: (bd=%d, bs=%d) csr_addr = 0x%llx\n", proc, board, bus, csr_addr); PR_XF("%s:LSB: sm_lar[15:0] = 0x%x\n", proc, (ushort_t)sm_lar_lsb); PHYSIO_STH(csr_addr, (ushort_t)sm_lar_lsb); for (cnt = 0; ((uint_t)PHYSIO_LDH(csr_addr) != sm_lar_lsb) && (cnt < 10); cnt++) ; if (cnt == 10) { cmn_err(CE_WARN, "IDN: 501: failed to write sm_lar (lsb) (0x%x)", (uint_t)sm_lar_lsb); return (-1); } return (0); } static int cic_get_smmask_bit(void) { u_longlong_t csr_addr; int board; uint_t config1; procname_t proc = "cic_get_smmask_bit"; affinity_set(CPU_CURRENT); board = CPUID_TO_BOARDID(CPU->cpu_id); /* * Now that I'm stuck on this cpu I can go look at this * board's CIC registers. */ csr_addr = MAKE_CIC_CSR_PA(board, CSR_TYPE_CIC, CIC_CONFIG1_ADDR, 0); PR_XF("%s: (bd=%d) csr_addr = 0x%llx (via cpu %d)\n", proc, board, csr_addr, (int)CPU->cpu_id); config1 = (uint_t)PHYSIO_LDH(csr_addr); config1 = CIC_CONFIG1_SMMASK_BIT(config1); affinity_clear(); return (config1); } static int pc_prep_cic_buffer(int cpuid, uint_t cicdata) { int rv; int brd, port; u_longlong_t csr_addr; register int cnt; procname_t proc = "pc_prep_cic_buffer"; ASSERT(CPU->cpu_id == cpuid); port = cpuid % plat_max_cpu_units_per_board(); brd = CPUID_TO_BOARDID(cpuid); csr_addr = STARFIRE_PC_CICBUF_ADDR(brd, port); /* * csr_addr now points to CIC write buffer which resides * in PC register space. */ PR_XF("%s: (cpu=%d) csr_addr = 0x%llx\n", proc, cpuid, csr_addr); PHYSIO_ST(csr_addr, cicdata); /* * Now we need to read back the data to guarantee * it got there. Part of the PC protocol. */ for (cnt = 0; (PHYSIO_LD(csr_addr) != cicdata) && (cnt < 10); cnt++) ; rv = 0; if (cnt == 10) { cmn_err(CE_WARN, "IDN: 502: unable to store data (0x%x) to " "CIC buffer (0x%llx)", cicdata, csr_addr); rv = -1; } else if (cnt >= 1) { PR_XF("%s: MULTIPLE READS (cpu=%d) cnt = %d\n", proc, cpuid, cnt); } return (rv); } /* * -------------------------------------------------- * Write the given MC address decoding register contents (madr) of * the respective remote board (rboard) into all the PCs located on * the local board (lboard). * -------------------------------------------------- */ static int pc_write_madr(pda_handle_t ph, int lboard, int rboard, uint_t madr) { u_longlong_t pc_madr_addr; register int p, ioc; register ushort_t procset, iocset; int rv = 0; uint_t rd_madr; board_desc_t *lbp; procname_t proc = "pc_write_madr"; lbp = pda_get_board_info(ph, lboard); ASSERT(lbp); ASSERT((lbp->bda_board & BDAN_MASK) == BDAN_GOOD); procset = lbp->bda_proc; iocset = lbp->bda_ioc; /* * Update the PCs for the cpus. */ for (p = 0; p < MAX_PROCMODS; procset >>= 4, p++) { int i; if (!((procset & BDAN_MASK) == BDAN_GOOD)) continue; pc_madr_addr = (u_longlong_t)STARFIRE_PC_MADR_ADDR(lboard, rboard, p); /* * On this first iteration of updating the PC * we need to turn off the MADR VALID bit so that * there's no accidental usage of the entry before * all four bytes have been updated in the PC. */ if (madr != 0) { /* * Need to clear valid bit on first * go around. */ madr &= ~STARFIRE_PC_MADR_VALIDBIT; } PR_XF("%s: write madr(0x%x) to pc_addr(0x%llx) " "[lb=%d, rb=%d, cpu=%d]\n", proc, madr, pc_madr_addr, lboard, rboard, p); DEBUG_DELAY(); for (i = 0; i < 20; i++) { PHYSIO_ST(pc_madr_addr, madr); /* * Read back for sanity check. */ rd_madr = PHYSIO_LD(pc_madr_addr); if (madr == rd_madr) break; } if (i > 0) { PR_XF("%s: WARNING: (1) lb=%d, rb=%d, " "madr=0x%x (i=%d)\n", proc, lboard, rboard, madr, i); } if (rd_madr != madr) { cmn_err(CE_WARN, "IDN: 503: (invalidate) failed to update " "PC madr (expected 0x%x, actual 0x%x)", madr, rd_madr); rv++; continue; } if (madr == 0) { continue; } else { /* * Turn the valid bit back on. */ madr |= STARFIRE_PC_MADR_VALIDBIT; } PR_XF("%s: write madr(0x%x) to pc_addr(0x%llx) " "[lb=%d, rb=%d, cpu=%d]\n", proc, madr, pc_madr_addr, lboard, rboard, p); DEBUG_DELAY(); for (i = 0; i < 20; i++) { PHYSIO_ST(pc_madr_addr, madr); /* * Read back for sanity check. */ rd_madr = PHYSIO_LD(pc_madr_addr); if (madr == rd_madr) break; } if (i > 0) { PR_XF("%s: WARNING: (2) lb=%d, rb=%d, " "madr=0x%x (i=%d)\n", proc, lboard, rboard, madr, i); } if (rd_madr != madr) { cmn_err(CE_WARN, "IDN: 503: (validate) failed to update " "PC madr (expected 0x%x, actual 0x%x)", madr, rd_madr); rv++; } } /* * Update the PCs for the iocs. */ for (ioc = 0; ioc < MAX_IOCS; iocset >>= 4, ioc++) { int i; if (!((iocset & BDAN_MASK) == BDAN_GOOD)) continue; pc_madr_addr = (u_longlong_t)STARFIRE_PC_MADR_ADDR(lboard, rboard, ioc + 4); if (madr != 0) { /* * Need to clear valid bit on first * go around. */ madr &= ~STARFIRE_PC_MADR_VALIDBIT; } PR_XF("%s: write madr(0x%x) to iopc_madr_addr(0x%llx) " "[lb=%d, rb=%d, ioc=%d]\n", proc, madr, pc_madr_addr, lboard, rboard, ioc); DEBUG_DELAY(); for (i = 0; i < 20; i++) { PHYSIO_ST(pc_madr_addr, madr); /* * Read back for sanity check. */ rd_madr = PHYSIO_LD(pc_madr_addr); if (madr == rd_madr) break; } if (i > 0) { PR_XF("%s: WARNING: (3) lb=%d, rb=%d, " "madr=0x%x (i=%d)\n", proc, lboard, rboard, madr, i); } if (rd_madr != madr) { cmn_err(CE_WARN, "IDN: 504: (invalidate) failed to update " "IOPC madr (expected 0x%x, actual 0x%x)", madr, rd_madr); rv++; continue; } if (madr == 0) { continue; } else { /* * Turn the valid bit back on. */ madr |= STARFIRE_PC_MADR_VALIDBIT; } PR_XF("%s: write madr(0x%x) to iopc_madr_addr(0x%llx) " "[lb=%d, rb=%d, ioc=%d]\n", proc, madr, pc_madr_addr, lboard, rboard, ioc); DEBUG_DELAY(); for (i = 0; i < 20; i++) { PHYSIO_ST(pc_madr_addr, madr); /* * Read back for sanity check. */ rd_madr = PHYSIO_LD(pc_madr_addr); if (madr == rd_madr) break; } if (i > 0) { PR_XF("%s: WARNING: (4) lb=%d, rb=%d, " "madr=0x%x (i=%d)\n", proc, lboard, rboard, madr, i); } if (rd_madr != madr) { cmn_err(CE_WARN, "IDN: 504: (validate) failed to update " "IOPC madr (expected 0x%x, actual 0x%x)", madr, rd_madr); rv++; } } return (rv ? -1 : 0); } /* * -------------------------------------------------- * Read the array of MC address decoding registers from one of the * PCs on the local board (lboard) into the given in array (mc_adr). * -------------------------------------------------- */ void pc_read_madr(pda_handle_t ph, int lboard, uint_t mc_adr[], int local_only) { u_longlong_t pc_madr_addr; register int p, ioc; register ushort_t procset, iocset; int brd; board_desc_t *lbp; lbp = pda_get_board_info(ph, lboard); ASSERT(lbp); ASSERT((lbp->bda_board & BDAN_MASK) == BDAN_GOOD); procset = lbp->bda_proc; iocset = lbp->bda_ioc; for (p = 0; p < MAX_PROCMODS; procset >>= 4, p++) if ((procset & BDAN_MASK) == BDAN_GOOD) break; if (p == MAX_PROCMODS) { /* * Couldn't find a PC off a cpu, let's check the * IOCs. */ for (ioc = 0; ioc < MAX_IOCS; iocset >>= 4, ioc++) if ((iocset & BDAN_MASK) == BDAN_GOOD) break; if (ioc == MAX_IOCS) { cmn_err(CE_WARN, "IDN: 505: board %d missing any valid PCs", lboard); return; } p = ioc + 4; } pc_madr_addr = (u_longlong_t)STARFIRE_PC_MADR_ADDR(lboard, 0, p); /* * pc_madr_addr = Starts at entry for board 0. */ for (brd = 0; brd < MAX_BOARDS; brd++) { /* * It's possible our local PC may have old entries to * AWOL domains. Only want to pay attention to PC * entries corresponding to our boards. */ lbp = pda_get_board_info(ph, brd); if (!local_only || ((lbp->bda_board & BDAN_MASK) == BDAN_GOOD)) mc_adr[brd] = PHYSIO_LD(pc_madr_addr); else mc_adr[brd] = 0; pc_madr_addr += ((u_longlong_t)1 << STARFIRE_PC_MADR_BOARD_SHIFT); } } /* * -------------------------------------------------- * Read the MC address decoding register contents for all * possible boards and store the results in their respective * slot in mc_adr. Keep a count of non-zero MC ADRs and * return that. * -------------------------------------------------- */ void mc_get_adr_all(pda_handle_t ph, uint_t mc_adr[], int *nmcadr) { int brd; uint_t madr[MAX_BOARDS]; /* * Note that each PC has a complete copy of all MC contents * and so all we have to do is read one PC rather than * each of the MCs in the system. */ brd = CPUID_TO_BOARDID(CPU->cpu_id); pc_read_madr(ph, brd, madr, 1); *nmcadr = 0; for (brd = 0; brd < MAX_BOARDS; brd++) if ((mc_adr[brd] = madr[brd]) != 0) (*nmcadr)++; } static boardset_t get_boardset(pda_handle_t ph, int *nboards) { int brd; int nbrds = 0; boardset_t bmask; if (nboards != NULL) *nboards = 0; bmask = 0; for (brd = 0; brd < MAX_BOARDS; brd++) { if (pda_board_present(ph, brd)) { bmask |= 1 << brd; nbrds++; } } if (nboards != NULL) *nboards = (short)nbrds; return (bmask); } int update_local_hw_config(idn_domain_t *ldp, struct hwconfig *loc_hw) { procname_t proc = "update_local_hw_config"; ASSERT(IDN_DLOCK_IS_EXCL(ldp->domid)); ASSERT(IDN_GLOCK_IS_EXCL()); ASSERT(ldp == &idn_domain[idn.localid]); if (ldp->dhw.dh_boardset != loc_hw->dh_boardset) { int c; PR_PROTO("%s: NEW HW CONFIG (old_bset = 0x%x, " "new_bset = 0x%x)\n", proc, ldp->dhw.dh_boardset, loc_hw->dh_boardset); PR_PROTO("%s: clearing boardset 0x%x\n", proc, ldp->dhw.dh_boardset & ~loc_hw->dh_boardset); PR_PROTO("%s: setting boardset 0x%x\n", proc, loc_hw->dh_boardset & ~ldp->dhw.dh_boardset); idn.dc_boardset &= ~ldp->dhw.dh_boardset; idn.dc_boardset |= loc_hw->dh_boardset; for (c = 0; c < NCPU; c++) { if (CPU_IN_SET(ldp->dcpuset, c)) { CPUSET_DEL(idn.dc_cpuset, c); } } CPUSET_OR(idn.dc_cpuset, cpu_ready_set); bcopy(loc_hw, &ldp->dhw, sizeof (ldp->dhw)); ldp->dcpuset = cpu_ready_set; ldp->dcpu = cpu0.cpu_id; ldp->dncpus = (int)ncpus; ldp->dvote.v.nmembrds = ldp->dhw.dh_nmcadr - 1; ldp->dvote.v.ncpus = (int)ldp->dncpus - 1; ldp->dvote.v.board = CPUID_TO_BOARDID(CPU->cpu_id); return (1); } else { PR_PROTO("%s: NO change detected\n", proc); return (0); } } int get_hw_config(struct hwconfig *loc_hw) { pda_handle_t ph; boardset_t domainset; int bd; int nmcadr; int nboards; procname_t proc = "get_hw_config"; ASSERT(loc_hw != NULL); bzero(loc_hw, sizeof (*loc_hw)); /* * See if sm_mask is writable. * XXX - Should be the same for all CIC's. Do we * we need to verify? */ if (cic_get_smmask_bit() == 0) { /* * If smmask is not writable, we can not allow * IDN operations. */ cmn_err(CE_WARN, "IDN: 506: cic sm_mask is not writeable"); return (-1); } /* * Map in the post2obp structure so we can find * valid boards and hardware asics. */ ph = pda_open(); if (ph == (pda_handle_t)NULL) { cmn_err(CE_WARN, "IDN: 507: failed to map-in post2obp structure"); return (-1); } else if (!pda_is_valid(ph)) { cmn_err(CE_WARN, "IDN: 508: post2obp checksum invalid"); pda_close(ph); return (-1); } /* * Need to read the MC address decoding registers * so that they can be given to other domains. */ loc_hw->dh_boardset = get_boardset(ph, &nboards); loc_hw->dh_nboards = (short)nboards; ASSERT(loc_hw->dh_boardset & (1 << CPUID_TO_BOARDID(CPU->cpu_id))); mc_get_adr_all(ph, loc_hw->dh_mcadr, &nmcadr); loc_hw->dh_nmcadr = (short)nmcadr; affinity_set(CPU_CURRENT); /* * There will always be a bus 0 (logical). */ bd = CPUID_TO_BOARDID(CPU->cpu_id); domainset = (boardset_t)cic_read_domain_mask(bd, 0); affinity_clear(); if (!idn_cpu_per_board(ph, cpu_ready_set, loc_hw)) { pda_close(ph); return (-1); } pda_close(ph); #ifdef DEBUG { int brd; for (brd = 0; brd < MAX_BOARDS; brd++) if (loc_hw->dh_mcadr[brd] != 0) { PR_XF("%s: brd %d, mc = 0x%x\n", proc, brd, loc_hw->dh_mcadr[brd]); } } #endif /* DEBUG */ if ((loc_hw->dh_boardset != domainset) || (loc_hw->dh_nmcadr < 1)) return (-1); else return (0); } /* * Function called via timeout() to wakeup a possibly stuck * idnxf_shmem_update_all() should not all cpus check-in after a * x-call to update their respective CICs. */ /*ARGSUSED0*/ static void idnxf_shmem_wakeup(void *arg) { struct idnxf_cic_info *idnxfp = (struct idnxf_cic_info *)arg; int count; int expired; procname_t proc = "idnxf_shmem_wakeup"; expired = ((ddi_get_lbolt() - idnxfp->xf_start_time) >= IDNCIC_TIMEOUT) ? 1 : 0; if ((count = idnxfp->xf_count) == 0) { /* * Everybody has finished. Wakeup the requester. */ mutex_enter(&idnxfp->xf_mutex); cv_signal(&idnxfp->xf_cv); mutex_exit(&idnxfp->xf_mutex); } else if ((count > 0) && expired) { /* * There are still active cic updaters and time * has expired. Bail on them. */ idnxfp->xf_errtimer = 1; #ifdef DEBUG /* * Special debug case since idn_debug * may have been temporarily cleared * during xc_some. */ if ((idn_debug | o_idn_debug) & IDNDBG_REGS) printf("%s: TIMEOUT...bailing on %d lost CIC " "updates...\n", proc, count); #endif /* DEBUG */ ATOMIC_SUB(idnxfp->xf_count, count); mutex_enter(&idnxfp->xf_mutex); cv_signal(&idnxfp->xf_cv); mutex_exit(&idnxfp->xf_mutex); } else { (void) timeout(idnxf_shmem_wakeup, (caddr_t)idnxfp, (clock_t)IDNCIC_TIMECHK); } } /* * Called indirectly from idnxf_shmem_update_all() via a xcall * for the recepient cpu to update the CICs on its respective * board. * IMPORTANT: NO console output from this routine!! */ static void idnxf_shmem_update_one(uint64_t arg1, uint64_t arg2) { struct idnxf_cic_info *idnxfp = (struct idnxf_cic_info *)arg1; time_t start_time = (time_t)arg2; int rv, cpuid, brd, bus; boardset_t smmask; cpuid = CPU->cpu_id; brd = CPUID_TO_BOARDID(cpuid); if (idnxfp->xf_start_time != start_time) { /* * Ooops! Not my place to intrude. */ idnxf_cic_info.xf_errcic[brd][0] = IDNCIC_BUSY; ATOMIC_INC(idnxf_cic_info.xf_errcnt); goto done; } /* * We're executing out of the context of a cross-call * so we're effectively bound! :) */ for (bus = 0; bus < MAX_ABUSES; bus++) { /* * XXX - need to worry about shuffle?? */ if (!(idnxfp->xf_abus_mask & (1 << bus))) continue; smmask = cic_read_sm_mask(brd, bus); if (idnxfp->xf_doadd) { smmask |= idnxfp->xf_boardset; (void) cic_write_sm_mask(brd, bus, smmask); if (idnxfp->xf_smbase != (uint_t)-1) { (void) cic_write_sm_bar(brd, bus, idnxfp->xf_smbase); (void) cic_write_sm_lar(brd, bus, idnxfp->xf_smlimit); } /* * Verify data got there! */ rv = verify_smregs(brd, bus, smmask, idnxfp->xf_smbase, idnxfp->xf_smlimit); } else { smmask &= ~idnxfp->xf_boardset; (void) cic_write_sm_mask(brd, bus, smmask); if (!smmask) { /* * Update the LAR first so that we effectively * disable the register without possibly * opening the window to transaction we * don't care about. Updating the LAR first * will guarantee we effectively turn it * off immediately. */ (void) cic_write_sm_lar(brd, bus, 0); (void) cic_write_sm_bar(brd, bus, 1); rv = verify_smregs(brd, bus, smmask, 1, 0); } else { rv = verify_smregs(brd, bus, smmask, (uint_t)-1, (uint_t)-1); } } if (rv) { idnxf_cic_info.xf_errcic[brd][bus] = IDNCIC_ERR; ATOMIC_INC(idnxf_cic_info.xf_errcnt); } else { idnxf_cic_info.xf_errcic[brd][bus] = IDNCIC_OK; } } done: ATOMIC_DEC(idnxf_cic_info.xf_count); } static int idnxf_shmem_update_all(pda_handle_t ph, boardset_t boardset, uint_t smbase, uint_t smlimit, int doadd) { cpuset_t target_cpuset; int target_count; int rv = 0; int c, brd, bus; short abus_mask; time_t start_time; procname_t proc = "idnxf_shmem_update_all"; ASSERT(MUTEX_HELD(&idn_xf_mutex)); pda_get_busmask(ph, &abus_mask, NULL); CPUSET_ZERO(target_cpuset); target_count = 0; /* * Build a cpuset of target cpus (one per board) to * be used to send the CIC update xcall. */ for (brd = 0; brd < MAX_BOARDS; brd++) { /* * Need to target an available cpu on the target board * so that we can look at the CICs on that board. */ c = board_to_ready_cpu(brd, cpu_ready_set); if (c == -1) { /* * If there's no cpu on this board, no * need to update the CICs. */ continue; } CPUSET_ADD(target_cpuset, c); target_count++; } if (CPUSET_ISNULL(target_cpuset)) { PR_REGS("%s: NO target cpus to update!!\n", proc); return (0); } RESET_CIC_HISTORY(); /* * Broadcast out the CIC update request and then * sit back and wait for dinner to arrive! * Let's set up the global structure all the xcall * recepients will read. */ start_time = ddi_get_lbolt(); /* * Set the start time. Make sure it's different * then the previous run. */ if (start_time <= idnxf_cic_info.xf_start_time) start_time++; idnxf_cic_info.xf_start_time = start_time; idnxf_cic_info.xf_abus_mask = abus_mask; idnxf_cic_info.xf_boardset = boardset; idnxf_cic_info.xf_smbase = smbase; idnxf_cic_info.xf_smlimit = smlimit; idnxf_cic_info.xf_doadd = doadd; idnxf_cic_info.xf_count = target_count; idnxf_cic_info.xf_errcnt = 0; idnxf_cic_info.xf_errtimer = 0; bzero(&idnxf_cic_info.xf_errcic, sizeof (idnxf_cic_info.xf_errcic)); /* * Broadcast out the xcall to do the task. */ #ifdef DEBUG { uint_t tu32, tl32; tu32 = UPPER32_CPUMASK(target_cpuset); tl32 = LOWER32_CPUMASK(target_cpuset); PR_REGS("%s: (start %ld) broadcasting CIC - " "%s to cpus 0x%x.%0x\n", proc, start_time, doadd ? "LINK" : "UNLINK", tu32, tl32); } /* * Can't dump debug during cross-calls. */ o_idn_debug = idn_debug; idn_debug = 0; #endif /* DEBUG */ xc_attention(target_cpuset); xc_some(target_cpuset, idnxf_shmem_update_one, (uint64_t)&idnxf_cic_info, (uint64_t)start_time); xc_dismissed(target_cpuset); ASSERT(idnxf_cic_info.xf_count == 0); #ifdef DEBUG idn_debug = o_idn_debug; o_idn_debug = 0; #endif /* DEBUG */ PR_REGS("%s: waiting for completion of %d CIC - %s...\n", proc, idnxf_cic_info.xf_count, doadd ? "LINKS" : "UNLINKS"); PR_REGS("%s: CIC - %s have checked IN.\n", proc, doadd ? "LINKS" : "UNLINKS"); /* * Modifying xf_start_time effectively disables any * possible outstanding xcall's since they don't touch * idnxf_cic_info unless their given start_time matches * that in the idnxf_cic_info structure. */ idnxf_cic_info.xf_start_time++; PR_REGS("%s: xf_errcnt = %d, xf_errtimer = %d\n", proc, idnxf_cic_info.xf_errcnt, idnxf_cic_info.xf_errtimer); DUMP_CIC_HISTORY(); /* * Should errors be fatal? (panic). */ rv = 0; for (c = 0; c < NCPU; c++) { if (!CPU_IN_SET(target_cpuset, c)) continue; brd = CPUID_TO_BOARDID(c); for (bus = 0; bus < MAX_ABUSES; bus++) { if (!(abus_mask & (1 << bus))) continue; switch (idnxf_cic_info.xf_errcic[brd][bus]) { case IDNCIC_UNKNOWN: /* * Unknown is only an error if the * timer expired. */ if (!idnxf_cic_info.xf_errtimer) break; cmn_err(CE_WARN, "IDN: 509: CPU %d never responded " "to CIC update", c); /*FALLTHROUGH*/ case IDNCIC_ERR: cmn_err(CE_WARN, "IDN: 510: failed write-smregs " "(bd=%d, bs=%d, sm(bar=0x%x, " "lar=0x%x))", brd, bus, smbase, smlimit); rv++; break; case IDNCIC_BUSY: cmn_err(CE_WARN, "IDN: 511: update-one " "(cpu=%d, bd=%d) time conflict", c, brd); /* * Should never occur. Not fatal, * just continue. */ break; default: PR_REGS("%s: board %d, bus %d " "(bar=0x%x,lar=0x%x) - update OK\n", proc, brd, bus, smbase, smlimit); break; } } } return (rv ? -1 : 0); } /* * Add the respective boardset/base/limit/mcadr's to the local * domain's hardware configuration with respect to the SMR. * * is_master Indicates remote domain is a master. */ int idnxf_shmem_add(int is_master, boardset_t boardset, pfn_t pfnbase, pfn_t pfnlimit, uint_t *mcadr) { int rv = 0; register int brd, rbrd; register boardset_t localboardset; uint_t madr; uint_t smbase, smlimit; pda_handle_t ph; procname_t proc = "idnxf_shmem_add"; localboardset = idn_domain[idn.localid].dhw.dh_boardset; ASSERT(localboardset && boardset && ((localboardset & boardset) == 0)); ASSERT(is_master ? (pfnbase && pfnlimit && mcadr) : 1); if (pfnbase != PFN_INVALID) { smbase = (uint_t)PFN_TO_SMADDR(pfnbase); smlimit = (uint_t)PFN_TO_SMADDR(pfnlimit); } else { smbase = smlimit = (uint_t)-1; } PR_REGS("%s: is_master=%d, boardset=0x%x, smbase=0x%x, smlimit=%x\n", proc, is_master, boardset, smbase, smlimit); /* * Need to serialize hardware access so we don't have multiple * threads attempting to access hardware regs simulataneously. * This should not be a significant performance penalty since * the hardware is only touched when domains are linking or * unlinking. */ mutex_enter(&idn_xf_mutex); /* * Map in the post2obp structure so we can find * bus config information. */ ph = pda_open(); if (ph == (pda_handle_t)NULL) { cmn_err(CE_WARN, "IDN: 507: failed to map-in post2obp structure"); rv = -1; goto done; } else if (!pda_is_valid(ph)) { cmn_err(CE_WARN, "IDN: 508: post2obp checksum invalid"); rv = -1; goto done; } /* * Take a checkpoint in bbsram for diagnostic purposes. */ CHECKPOINT_OPENED(IDNSB_CHKPT_SMR, boardset, 1); rv = idnxf_shmem_update_all(ph, boardset, smbase, smlimit, 1); if (rv || (is_master == 0)) goto done; /* * If this is a slave (i.e. remote domain is_master), * then we need to deprogram our PCs. */ PR_REGS("%s: updating PC regs (lboardset=0x%x, rboardset=0x%x)\n", proc, localboardset, boardset); for (brd = 0; brd < MAX_BOARDS; brd++) { if (!BOARD_IN_SET(localboardset, brd)) continue; /* * If this is a slave (i.e. remote domain is_master), * then we need to program our PCs. */ for (rbrd = 0; rbrd < MAX_BOARDS; rbrd++) { if ((madr = mcadr[rbrd]) == 0) continue; ASSERT(BOARD_IN_SET(boardset, rbrd)); /* * Write the MC adr for the respective * remote board (rbrd) into the PCs of * the given local board (brd). */ if (pc_write_madr(ph, brd, rbrd, madr) < 0) { cmn_err(CE_WARN, "IDN: 512: failed [add] write-madr " "(bd=%d, rbd=%d, madr=0x%x)", brd, rbrd, madr); rv = -1; goto done; } } } done: if (ph) pda_close(ph); mutex_exit(&idn_xf_mutex); /* * XXX * * Failure here is fatal. Disable IDN? * NOn-zero return value will at least prevent * linkage with domain - probably sufficient. */ return (rv); } /* * Remove the respective boardset from the local domain's * hardware configuration with respect to the SMR. * * is_master Indicates remote domain is a master. */ int idnxf_shmem_sub(int is_master, boardset_t boardset) { int rv = 0; register int brd, rbrd; register boardset_t localboardset; pda_handle_t ph; procname_t proc = "idnxf_shmem_sub"; localboardset = idn_domain[idn.localid].dhw.dh_boardset; ASSERT(localboardset && boardset && ((localboardset & boardset) == 0)); PR_REGS("%s: is_master=%d, boardset=0x%x\n", proc, is_master, boardset); /* * Need to serialize hardware access so we don't have multiple * threads attempting to access hardware regs simulataneously. * This should not be a significant performance penalty since * the hardware is only touched when domains are linking or * unlinking. */ mutex_enter(&idn_xf_mutex); /* * Map in the post2obp structure so we can find * bus config information. */ ph = pda_open(); if (ph == (pda_handle_t)NULL) { cmn_err(CE_WARN, "IDN: 507: failed to map-in post2obp structure"); rv = -1; goto done; } else if (!pda_is_valid(ph)) { cmn_err(CE_WARN, "IDN: 508: post2obp checksum invalid"); rv = -1; goto done; } /* * Take a checkpoint in bbsram for diagnostic purposes. */ CHECKPOINT_CLOSED(IDNSB_CHKPT_SMR, boardset, 2); rv = idnxf_shmem_update_all(ph, boardset, (uint_t)-1, (uint_t)-1, 0); if (rv || (is_master == 0)) goto done; /* * If this is a slave (i.e. remote domain is_master), * then we need to deprogram our PCs. */ PR_REGS("%s: reseting PC regs (lboardset=0x%x, rboardset=0x%x)\n", proc, localboardset, boardset); for (brd = 0; brd < MAX_BOARDS; brd++) { if (!BOARD_IN_SET(localboardset, brd)) continue; for (rbrd = 0; rbrd < MAX_BOARDS; rbrd++) { if (!BOARD_IN_SET(boardset, rbrd)) continue; /* * Clear the MC adr for the respective * remote board (rbrd) into the PCs of * the given local board (brd). */ if (pc_write_madr(ph, brd, rbrd, 0) < 0) { cmn_err(CE_WARN, "IDN: 512: failed [del] write-madr " "(bd=%d, rbd=%d, madr=0x%x)", brd, rbrd, 0); rv = -1; goto done; } } } done: if (ph) pda_close(ph); mutex_exit(&idn_xf_mutex); return (rv); } /* * We cannot cross-trap cpu_flush_ecache since it references * %g7 via CPU. It's possible that %g7 may not be set up * when the trap comes in, and could thus cause a crash. * Well...at least that's what has been happening when I * tried x-calls within an xc_attention (KMISS) panic. * Instead we use cross-calls. However, since we can't * xc_attention around a cross-call, we have not guaranteed * way of knowing the operation succeeded. To synchronize * this flush operation across cpus, we use a semaphore * which is V'd by the receiving cpus and P'd by the caller * initiating the all-cpus flush. */ /*ARGSUSED0*/ void idn_flush_ecache(uint64_t arg1, uint64_t arg2) { extern void cpu_flush_ecache(void); cpu_flush_ecache(); /* * Paranoia...Give things a chance to drain. */ drv_usecwait(500000); /* 500 msec */ } /* * Flush the ecache's of all the cpus within this domain of * any possible SMR references. * This logic is borrowed from ecc.c:cpu_flush_ecache(). */ void idnxf_flushall_ecache() { cpuset_t local_cpuset; procname_t proc = "idnxf_flushall_ecache"; PR_XF("%s: flushing ecache (cpu_ready_set = 0x%x.%x)\n", proc, UPPER32_CPUMASK(cpu_ready_set), LOWER32_CPUMASK(cpu_ready_set)); CHECKPOINT_CACHE_CLEAR_DEBUG(1); CHECKPOINT_CACHE_STEP_DEBUG(0x1, 2); local_cpuset = cpu_ready_set; xc_attention(local_cpuset); /* * We tell each cpu to do a flush and then we hit * a semaphore to synchronize with all of them * to guarantee they have completed the flush before * we continue on. We have to do this type of * sychronization since we can't xc_attention around * a cross-call. */ CHECKPOINT_CACHE_STEP_DEBUG(0x2, 3); xc_all(idn_flush_ecache, 0, 0); CHECKPOINT_CACHE_STEP_DEBUG(0x4, 4); xc_dismissed(local_cpuset); CHECKPOINT_CACHE_STEP_DEBUG(0x8, 5); } /* * -------------------------------------------------- */ static int verify_smregs(int brd, int bus, boardset_t smmask, uint_t smbase, uint_t smlimit) { int rv = 0; uint_t smreg; if (smmask != (boardset_t)-1) { smreg = (uint_t)cic_read_sm_mask(brd, bus); if (smreg != (uint_t)smmask) { cmn_err(CE_WARN, "IDN: 513: sm-mask error " "(expected = 0x%x, actual = 0x%x)", (uint_t)smmask, smreg); rv++; } } if (smbase != (uint_t)-1) { smreg = cic_read_sm_bar(brd, bus); if (smreg != smbase) { cmn_err(CE_WARN, "IDN: 514: sm-base error " "(expected = 0x%x, actual = 0x%x)", smbase, smreg); rv++; } } if (smlimit != (uint_t)-1) { smreg = cic_read_sm_lar(brd, bus); if (smreg != smlimit) { cmn_err(CE_WARN, "IDN: 515: sm-limit error " "(expected = 0x%x, actual = 0x%x)", smlimit, smreg); rv++; } } return (rv ? -1 : 0); } /* * ------------------------------------------------------------- */ int idn_cpu_per_board(pda_handle_t ph, cpuset_t cset, struct hwconfig *hwp) { int b, err = 0; boardset_t bset, cpu_bset; board_desc_t *lbp; if (!idn_check_cpu_per_board) return (1); bset = hwp->dh_boardset; CPUSET_TO_BOARDSET(cset, cpu_bset); /* * Every board has at least one cpu, we're happy. */ if (cpu_bset == bset) return (1); /* * Well, not all boards had cpus. That's okay so * long as they don't have memory also. * Get rid of the boards that have a cpu. */ bset &= ~cpu_bset; /* * None of the remaining boards in the set shold have mem. */ err = 0; /* * A NULL post2obp pointer indicates we're checking * the config of a remote domain. Since we can't * look at the post2obp of the remote domain, we'll * have to trust what he passed us in his config. */ if (ph && !pda_is_valid(ph)) { cmn_err(CE_WARN, "IDN: 508: post2obp checksum invalid"); return (0); } for (b = 0; b < MAX_BOARDS; b++) { if (!BOARD_IN_SET(bset, b)) continue; lbp = ph ? pda_get_board_info(ph, b) : NULL; if ((lbp && (BDA_NBL(lbp->bda_board, BDA_MC_NBL) == BDAN_GOOD)) || (!lbp && hwp->dh_mcadr[b])) { err++; cmn_err(CE_WARN, "IDN: 516: (%s) board %d has memory, " "but no CPUs - CPU per memory board REQUIRED", ph ? "local" : "remote", b); } } return (err ? 0 : 1); }