/* * 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 /* xt_one() */ #include #include #include #include #include #include #include /* flush_instr_mem() */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef TRUE #define TRUE (1) #endif #ifndef FALSE #define FALSE (0) #endif /* * Function to register and deregister callbacks, for sunfire only. */ extern void plat_register_tod_fault(void (*func)(enum tod_fault_type)); /* * This table represents the FHC interrupt priorities. They range from * 1-15, and have been modeled after the sun4d interrupts. The mondo * number anded with 0x7 is used to index into this table. This was * done to save table space. */ static int fhc_int_priorities[] = { PIL_15, /* System interrupt priority */ PIL_12, /* zs interrupt priority */ PIL_15, /* TOD interrupt priority */ PIL_15 /* Fan Fail priority */ }; static void fhc_tod_fault(enum tod_fault_type tod_bad); static void fhc_cpu_shutdown_self(void); static void os_completes_shutdown(void); /* * The dont_calibrate variable is meant to be set to one in /etc/system * or by boot -h so that the calibration tables are not used. This * is useful for checking thermistors whose output seems to be incorrect. */ static int dont_calibrate = 0; /* Only one processor should powerdown the system. */ static int powerdown_started = 0; /* Let user disable overtemp powerdown. */ int enable_overtemp_powerdown = 1; /* * The following tables correspond to the degress Celcius for each count * value possible from the 8-bit A/C convertors on each type of system * board for the UltraSPARC Server systems. To access a temperature, * just index into the correct table using the count from the A/D convertor * register, and that is the correct temperature in degress Celsius. These * values can be negative. */ static short cpu_table[] = { -16, -14, -12, -10, -8, -6, -4, -2, /* 0-7 */ 1, 4, 6, 8, 10, 12, 13, 15, /* 8-15 */ 16, 18, 19, 20, 22, 23, 24, 25, /* 16-23 */ 26, 27, 28, 29, 30, 31, 32, 33, /* 24-31 */ 34, 35, 35, 36, 37, 38, 39, 39, /* 32-39 */ 40, 41, 41, 42, 43, 44, 44, 45, /* 40-47 */ 46, 46, 47, 47, 48, 49, 49, 50, /* 48-55 */ 51, 51, 52, 53, 53, 54, 54, 55, /* 56-63 */ 55, 56, 56, 57, 57, 58, 58, 59, /* 64-71 */ 60, 60, 61, 61, 62, 62, 63, 63, /* 72-79 */ 64, 64, 65, 65, 66, 66, 67, 67, /* 80-87 */ 68, 68, 69, 69, 70, 70, 71, 71, /* 88-95 */ 72, 72, 73, 73, 74, 74, 75, 75, /* 96-103 */ 76, 76, 77, 77, 78, 78, 79, 79, /* 104-111 */ 80, 80, 81, 81, 82, 82, 83, 83, /* 112-119 */ 84, 84, 85, 85, 86, 86, 87, 87, /* 120-127 */ 88, 88, 89, 89, 90, 90, 91, 91, /* 128-135 */ 92, 92, 93, 93, 94, 94, 95, 95, /* 136-143 */ 96, 96, 97, 98, 98, 99, 99, 100, /* 144-151 */ 100, 101, 101, 102, 103, 103, 104, 104, /* 152-159 */ 105, 106, 106, 107, 107, 108, 109, 109, /* 160-167 */ 110, /* 168 */ }; #define CPU_MX_CNT (sizeof (cpu_table)/sizeof (short)) static short cpu2_table[] = { -17, -16, -15, -14, -13, -12, -11, -10, /* 0-7 */ -9, -8, -7, -6, -5, -4, -3, -2, /* 8-15 */ -1, 0, 1, 2, 3, 4, 5, 6, /* 16-23 */ 7, 8, 9, 10, 11, 12, 13, 13, /* 24-31 */ 14, 15, 16, 16, 17, 18, 18, 19, /* 32-39 */ 20, 20, 21, 22, 22, 23, 24, 24, /* 40-47 */ 25, 25, 26, 26, 27, 27, 28, 28, /* 48-55 */ 29, 30, 30, 31, 31, 32, 32, 33, /* 56-63 */ 33, 34, 34, 35, 35, 36, 36, 37, /* 64-71 */ 37, 37, 38, 38, 39, 39, 40, 40, /* 72-79 */ 41, 41, 42, 42, 43, 43, 43, 44, /* 80-87 */ 44, 45, 45, 46, 46, 46, 47, 47, /* 88-95 */ 48, 48, 49, 49, 50, 50, 50, 51, /* 96-103 */ 51, 52, 52, 53, 53, 53, 54, 54, /* 104-111 */ 55, 55, 56, 56, 56, 57, 57, 58, /* 112-119 */ 58, 59, 59, 59, 60, 60, 61, 61, /* 120-127 */ 62, 62, 63, 63, 63, 64, 64, 65, /* 128-135 */ 65, 66, 66, 67, 67, 68, 68, 68, /* 136-143 */ 69, 69, 70, 70, 71, 71, 72, 72, /* 144-151 */ 73, 73, 74, 74, 75, 75, 76, 76, /* 152-159 */ 77, 77, 78, 78, 79, 79, 80, 80, /* 160-167 */ 81, 81, 82, 83, 83, 84, 84, 85, /* 168-175 */ 85, 86, 87, 87, 88, 88, 89, 90, /* 176-183 */ 90, 91, 92, 92, 93, 94, 94, 95, /* 184-191 */ 96, 96, 97, 98, 99, 99, 100, 101, /* 192-199 */ 102, 103, 103, 104, 105, 106, 107, 108, /* 200-207 */ 109, 110, /* 208-209 */ }; #define CPU2_MX_CNT (sizeof (cpu2_table)/sizeof (short)) static short io_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, /* 0-7 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 8-15 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 16-23 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 24-31 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 32-39 */ 0, 3, 7, 10, 13, 15, 17, 19, /* 40-47 */ 21, 23, 25, 27, 28, 30, 31, 32, /* 48-55 */ 34, 35, 36, 37, 38, 39, 41, 42, /* 56-63 */ 43, 44, 45, 46, 46, 47, 48, 49, /* 64-71 */ 50, 51, 52, 53, 53, 54, 55, 56, /* 72-79 */ 57, 57, 58, 59, 60, 60, 61, 62, /* 80-87 */ 62, 63, 64, 64, 65, 66, 66, 67, /* 88-95 */ 68, 68, 69, 70, 70, 71, 72, 72, /* 96-103 */ 73, 73, 74, 75, 75, 76, 77, 77, /* 104-111 */ 78, 78, 79, 80, 80, 81, 81, 82, /* 112-119 */ }; #define IO_MN_CNT 40 #define IO_MX_CNT (sizeof (io_table)/sizeof (short)) static short clock_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, /* 0-7 */ 0, 0, 0, 0, 1, 2, 4, 5, /* 8-15 */ 7, 8, 10, 11, 12, 13, 14, 15, /* 16-23 */ 17, 18, 19, 20, 21, 22, 23, 24, /* 24-31 */ 24, 25, 26, 27, 28, 29, 29, 30, /* 32-39 */ 31, 32, 32, 33, 34, 35, 35, 36, /* 40-47 */ 37, 38, 38, 39, 40, 40, 41, 42, /* 48-55 */ 42, 43, 44, 44, 45, 46, 46, 47, /* 56-63 */ 48, 48, 49, 50, 50, 51, 52, 52, /* 64-71 */ 53, 54, 54, 55, 56, 57, 57, 58, /* 72-79 */ 59, 59, 60, 60, 61, 62, 63, 63, /* 80-87 */ 64, 65, 65, 66, 67, 68, 68, 69, /* 88-95 */ 70, 70, 71, 72, 73, 74, 74, 75, /* 96-103 */ 76, 77, 78, 78, 79, 80, 81, 82, /* 104-111 */ }; #define CLK_MN_CNT 11 #define CLK_MX_CNT (sizeof (clock_table)/sizeof (short)) /* * System temperature limits. * * The following variables are the warning and danger limits for the * different types of system boards. The limits are different because * the various boards reach different nominal temperatures because * of the different components that they contain. * * The warning limit is the temperature at which the user is warned. * The danger limit is the temperature at which the system is shutdown. * In the case of CPU/Memory system boards, the system will attempt * to offline and power down processors on a board in an attempt to * bring the board back into the nominal temperature range before * shutting down the system. * * These values can be tuned via /etc/system or boot -h. */ short cpu_warn_temp = 73; /* CPU/Memory Warning Temperature */ short cpu_danger_temp = 83; /* CPU/Memory Danger Temperature */ short io_warn_temp = 60; /* IO Board Warning Temperature */ short io_danger_temp = 68; /* IO Board Danger Temperature */ short clk_warn_temp = 60; /* Clock Board Warning Temperature */ short clk_danger_temp = 68; /* Clock Board Danger Temperature */ short dft_warn_temp = 60; /* default warning temp value */ short dft_danger_temp = 68; /* default danger temp value */ short cpu_warn_temp_4x = 60; /* CPU/Memory warning temp for 400 MHZ */ short cpu_danger_temp_4x = 68; /* CPU/Memory danger temp for 400 MHZ */ /* * This variable tells us if we are in a heat chamber. It is set * early on in boot, after we check the OBP 'mfg-mode' property in * the options node. */ static int temperature_chamber = -1; /* * The fhc memloc structure is protected under the bdlist lock */ static struct fhc_memloc *fhc_base_memloc = NULL; /* * Driver global fault list mutex and list head pointer. The list is * protected by the mutex and contains a record of all known faults. * Faults can be inherited from the PROM or detected by the kernel. */ static kmutex_t ftlist_mutex; static struct ft_link_list *ft_list = NULL; static int ft_nfaults = 0; /* * Table of all known fault strings. This table is indexed by the fault * type. Do not change the ordering of the table without redefining the * fault type enum list on fhc.h. */ char *ft_str_table[] = { "Core Power Supply", /* FT_CORE_PS */ "Overtemp", /* FT_OVERTEMP */ "AC Power", /* FT_AC_PWR */ "Peripheral Power Supply", /* FT_PPS */ "System 3.3 Volt Power", /* FT_CLK_33 */ "System 5.0 Volt Power", /* FT_CLK_50 */ "Peripheral 5.0 Volt Power", /* FT_V5_P */ "Peripheral 12 Volt Power", /* FT_V12_P */ "Auxiliary 5.0 Volt Power", /* FT_V5_AUX */ "Peripheral 5.0 Volt Precharge", /* FT_V5_P_PCH */ "Peripheral 12 Volt Precharge", /* FT_V12_P_PCH */ "System 3.3 Volt Precharge", /* FT_V3_PCH */ "System 5.0 Volt Precharge", /* FT_V5_PCH */ "Peripheral Power Supply Fans", /* FT_PPS_FAN */ "Rack Exhaust Fan", /* FT_RACK_EXH */ "Disk Drive Fan", /* FT_DSK_FAN */ "AC Box Fan", /* FT_AC_FAN */ "Key Switch Fan", /* FT_KEYSW_FAN */ "Minimum Power", /* FT_INSUFFICIENT_POWER */ "PROM detected", /* FT_PROM */ "Hot Plug Support System", /* FT_HOT_PLUG */ "TOD" /* FT_TODFAULT */ }; static int ft_max_index = (sizeof (ft_str_table) / sizeof (char *)); /* * Function prototypes */ static int fhc_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, void *); static int fhc_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result); static int fhc_add_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp); static void fhc_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp); static int fhc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd); static int fhc_detach(dev_info_t *devi, ddi_detach_cmd_t cmd); static int fhc_init(struct fhc_soft_state *softsp); static void fhc_unmap_regs(struct fhc_soft_state *softsp); static enum board_type fhc_board_type(struct fhc_soft_state *, int); static void fhc_xlate_intrs(ddi_intr_handle_impl_t *hdlp, uint32_t ign); static int fhc_ctlops_peekpoke(ddi_ctl_enum_t, peekpoke_ctlops_t *, void *result); static void fhc_add_kstats(struct fhc_soft_state *); static int fhc_kstat_update(kstat_t *, int); static int check_for_chamber(void); static int ft_ks_snapshot(struct kstat *, void *, int); static int ft_ks_update(struct kstat *, int); static int check_central(int board); /* * board type and A/D convertor output passed in and real temperature * is returned. */ static short calibrate_temp(enum board_type, uchar_t, uint_t); static enum temp_state get_temp_state(enum board_type, short, int); /* Routine to determine if there are CPUs on this board. */ static int cpu_on_board(int); static void build_bd_display_str(char *, enum board_type, int); /* Interrupt distribution callback function. */ static void fhc_intrdist(void *); /* CPU power control */ int fhc_cpu_poweroff(struct cpu *); /* cpu_poweroff()->platform */ int fhc_cpu_poweron(struct cpu *); /* cpu_poweron()->platform */ extern struct cpu_node cpunodes[]; extern void halt(char *); /* * Configuration data structures */ static struct bus_ops fhc_bus_ops = { BUSO_REV, ddi_bus_map, /* map */ 0, /* get_intrspec */ 0, /* add_intrspec */ 0, /* remove_intrspec */ i_ddi_map_fault, /* map_fault */ ddi_no_dma_map, /* dma_map */ ddi_no_dma_allochdl, ddi_no_dma_freehdl, ddi_no_dma_bindhdl, ddi_no_dma_unbindhdl, ddi_no_dma_flush, ddi_no_dma_win, ddi_dma_mctl, /* dma_ctl */ fhc_ctlops, /* ctl */ ddi_bus_prop_op, /* prop_op */ 0, /* (*bus_get_eventcookie)(); */ 0, /* (*bus_add_eventcall)(); */ 0, /* (*bus_remove_eventcall)(); */ 0, /* (*bus_post_event)(); */ 0, /* (*bus_intr_control)(); */ 0, /* (*bus_config)(); */ 0, /* (*bus_unconfig)(); */ 0, /* (*bus_fm_init)(); */ 0, /* (*bus_fm_fini)(); */ 0, /* (*bus_fm_access_enter)(); */ 0, /* (*bus_fm_access_exit)(); */ 0, /* (*bus_power)(); */ fhc_intr_ops /* (*bus_intr_op)(); */ }; static struct cb_ops fhc_cb_ops = { nulldev, /* open */ nulldev, /* close */ nulldev, /* strategy */ nulldev, /* print */ nulldev, /* dump */ nulldev, /* read */ nulldev, /* write */ nulldev, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* cb_prop_op */ 0, /* streamtab */ D_MP|D_NEW|D_HOTPLUG, /* Driver compatibility flag */ CB_REV, /* rev */ nodev, /* cb_aread */ nodev /* cb_awrite */ }; static struct dev_ops fhc_ops = { DEVO_REV, /* rev */ 0, /* refcnt */ ddi_no_info, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ fhc_attach, /* attach */ fhc_detach, /* detach */ nulldev, /* reset */ &fhc_cb_ops, /* cb_ops */ &fhc_bus_ops, /* bus_ops */ nulldev, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; /* * Driver globals * TODO - We need to investigate what locking needs to be done here. */ void *fhcp; /* fhc soft state hook */ extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ "FHC Nexus", /* Name of module. */ &fhc_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, /* rev */ (void *)&modldrv, NULL }; /* * These are the module initialization routines. */ static caddr_t shutdown_va; int _init(void) { int error; if ((error = ddi_soft_state_init(&fhcp, sizeof (struct fhc_soft_state), 1)) != 0) return (error); fhc_bdlist_init(); mutex_init(&ftlist_mutex, NULL, MUTEX_DEFAULT, NULL); shutdown_va = vmem_alloc(heap_arena, PAGESIZE, VM_SLEEP); ASSERT(shutdown_va != NULL); plat_register_tod_fault(fhc_tod_fault); return (mod_install(&modlinkage)); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) != 0) return (error); plat_register_tod_fault(NULL); mutex_destroy(&ftlist_mutex); fhc_bdlist_fini(); ddi_soft_state_fini(&fhcp); return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * Reset the interrupt mapping registers. * This function resets the values during DDI_RESUME. * * NOTE: This function will not work for a full CPR cycle * and is currently designed to handle the RESUME after a connect. * * Note about the PROM handling of moving CENTRAL to another board: * The PROM moves the IGN identity (igr register) from the * original CENTRAL to the new one. This means that we do not * duplicate the fhc_attach code that sets it to (board number * 2). * We rely on only using FHC interrupts from one board only * (the UART and SYS interrupts) so that the values of the other IGNs * are irrelevant. The benefit of this approach is that we don't * have to have to tear down and rebuild the interrupt records * for UART and SYS. It is also why we don't try to change the * board number in the fhc instance for the clock board. */ static void fhc_handle_imr(struct fhc_soft_state *softsp) { int i; int cent; uint_t tmp_reg; if (softsp->is_central) { uint_t want_igr, act_igr; want_igr = softsp->list->sc.board << 1; act_igr = *softsp->igr & 0x1f; if (want_igr != act_igr) { *softsp->igr = want_igr; tmp_reg = *softsp->igr; #ifdef lint tmp_reg = tmp_reg; #endif /* We must now re-issue any pending interrupts. */ for (i = 0; i < FHC_MAX_INO; i++) { if (*(softsp->intr_regs[i].clear_reg) == 3) { *(softsp->intr_regs[i].clear_reg) = ISM_IDLE; tmp_reg = *(softsp->intr_regs[i].clear_reg); #ifdef lint tmp_reg = tmp_reg; #endif } } cmn_err(CE_NOTE, "central IGN corruption fixed: " "got %x wanted %x", act_igr, want_igr); } return; } ASSERT(softsp->list->sc.board == FHC_BSR_TO_BD(*(softsp->bsr))); cent = check_central(softsp->list->sc.board); /* Loop through all 4 FHC interrupt mapping registers */ for (i = 0; i < FHC_MAX_INO; i++) { if (i == FHC_SYS_INO && *(softsp->intr_regs[i].clear_reg) == 3) { cmn_err(CE_NOTE, "found lost system interrupt, resetting.."); *(softsp->intr_regs[i].clear_reg) = ISM_IDLE; /* * ensure atomic write with this read. */ tmp_reg = *(softsp->intr_regs[i].clear_reg); #ifdef lint tmp_reg = tmp_reg; #endif } /* * The mapping registers on the board with the "central" bit * set should not be touched as it has been taken care by POST. */ if (cent) continue; *(softsp->intr_regs[i].mapping_reg) = 0; /* * ensure atomic write with this read. */ tmp_reg = *(softsp->intr_regs[i].mapping_reg); #ifdef lint tmp_reg = tmp_reg; #endif } } static int check_central(int board) { uint_t cs_value; /* * This is the value of AC configuration and status reg * in the Local Devices space. We access it as a physical * address. */ cs_value = ldphysio(AC_BCSR(board)); if (cs_value & AC_CENTRAL) return (TRUE); else return (FALSE); } static int fhc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { struct fhc_soft_state *softsp; int instance; instance = ddi_get_instance(devi); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: softsp = ddi_get_soft_state(fhcp, instance); /* IGR, NOT_BRD_PRES handled by prom */ /* reset interrupt mapping registers */ fhc_handle_imr(softsp); return (DDI_SUCCESS); default: return (DDI_FAILURE); } if (ddi_soft_state_zalloc(fhcp, instance) != DDI_SUCCESS) return (DDI_FAILURE); softsp = ddi_get_soft_state(fhcp, instance); /* Set the dip in the soft state */ softsp->dip = devi; if (fhc_init(softsp) != DDI_SUCCESS) goto bad; ddi_report_dev(devi); return (DDI_SUCCESS); bad: ddi_soft_state_free(fhcp, instance); return (DDI_FAILURE); } static int fhc_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { int board; int instance; struct fhc_soft_state *softsp; fhc_bd_t *list = NULL; /* get the instance of this devi */ instance = ddi_get_instance(devi); /* get the soft state pointer for this device node */ softsp = ddi_get_soft_state(fhcp, instance); board = softsp->list->sc.board; switch (cmd) { case DDI_SUSPEND: return (DDI_SUCCESS); case DDI_DETACH: /* grab the lock on the board list */ list = fhc_bdlist_lock(board); if (fhc_bd_detachable(board) && !fhc_bd_is_jtag_master(board)) break; else fhc_bdlist_unlock(); /* FALLTHROUGH */ default: return (DDI_FAILURE); } /* Remove the interrupt redistribution callback. */ intr_dist_rem(fhc_intrdist, (void *)devi); /* remove the soft state pointer from the board list */ list->softsp = NULL; /* clear inherited faults from the PROM. */ clear_fault(list->sc.board, FT_PROM, FT_BOARD); /* remove the kstat for this board */ kstat_delete(softsp->fhc_ksp); /* destroy the mutexes in this soft state structure */ mutex_destroy(&softsp->poll_list_lock); mutex_destroy(&softsp->ctrl_lock); /* unmap all the register sets */ fhc_unmap_regs(softsp); /* release the board list lock now */ fhc_bdlist_unlock(); /* free the soft state structure */ ddi_soft_state_free(fhcp, instance); return (DDI_SUCCESS); } static enum board_type fhc_board_type(struct fhc_soft_state *softsp, int board) { int proplen; char *board_type; enum board_type type; if (softsp->is_central) type = CLOCK_BOARD; else if (ddi_getlongprop(DDI_DEV_T_ANY, softsp->dip, DDI_PROP_DONTPASS, "board-type", (caddr_t)&board_type, &proplen) == DDI_PROP_SUCCESS) { /* match the board-type string */ if (strcmp(CPU_BD_NAME, board_type) == 0) { type = CPU_BOARD; } else if (strcmp(MEM_BD_NAME, board_type) == 0) { type = MEM_BOARD; } else if (strcmp(IO_2SBUS_BD_NAME, board_type) == 0) { type = IO_2SBUS_BOARD; } else if (strcmp(IO_SBUS_FFB_BD_NAME, board_type) == 0) { type = IO_SBUS_FFB_BOARD; } else if (strcmp(IO_2SBUS_SOCPLUS_BD_NAME, board_type) == 0) { type = IO_2SBUS_SOCPLUS_BOARD; } else if (strcmp(IO_SBUS_FFB_SOCPLUS_BD_NAME, board_type) == 0) { type = IO_SBUS_FFB_SOCPLUS_BOARD; } else if (strcmp(IO_PCI_BD_NAME, board_type) == 0) { type = IO_PCI_BOARD; } else { type = UNKNOWN_BOARD; } kmem_free(board_type, proplen); } else type = UNKNOWN_BOARD; /* * if the board type is indeterminate, it must be determined. */ if (type == UNKNOWN_BOARD) { /* * Use the UPA64 bits from the FHC. * This is not the best solution since we * cannot fully type the IO boards. */ if (cpu_on_board(board)) type = CPU_BOARD; else if ((*(softsp->bsr) & FHC_UPADATA64A) || (*(softsp->bsr) & FHC_UPADATA64B)) type = IO_2SBUS_BOARD; else type = MEM_BOARD; } return (type); } static void fhc_unmap_regs(struct fhc_soft_state *softsp) { dev_info_t *dip = softsp->dip; if (softsp->id) { ddi_unmap_regs(dip, 0, (caddr_t *)&softsp->id, 0, 0); softsp->id = NULL; } if (softsp->igr) { ddi_unmap_regs(dip, 1, (caddr_t *)&softsp->igr, 0, 0); softsp->igr = NULL; } if (softsp->intr_regs[FHC_FANFAIL_INO].mapping_reg) { ddi_unmap_regs(dip, 2, (caddr_t *)&softsp->intr_regs[FHC_FANFAIL_INO].mapping_reg, 0, 0); softsp->intr_regs[FHC_FANFAIL_INO].mapping_reg = NULL; } if (softsp->intr_regs[FHC_SYS_INO].mapping_reg) { ddi_unmap_regs(dip, 3, (caddr_t *)&softsp->intr_regs[FHC_SYS_INO].mapping_reg, 0, 0); softsp->intr_regs[FHC_SYS_INO].mapping_reg = NULL; } if (softsp->intr_regs[FHC_UART_INO].mapping_reg) { ddi_unmap_regs(dip, 4, (caddr_t *)&softsp->intr_regs[FHC_UART_INO].mapping_reg, 0, 0); softsp->intr_regs[FHC_UART_INO].mapping_reg = NULL; } if (softsp->intr_regs[FHC_TOD_INO].mapping_reg) { ddi_unmap_regs(dip, 5, (caddr_t *)&softsp->intr_regs[FHC_TOD_INO].mapping_reg, 0, 0); softsp->intr_regs[FHC_TOD_INO].mapping_reg = NULL; } } static int fhc_init(struct fhc_soft_state *softsp) { int i; uint_t tmp_reg; int board; /* * Map in the FHC registers. Specifying length and offset of * zero maps in the entire OBP register set. */ /* map in register set 0 */ if (ddi_map_regs(softsp->dip, 0, (caddr_t *)&softsp->id, 0, 0)) { cmn_err(CE_WARN, "fhc%d: unable to map internal " "registers", ddi_get_instance(softsp->dip)); goto bad; } /* * Fill in the virtual addresses of the registers in the * fhc_soft_state structure. */ softsp->rctrl = (uint_t *)((char *)(softsp->id) + FHC_OFF_RCTRL); softsp->ctrl = (uint_t *)((char *)(softsp->id) + FHC_OFF_CTRL); softsp->bsr = (uint_t *)((char *)(softsp->id) + FHC_OFF_BSR); softsp->jtag_ctrl = (uint_t *)((char *)(softsp->id) + FHC_OFF_JTAG_CTRL); softsp->jt_master.jtag_cmd = (uint_t *)((char *)(softsp->id) + FHC_OFF_JTAG_CMD); /* map in register set 1 */ if (ddi_map_regs(softsp->dip, 1, (caddr_t *)&softsp->igr, 0, 0)) { cmn_err(CE_WARN, "fhc%d: unable to map IGR " "register", ddi_get_instance(softsp->dip)); goto bad; } /* * map in register set 2 * XXX this can never be used as an interrupt generator * (hardware queue overflow in fhc) */ if (ddi_map_regs(softsp->dip, 2, (caddr_t *)&softsp->intr_regs[FHC_FANFAIL_INO].mapping_reg, 0, 0)) { cmn_err(CE_WARN, "fhc%d: unable to map Fan Fail " "IMR register", ddi_get_instance(softsp->dip)); goto bad; } /* map in register set 3 */ if (ddi_map_regs(softsp->dip, 3, (caddr_t *)&softsp->intr_regs[FHC_SYS_INO].mapping_reg, 0, 0)) { cmn_err(CE_WARN, "fhc%d: unable to map System " "IMR register\n", ddi_get_instance(softsp->dip)); goto bad; } /* map in register set 4 */ if (ddi_map_regs(softsp->dip, 4, (caddr_t *)&softsp->intr_regs[FHC_UART_INO].mapping_reg, 0, 0)) { cmn_err(CE_WARN, "fhc%d: unable to map UART " "IMR register\n", ddi_get_instance(softsp->dip)); goto bad; } /* map in register set 5 */ if (ddi_map_regs(softsp->dip, 5, (caddr_t *)&softsp->intr_regs[FHC_TOD_INO].mapping_reg, 0, 0)) { cmn_err(CE_WARN, "fhc%d: unable to map FHC TOD " "IMR register", ddi_get_instance(softsp->dip)); goto bad; } /* Loop over all intr sets and setup the VAs for the ISMR */ /* TODO - Make sure we are calculating the ISMR correctly. */ for (i = 0; i < FHC_MAX_INO; i++) { softsp->intr_regs[i].clear_reg = (uint_t *)((char *)(softsp->intr_regs[i].mapping_reg) + FHC_OFF_ISMR); /* Now clear the state machines to idle */ *(softsp->intr_regs[i].clear_reg) = ISM_IDLE; } /* * It is OK to not have a OBP_BOARDNUM property. This happens for * the board which is a child of central. However this FHC * still needs a proper Interrupt Group Number programmed * into the Interrupt Group register, because the other * instance of FHC, which is not under central, will properly * program the IGR. The numbers from the two settings of the * IGR need to be the same. One driver cannot wait for the * other to program the IGR, because there is no guarantee * which instance of FHC will get attached first. */ if ((board = (int)ddi_getprop(DDI_DEV_T_ANY, softsp->dip, DDI_PROP_DONTPASS, OBP_BOARDNUM, -1)) == -1) { /* * Now determine the board number by reading the * hardware register. */ board = FHC_BSR_TO_BD(*(softsp->bsr)); softsp->is_central = 1; } /* * If this fhc holds JTAG master line, and is not the central fhc, * (this avoids two JTAG master nodes) then initialize the * mutex and set the flag in the structure. */ if ((*(softsp->jtag_ctrl) & JTAG_MASTER_EN) && !softsp->is_central) { mutex_init(&(softsp->jt_master.lock), NULL, MUTEX_DEFAULT, NULL); softsp->jt_master.is_master = 1; } else { softsp->jt_master.is_master = 0; } fhc_bd_init(softsp, board, fhc_board_type(softsp, board)); /* Initialize the mutex guarding the poll_list. */ mutex_init(&softsp->poll_list_lock, NULL, MUTEX_DRIVER, NULL); /* Initialize the mutex guarding the FHC CSR */ mutex_init(&softsp->ctrl_lock, NULL, MUTEX_DRIVER, NULL); /* Initialize the poll_list to be empty */ for (i = 0; i < MAX_ZS_CNT; i++) { softsp->poll_list[i].funcp = NULL; } /* Modify the various registers in the FHC now */ /* * We know this board to be present now, record that state and * remove the NOT_BRD_PRES condition */ if (!(softsp->is_central)) { mutex_enter(&softsp->ctrl_lock); *(softsp->ctrl) |= FHC_NOT_BRD_PRES; /* Now flush the hardware store buffers. */ tmp_reg = *(softsp->ctrl); #ifdef lint tmp_reg = tmp_reg; #endif /* XXX record the board state in global space */ mutex_exit(&softsp->ctrl_lock); /* Add kstats for all non-central instances of the FHC. */ fhc_add_kstats(softsp); } /* * Read the device tree to see if this system is in an environmental * chamber. */ if (temperature_chamber == -1) { temperature_chamber = check_for_chamber(); } /* Check for inherited faults from the PROM. */ if (*softsp->ctrl & FHC_LED_MID) { reg_fault(softsp->list->sc.board, FT_PROM, FT_BOARD); } /* * setup the IGR. Shift the board number over by one to get * the UPA MID. */ *(softsp->igr) = (softsp->list->sc.board) << 1; /* Now flush the hardware store buffers. */ tmp_reg = *(softsp->id); #ifdef lint tmp_reg = tmp_reg; #endif /* Add the interrupt redistribution callback. */ intr_dist_add(fhc_intrdist, (void *)softsp->dip); return (DDI_SUCCESS); bad: fhc_unmap_regs(softsp); return (DDI_FAILURE); } static uint_t fhc_intr_wrapper(caddr_t arg) { uint_t intr_return; uint_t tmpreg; struct fhc_wrapper_arg *intr_info = (struct fhc_wrapper_arg *)arg; uint_t (*funcp)(caddr_t, caddr_t) = intr_info->funcp; caddr_t iarg1 = intr_info->arg1; caddr_t iarg2 = intr_info->arg2; dev_info_t *dip = intr_info->child; tmpreg = ISM_IDLE; DTRACE_PROBE4(interrupt__start, dev_info_t, dip, void *, funcp, caddr_t, iarg1, caddr_t, iarg2); intr_return = (*funcp)(iarg1, iarg2); DTRACE_PROBE4(interrupt__complete, dev_info_t, dip, void *, funcp, caddr_t, iarg1, int, intr_return); /* Idle the state machine. */ *(intr_info->clear_reg) = tmpreg; /* Flush the hardware store buffers. */ tmpreg = *(intr_info->clear_reg); #ifdef lint tmpreg = tmpreg; #endif /* lint */ return (intr_return); } /* * fhc_zs_intr_wrapper * * This function handles intrerrupts where more than one device may interupt * the fhc with the same mondo. */ #define MAX_INTR_CNT 10 static uint_t fhc_zs_intr_wrapper(caddr_t arg) { struct fhc_soft_state *softsp = (struct fhc_soft_state *)arg; uint_t (*funcp0)(caddr_t, caddr_t); uint_t (*funcp1)(caddr_t, caddr_t); caddr_t funcp0_arg1, funcp0_arg2, funcp1_arg1, funcp1_arg2; uint_t tmp_reg; uint_t result = DDI_INTR_UNCLAIMED; volatile uint_t *clear_reg; uchar_t *spurious_cntr = &softsp->spurious_zs_cntr; funcp0 = softsp->poll_list[0].funcp; funcp1 = softsp->poll_list[1].funcp; funcp0_arg1 = softsp->poll_list[0].arg1; funcp0_arg2 = softsp->poll_list[0].arg2; funcp1_arg1 = softsp->poll_list[1].arg1; funcp1_arg2 = softsp->poll_list[1].arg2; clear_reg = softsp->intr_regs[FHC_UART_INO].clear_reg; if (funcp0 != NULL) { if ((funcp0)(funcp0_arg1, funcp0_arg2) == DDI_INTR_CLAIMED) { result = DDI_INTR_CLAIMED; } } if (funcp1 != NULL) { if ((funcp1)(funcp1_arg1, funcp1_arg2) == DDI_INTR_CLAIMED) { result = DDI_INTR_CLAIMED; } } if (result == DDI_INTR_UNCLAIMED) { (*spurious_cntr)++; if (*spurious_cntr < MAX_INTR_CNT) { result = DDI_INTR_CLAIMED; } else { *spurious_cntr = (uchar_t)0; } } else { *spurious_cntr = (uchar_t)0; } /* Idle the state machine. */ *(clear_reg) = ISM_IDLE; /* flush the store buffers. */ tmp_reg = *(clear_reg); #ifdef lint tmp_reg = tmp_reg; #endif return (result); } /* * add_intrspec - Add an interrupt specification. */ static int fhc_add_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { int ino; struct fhc_wrapper_arg *fhc_arg; struct fhc_soft_state *softsp = (struct fhc_soft_state *) ddi_get_soft_state(fhcp, ddi_get_instance(dip)); volatile uint_t *mondo_vec_reg; uint_t tmp_mondo_vec; uint_t tmpreg; /* HW flush reg */ uint_t cpu_id; int ret = DDI_SUCCESS; /* Xlate the interrupt */ fhc_xlate_intrs(hdlp, (softsp->list->sc.board << BD_IVINTR_SHFT)); /* get the mondo number */ ino = FHC_INO(hdlp->ih_vector); mondo_vec_reg = softsp->intr_regs[ino].mapping_reg; ASSERT(ino < FHC_MAX_INO); /* We don't use the two spare interrupts. */ if (ino >= FHC_MAX_INO) { cmn_err(CE_WARN, "fhc%d: Spare interrupt %d not usable", ddi_get_instance(dip), ino); return (DDI_FAILURE); } /* TOD and Fan Fail interrupts are not usable */ if (ino == FHC_TOD_INO) { cmn_err(CE_WARN, "fhc%d: TOD interrupt not usable", ddi_get_instance(dip)); return (DDI_FAILURE); } if (ino == FHC_FANFAIL_INO) { cmn_err(CE_WARN, "fhc%d: Fan fail interrupt not usable", ddi_get_instance(dip)); return (DDI_FAILURE); } /* * If the interrupt is for the zs chips, use the vector * polling lists. Otherwise use a straight handler. */ if (ino == FHC_UART_INO) { int32_t zs_inst; /* First lock the mutex for this poll_list */ mutex_enter(&softsp->poll_list_lock); /* * Add this interrupt to the polling list. */ /* figure out where to add this item in the list */ for (zs_inst = 0; zs_inst < MAX_ZS_CNT; zs_inst++) { if (softsp->poll_list[zs_inst].funcp == NULL) { softsp->poll_list[zs_inst].arg1 = hdlp->ih_cb_arg1; softsp->poll_list[zs_inst].arg2 = hdlp->ih_cb_arg2; softsp->poll_list[zs_inst].funcp = (ddi_intr_handler_t *) hdlp->ih_cb_func; softsp->poll_list[zs_inst].inum = hdlp->ih_inum; softsp->poll_list[zs_inst].child = rdip; break; } } if (zs_inst >= MAX_ZS_CNT) { cmn_err(CE_WARN, "fhc%d: poll list overflow", ddi_get_instance(dip)); mutex_exit(&softsp->poll_list_lock); ret = DDI_FAILURE; goto done; } /* * If polling list is empty, then install handler * and enable interrupts for this ino. */ if (zs_inst == 0) { DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, (ddi_intr_handler_t *)fhc_zs_intr_wrapper, (caddr_t)softsp, NULL); ret = i_ddi_add_ivintr(hdlp); DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, softsp->poll_list[zs_inst].funcp, softsp->poll_list[zs_inst].arg1, softsp->poll_list[zs_inst].arg2); if (ret != DDI_SUCCESS) goto done; } /* * If both zs handlers are active, then this is the * second add_intrspec called, so do not enable * the IMR_VALID bit, it is already on. */ if (zs_inst > 0) { /* now release the mutex and return */ mutex_exit(&softsp->poll_list_lock); goto done; } else { /* just release the mutex */ mutex_exit(&softsp->poll_list_lock); } } else { /* normal interrupt installation */ int32_t i; /* Allocate a nexus interrupt data structure */ fhc_arg = kmem_alloc(sizeof (struct fhc_wrapper_arg), KM_SLEEP); fhc_arg->child = rdip; fhc_arg->mapping_reg = mondo_vec_reg; fhc_arg->clear_reg = (softsp->intr_regs[ino].clear_reg); fhc_arg->softsp = softsp; fhc_arg->funcp = (ddi_intr_handler_t *)hdlp->ih_cb_func; fhc_arg->arg1 = hdlp->ih_cb_arg1; fhc_arg->arg2 = hdlp->ih_cb_arg2; fhc_arg->inum = hdlp->ih_inum; for (i = 0; i < FHC_MAX_INO; i++) { if (softsp->intr_list[i] == 0) { softsp->intr_list[i] = fhc_arg; break; } } /* * Save the fhc_arg in the ispec so we can use this info * later to uninstall this interrupt spec. */ DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, (ddi_intr_handler_t *)fhc_intr_wrapper, (caddr_t)fhc_arg, NULL); ret = i_ddi_add_ivintr(hdlp); DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, fhc_arg->funcp, fhc_arg->arg1, fhc_arg->arg2); if (ret != DDI_SUCCESS) goto done; } /* * Clear out a stale 'pending' or 'transmit' state in * this device's ISM that might have been left from a * previous session. * * Since all FHC interrupts are level interrupts, any * real interrupting condition will immediately transition * the ISM back to pending. */ *(softsp->intr_regs[ino].clear_reg) = ISM_IDLE; /* * Program the mondo vector accordingly. This MUST be the * last thing we do. Once we program the ino, the device * may begin to interrupt. */ cpu_id = intr_dist_cpuid(); tmp_mondo_vec = cpu_id << INR_PID_SHIFT; /* don't do this for fan because fan has a special control */ if (ino == FHC_FANFAIL_INO) panic("fhc%d: enabling fanfail interrupt", ddi_get_instance(dip)); else tmp_mondo_vec |= IMR_VALID; DPRINTF(FHC_INTERRUPT_DEBUG, ("Mondo 0x%x mapping reg: 0x%p", hdlp->ih_vector, (void *)mondo_vec_reg)); /* Store it in the hardware reg. */ *mondo_vec_reg = tmp_mondo_vec; /* Read a FHC register to flush store buffers */ tmpreg = *(softsp->id); #ifdef lint tmpreg = tmpreg; #endif done: return (ret); } /* * remove_intrspec - Remove an interrupt specification. */ static void fhc_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { volatile uint_t *mondo_vec_reg; volatile uint_t tmpreg; int i; struct fhc_soft_state *softsp = (struct fhc_soft_state *) ddi_get_soft_state(fhcp, ddi_get_instance(dip)); int ino; /* Xlate the interrupt */ fhc_xlate_intrs(hdlp, (softsp->list->sc.board << BD_IVINTR_SHFT)); /* get the mondo number */ ino = FHC_INO(hdlp->ih_vector); if (ino == FHC_UART_INO) { int intr_found = 0; /* Lock the poll_list first */ mutex_enter(&softsp->poll_list_lock); /* * Find which entry in the poll list belongs to this * intrspec. */ for (i = 0; i < MAX_ZS_CNT; i++) { if (softsp->poll_list[i].child == rdip && softsp->poll_list[i].inum == hdlp->ih_inum) { softsp->poll_list[i].funcp = NULL; intr_found++; } } /* If we did not find an entry, then we have a problem */ if (!intr_found) { cmn_err(CE_WARN, "fhc%d: Intrspec not found in" " poll list", ddi_get_instance(dip)); mutex_exit(&softsp->poll_list_lock); goto done; } /* * If we have removed all active entries for the poll * list, then we have to disable interupts at this point. */ if ((softsp->poll_list[0].funcp == NULL) && (softsp->poll_list[1].funcp == NULL)) { mondo_vec_reg = softsp->intr_regs[FHC_UART_INO].mapping_reg; *mondo_vec_reg &= ~IMR_VALID; /* flush the hardware buffers */ tmpreg = *(softsp->ctrl); /* Eliminate the particular handler from the system. */ i_ddi_rem_ivintr(hdlp); } mutex_exit(&softsp->poll_list_lock); } else { int32_t i; for (i = 0; i < FHC_MAX_INO; i++) if (softsp->intr_list[i]->child == rdip && softsp->intr_list[i]->inum == hdlp->ih_inum) break; if (i >= FHC_MAX_INO) goto done; mondo_vec_reg = softsp->intr_list[i]->mapping_reg; /* Turn off the valid bit in the mapping register. */ /* XXX what about FHC_FANFAIL owned imr? */ *mondo_vec_reg &= ~IMR_VALID; /* flush the hardware store buffers */ tmpreg = *(softsp->id); #ifdef lint tmpreg = tmpreg; #endif /* Eliminate the particular handler from the system. */ i_ddi_rem_ivintr(hdlp); kmem_free(softsp->intr_list[i], sizeof (struct fhc_wrapper_arg)); softsp->intr_list[i] = 0; } done: ; } /* new intr_ops structure */ static int fhc_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { int ret = DDI_SUCCESS; switch (intr_op) { case DDI_INTROP_GETCAP: *(int *)result = DDI_INTR_FLAG_LEVEL; break; case DDI_INTROP_ALLOC: *(int *)result = hdlp->ih_scratch1; break; case DDI_INTROP_FREE: break; case DDI_INTROP_GETPRI: if (hdlp->ih_pri == 0) { struct fhc_soft_state *softsp = (struct fhc_soft_state *)ddi_get_soft_state(fhcp, ddi_get_instance(dip)); /* Xlate the interrupt */ fhc_xlate_intrs(hdlp, (softsp->list->sc.board << BD_IVINTR_SHFT)); } *(int *)result = hdlp->ih_pri; break; case DDI_INTROP_SETPRI: break; case DDI_INTROP_ADDISR: ret = fhc_add_intr_impl(dip, rdip, hdlp); break; case DDI_INTROP_REMISR: fhc_remove_intr_impl(dip, rdip, hdlp); break; case DDI_INTROP_ENABLE: case DDI_INTROP_DISABLE: break; case DDI_INTROP_NINTRS: case DDI_INTROP_NAVAIL: *(int *)result = i_ddi_get_intx_nintrs(rdip); break; case DDI_INTROP_SETCAP: case DDI_INTROP_SETMASK: case DDI_INTROP_CLRMASK: case DDI_INTROP_GETPENDING: ret = DDI_ENOTSUP; break; case DDI_INTROP_SUPPORTED_TYPES: /* only support fixed interrupts */ *(int *)result = i_ddi_get_intx_nintrs(rdip) ? DDI_INTR_TYPE_FIXED : 0; break; default: ret = i_ddi_intr_ops(dip, rdip, intr_op, hdlp, result); break; } return (ret); } /* * FHC Control Ops routine * * Requests handled here: * DDI_CTLOPS_INITCHILD see impl_ddi_sunbus_initchild() for details * DDI_CTLOPS_UNINITCHILD see fhc_uninit_child() for details * DDI_CTLOPS_REPORTDEV TODO - need to implement this. */ static int fhc_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result) { switch (op) { case DDI_CTLOPS_INITCHILD: DPRINTF(FHC_CTLOPS_DEBUG, ("DDI_CTLOPS_INITCHILD\n")); return (impl_ddi_sunbus_initchild((dev_info_t *)arg)); case DDI_CTLOPS_UNINITCHILD: impl_ddi_sunbus_removechild((dev_info_t *)arg); return (DDI_SUCCESS); case DDI_CTLOPS_REPORTDEV: /* * TODO - Figure out what makes sense to report here. */ return (DDI_SUCCESS); case DDI_CTLOPS_POKE: case DDI_CTLOPS_PEEK: return (fhc_ctlops_peekpoke(op, (peekpoke_ctlops_t *)arg, result)); default: return (ddi_ctlops(dip, rdip, op, arg, result)); } } /* * We're prepared to claim that the interrupt string is in * the form of a list of specifications, or we're dealing * with on-board devices and we have an interrupt_number property which * gives us our mondo number. * Translate the mondos into fhcintrspecs. */ /* ARGSUSED */ static void fhc_xlate_intrs(ddi_intr_handle_impl_t *hdlp, uint32_t ign) { uint32_t mondo; mondo = hdlp->ih_vector; hdlp->ih_vector = (mondo | ign); if (hdlp->ih_pri == 0) hdlp->ih_pri = fhc_int_priorities[FHC_INO(mondo)]; } static int fhc_ctlops_peekpoke(ddi_ctl_enum_t cmd, peekpoke_ctlops_t *in_args, void *result) { int err = DDI_SUCCESS; on_trap_data_t otd; /* No safe access except for peek/poke is supported. */ if (in_args->handle != NULL) return (DDI_FAILURE); /* Set up protected environment. */ if (!on_trap(&otd, OT_DATA_ACCESS)) { uintptr_t tramp = otd.ot_trampoline; if (cmd == DDI_CTLOPS_POKE) { otd.ot_trampoline = (uintptr_t)&poke_fault; err = do_poke(in_args->size, (void *)in_args->dev_addr, (void *)in_args->host_addr); } else { otd.ot_trampoline = (uintptr_t)&peek_fault; err = do_peek(in_args->size, (void *)in_args->dev_addr, (void *)in_args->host_addr); result = (void *)in_args->host_addr; } otd.ot_trampoline = tramp; } else err = DDI_FAILURE; /* Take down protected environment. */ no_trap(); return (err); } /* * This function initializes the temperature arrays for use. All * temperatures are set in to invalid value to start. */ void init_temp_arrays(struct temp_stats *envstat) { int i; envstat->index = 0; for (i = 0; i < L1_SZ; i++) { envstat->l1[i] = NA_TEMP; } for (i = 0; i < L2_SZ; i++) { envstat->l2[i] = NA_TEMP; } for (i = 0; i < L3_SZ; i++) { envstat->l3[i] = NA_TEMP; } for (i = 0; i < L4_SZ; i++) { envstat->l4[i] = NA_TEMP; } for (i = 0; i < L5_SZ; i++) { envstat->l5[i] = NA_TEMP; } envstat->max = NA_TEMP; envstat->min = NA_TEMP; envstat->trend = TREND_UNKNOWN; envstat->version = TEMP_KSTAT_VERSION; envstat->override = NA_TEMP; } /* Inhibit warning messages below this temperature, eg for CPU poweron. */ static uint_t fhc_cpu_warning_temp_threshold = FHC_CPU_WARNING_TEMP_THRESHOLD; /* * This function manages the temperature history in the temperature * statistics buffer passed in. It calls the temperature calibration * routines and maintains the time averaged temperature data. */ void update_temp(dev_info_t *pdip, struct temp_stats *envstat, uchar_t value) { uint_t index; /* The absolute temperature counter */ uint_t tmp_index; /* temp index into upper level array */ int count; /* Count of non-zero values in array */ int total; /* sum total of non-zero values in array */ short real_temp; /* calibrated temperature */ int i; struct fhc_soft_state *softsp; char buffer[256]; /* buffer for warning of overtemp */ enum temp_state temp_state; /* Temperature state */ /* * NOTE: This global counter is not protected since we're called * serially for each board. */ static int shutdown_msg = 0; /* Flag if shutdown warning issued */ /* determine soft state pointer of parent */ softsp = ddi_get_soft_state(fhcp, ddi_get_instance(pdip)); envstat->index++; index = envstat->index; /* * You need to update the level 5 intervals first, since * they are based on the data from the level 4 intervals, * and so on, down to the level 1 intervals. */ /* update the level 5 intervals if it is time */ if (((tmp_index = L5_INDEX(index)) > 0) && (L5_REM(index) == 0)) { /* Generate the index within the level 5 array */ tmp_index -= 1; /* decrement by 1 for indexing */ tmp_index = tmp_index % L5_SZ; /* take an average of the level 4 array */ for (i = 0, count = 0, total = 0; i < L4_SZ; i++) { /* Do not include zero values in average */ if (envstat->l4[i] != NA_TEMP) { total += (int)envstat->l4[i]; count++; } } /* * If there were any level 4 data points to average, * do so. */ if (count != 0) { envstat->l5[tmp_index] = total/count; } else { envstat->l5[tmp_index] = NA_TEMP; } } /* update the level 4 intervals if it is time */ if (((tmp_index = L4_INDEX(index)) > 0) && (L4_REM(index) == 0)) { /* Generate the index within the level 4 array */ tmp_index -= 1; /* decrement by 1 for indexing */ tmp_index = tmp_index % L4_SZ; /* take an average of the level 3 array */ for (i = 0, count = 0, total = 0; i < L3_SZ; i++) { /* Do not include zero values in average */ if (envstat->l3[i] != NA_TEMP) { total += (int)envstat->l3[i]; count++; } } /* * If there were any level 3 data points to average, * do so. */ if (count != 0) { envstat->l4[tmp_index] = total/count; } else { envstat->l4[tmp_index] = NA_TEMP; } } /* update the level 3 intervals if it is time */ if (((tmp_index = L3_INDEX(index)) > 0) && (L3_REM(index) == 0)) { /* Generate the index within the level 3 array */ tmp_index -= 1; /* decrement by 1 for indexing */ tmp_index = tmp_index % L3_SZ; /* take an average of the level 2 array */ for (i = 0, count = 0, total = 0; i < L2_SZ; i++) { /* Do not include zero values in average */ if (envstat->l2[i] != NA_TEMP) { total += (int)envstat->l2[i]; count++; } } /* * If there were any level 2 data points to average, * do so. */ if (count != 0) { envstat->l3[tmp_index] = total/count; } else { envstat->l3[tmp_index] = NA_TEMP; } } /* update the level 2 intervals if it is time */ if (((tmp_index = L2_INDEX(index)) > 0) && (L2_REM(index) == 0)) { /* Generate the index within the level 2 array */ tmp_index -= 1; /* decrement by 1 for indexing */ tmp_index = tmp_index % L2_SZ; /* take an average of the level 1 array */ for (i = 0, count = 0, total = 0; i < L1_SZ; i++) { /* Do not include zero values in average */ if (envstat->l1[i] != NA_TEMP) { total += (int)envstat->l1[i]; count++; } } /* * If there were any level 1 data points to average, * do so. */ if (count != 0) { envstat->l2[tmp_index] = total/count; } else { envstat->l2[tmp_index] = NA_TEMP; } } /* determine the current temperature in degrees Celcius */ if (envstat->override != NA_TEMP) { /* use override temperature for this board */ real_temp = envstat->override; } else { /* Run the calibration function using this board type */ real_temp = calibrate_temp(softsp->list->sc.type, value, softsp->list->sc.ac_compid); } envstat->l1[index % L1_SZ] = real_temp; /* check if the temperature state for this device needs to change */ temp_state = get_temp_state(softsp->list->sc.type, real_temp, softsp->list->sc.board); /* has the state changed? Then get the board string ready */ if (temp_state != envstat->state) { int board = softsp->list->sc.board; enum board_type type = softsp->list->sc.type; build_bd_display_str(buffer, type, board); if (temp_state > envstat->state) { if (envstat->state == TEMP_OK) { if (type == CLOCK_BOARD) { reg_fault(0, FT_OVERTEMP, FT_SYSTEM); } else { reg_fault(board, FT_OVERTEMP, FT_BOARD); } } /* heating up, change state now */ envstat->temp_cnt = 0; envstat->state = temp_state; if (temp_state == TEMP_WARN) { /* now warn the user of the problem */ cmn_err(CE_WARN, "%s is warm (temperature: %dC). " "Please check system cooling", buffer, real_temp); fhc_bd_update(board, SYSC_EVT_BD_OVERTEMP); if (temperature_chamber == -1) temperature_chamber = check_for_chamber(); } else if (temp_state == TEMP_DANGER) { cmn_err(CE_WARN, "%s is very hot (temperature: %dC)", buffer, real_temp); envstat->shutdown_cnt = 1; if (temperature_chamber == -1) temperature_chamber = check_for_chamber(); if ((temperature_chamber == 0) && enable_overtemp_powerdown) { /* * NOTE: The "%d seconds" is not * necessarily accurate in the case * where we have multiple boards * overheating and subsequently cooling * down. */ if (shutdown_msg == 0) { cmn_err(CE_WARN, "System " "shutdown scheduled " "in %d seconds due to " "over-temperature " "condition on %s", SHUTDOWN_TIMEOUT_SEC, buffer); } shutdown_msg++; } } /* * If this is a cpu board, power them off. */ if (temperature_chamber == 0) { mutex_enter(&cpu_lock); (void) fhc_board_poweroffcpus(board, NULL, CPU_FORCED); mutex_exit(&cpu_lock); } } else if (temp_state < envstat->state) { /* * Avert the sigpower that would * otherwise be sent to init. */ envstat->shutdown_cnt = 0; /* cooling down, use state counter */ if (envstat->temp_cnt == 0) { envstat->temp_cnt = TEMP_STATE_COUNT; } else if (--envstat->temp_cnt == 0) { if (temp_state == TEMP_WARN) { cmn_err(CE_NOTE, "%s is cooling " "(temperature: %dC)", buffer, real_temp); } else if (temp_state == TEMP_OK) { cmn_err(CE_NOTE, "%s has cooled down " "(temperature: %dC), system OK", buffer, real_temp); if (type == CLOCK_BOARD) { clear_fault(0, FT_OVERTEMP, FT_SYSTEM); } else { clear_fault(board, FT_OVERTEMP, FT_BOARD); } } /* * If we just came out of TEMP_DANGER, and * a warning was issued about shutting down, * let the user know it's been cancelled */ if (envstat->state == TEMP_DANGER && (temperature_chamber == 0) && enable_overtemp_powerdown && (powerdown_started == 0) && (--shutdown_msg == 0)) { cmn_err(CE_NOTE, "System " "shutdown due to over-" "temperature " "condition cancelled"); } envstat->state = temp_state; fhc_bd_update(board, SYSC_EVT_BD_TEMP_OK); } } } else { envstat->temp_cnt = 0; if (temp_state == TEMP_DANGER) { if (temperature_chamber == -1) { temperature_chamber = check_for_chamber(); } if ((envstat->shutdown_cnt++ >= SHUTDOWN_COUNT) && (temperature_chamber == 0) && enable_overtemp_powerdown && (powerdown_started == 0)) { powerdown_started = 1; /* the system is still too hot */ build_bd_display_str(buffer, softsp->list->sc.type, softsp->list->sc.board); cmn_err(CE_WARN, "%s still too hot " "(temperature: %dC)." " Overtemp shutdown started", buffer, real_temp); fhc_reboot(); } } } /* update the maximum and minimum temperatures if necessary */ if ((envstat->max == NA_TEMP) || (real_temp > envstat->max)) { envstat->max = real_temp; } if ((envstat->min == NA_TEMP) || (real_temp < envstat->min)) { envstat->min = real_temp; } /* * Update the temperature trend. Currently, the temperature * trend algorithm is based on the level 2 stats. So, we * only need to run every time the level 2 stats get updated. */ if (((tmp_index = L2_INDEX(index)) > 0) && (L2_REM(index) == 0)) { enum board_type type = softsp->list->sc.type; envstat->trend = temp_trend(envstat); /* Issue a warning if the temperature is rising rapidly. */ /* For CPU boards, don't warn if CPUs just powered on. */ if (envstat->trend == TREND_RAPID_RISE && (type != CPU_BOARD || real_temp > fhc_cpu_warning_temp_threshold)) { int board = softsp->list->sc.board; build_bd_display_str(buffer, type, board); cmn_err(CE_WARN, "%s temperature is rising rapidly! " "Current temperature is %dC", buffer, real_temp); } } } #define PREV_L2_INDEX(x) ((x) ? ((x) - 1) : (L2_SZ - 1)) /* * This routine determines if the temp of the device passed in is heating * up, cooling down, or staying stable. */ enum temp_trend temp_trend(struct temp_stats *tempstat) { int ii; uint_t curr_index; int curr_temp; uint_t prev_index; int prev_temp; int trail_temp; int delta; int read_cnt; enum temp_trend result = TREND_STABLE; if (tempstat == NULL) return (TREND_UNKNOWN); curr_index = (L2_INDEX(tempstat->index) - 1) % L2_SZ; curr_temp = tempstat->l2[curr_index]; /* Count how many temperature readings are available */ prev_index = curr_index; for (read_cnt = 0; read_cnt < L2_SZ - 1; read_cnt++) { if (tempstat->l2[prev_index] == NA_TEMP) break; prev_index = PREV_L2_INDEX(prev_index); } switch (read_cnt) { case 0: case 1: result = TREND_UNKNOWN; break; default: delta = curr_temp - tempstat->l2[PREV_L2_INDEX(curr_index)]; prev_index = curr_index; trail_temp = prev_temp = curr_temp; if (delta >= RAPID_RISE_THRESH) { /* rapid rise? */ result = TREND_RAPID_RISE; } else if (delta > 0) { /* rise? */ for (ii = 1; ii < read_cnt; ii++) { prev_index = PREV_L2_INDEX(prev_index); prev_temp = tempstat->l2[prev_index]; if (prev_temp > trail_temp) { break; } trail_temp = prev_temp; if (prev_temp <= curr_temp - NOISE_THRESH) { result = TREND_RISE; break; } } } else if (delta <= -RAPID_FALL_THRESH) { /* rapid fall? */ result = TREND_RAPID_FALL; } else if (delta < 0) { /* fall? */ for (ii = 1; ii < read_cnt; ii++) { prev_index = PREV_L2_INDEX(prev_index); prev_temp = tempstat->l2[prev_index]; if (prev_temp < trail_temp) { break; } trail_temp = prev_temp; if (prev_temp >= curr_temp + NOISE_THRESH) { result = TREND_FALL; break; } } } } return (result); } /* * Reboot the system if we can, otherwise attempt a power down */ void fhc_reboot(void) { proc_t *initpp; /* send a SIGPWR to init process */ mutex_enter(&pidlock); initpp = prfind(P_INITPID); mutex_exit(&pidlock); /* * If we're still booting and init(1) isn't * set up yet, simply halt. */ if (initpp != NULL) { psignal(initpp, SIGFPE); /* init 6 */ } else { power_down("Environmental Shutdown"); halt("Power off the System"); } } int overtemp_kstat_update(kstat_t *ksp, int rw) { struct temp_stats *tempstat; char *kstatp; int i; kstatp = (char *)ksp->ks_data; tempstat = (struct temp_stats *)ksp->ks_private; /* * Kstat reads are used to retrieve the current system temperature * history. Kstat writes are used to reset the max and min * temperatures. */ if (rw == KSTAT_WRITE) { short max; /* temporary copy of max temperature */ short min; /* temporary copy of min temperature */ /* * search for and reset the max and min to the current * array contents. Old max and min values will get * averaged out as they move into the higher level arrays. */ max = tempstat->l1[0]; min = tempstat->l1[0]; /* Pull the max and min from Level 1 array */ for (i = 0; i < L1_SZ; i++) { if ((tempstat->l1[i] != NA_TEMP) && (tempstat->l1[i] > max)) { max = tempstat->l1[i]; } if ((tempstat->l1[i] != NA_TEMP) && (tempstat->l1[i] < min)) { min = tempstat->l1[i]; } } /* Pull the max and min from Level 2 array */ for (i = 0; i < L2_SZ; i++) { if ((tempstat->l2[i] != NA_TEMP) && (tempstat->l2[i] > max)) { max = tempstat->l2[i]; } if ((tempstat->l2[i] != NA_TEMP) && (tempstat->l2[i] < min)) { min = tempstat->l2[i]; } } /* Pull the max and min from Level 3 array */ for (i = 0; i < L3_SZ; i++) { if ((tempstat->l3[i] != NA_TEMP) && (tempstat->l3[i] > max)) { max = tempstat->l3[i]; } if ((tempstat->l3[i] != NA_TEMP) && (tempstat->l3[i] < min)) { min = tempstat->l3[i]; } } /* Pull the max and min from Level 4 array */ for (i = 0; i < L4_SZ; i++) { if ((tempstat->l4[i] != NA_TEMP) && (tempstat->l4[i] > max)) { max = tempstat->l4[i]; } if ((tempstat->l4[i] != NA_TEMP) && (tempstat->l4[i] < min)) { min = tempstat->l4[i]; } } /* Pull the max and min from Level 5 array */ for (i = 0; i < L5_SZ; i++) { if ((tempstat->l5[i] != NA_TEMP) && (tempstat->l5[i] > max)) { max = tempstat->l5[i]; } if ((tempstat->l5[i] != NA_TEMP) && (tempstat->l5[i] < min)) { min = tempstat->l5[i]; } } } else { /* * copy the temperature history buffer into the * kstat structure. */ bcopy(tempstat, kstatp, sizeof (struct temp_stats)); } return (0); } int temp_override_kstat_update(kstat_t *ksp, int rw) { short *over; short *kstatp; kstatp = (short *)ksp->ks_data; over = (short *)ksp->ks_private; /* * Kstat reads are used to get the temperature override setting. * Kstat writes are used to set the temperature override setting. */ if (rw == KSTAT_WRITE) { *over = *kstatp; } else { *kstatp = *over; } return (0); } /* * This function uses the calibration tables at the beginning of this file * to lookup the actual temperature of the thermistor in degrees Celcius. * If the measurement is out of the bounds of the acceptable values, the * closest boundary value is used instead. */ static short calibrate_temp(enum board_type type, uchar_t temp, uint_t ac_comp) { short result = NA_TEMP; if (dont_calibrate == 1) { return ((short)temp); } switch (type) { case CPU_BOARD: /* * If AC chip revision is >= 4 or if it is unitialized, * then use the new calibration tables. */ if ((CHIP_REV(ac_comp) >= 4) || (CHIP_REV(ac_comp) == 0)) { if (temp >= CPU2_MX_CNT) { result = cpu2_table[CPU2_MX_CNT-1]; } else { result = cpu2_table[temp]; } } else { if (temp >= CPU_MX_CNT) { result = cpu_table[CPU_MX_CNT-1]; } else { result = cpu_table[temp]; } } break; case IO_2SBUS_BOARD: case IO_SBUS_FFB_BOARD: case IO_PCI_BOARD: case IO_2SBUS_SOCPLUS_BOARD: case IO_SBUS_FFB_SOCPLUS_BOARD: if (temp < IO_MN_CNT) { result = io_table[IO_MN_CNT]; } else if (temp >= IO_MX_CNT) { result = io_table[IO_MX_CNT-1]; } else { result = io_table[temp]; } break; case CLOCK_BOARD: if (temp < CLK_MN_CNT) { result = clock_table[CLK_MN_CNT]; } else if (temp >= CLK_MX_CNT) { result = clock_table[CLK_MX_CNT-1]; } else { result = clock_table[temp]; } break; default: break; } return (result); } /* * Determine the temperature state of this board based on its type and * the actual temperature in degrees Celcius. */ static enum temp_state get_temp_state(enum board_type type, short temp, int board) { enum temp_state state = TEMP_OK; short warn_limit; short danger_limit; struct cpu *cpa, *cpb; switch (type) { case CPU_BOARD: warn_limit = cpu_warn_temp; danger_limit = cpu_danger_temp; /* * For CPU boards with frequency >= 400 MHZ, * temperature zones are different. */ mutex_enter(&cpu_lock); if ((cpa = cpu_get(FHC_BOARD2CPU_A(board))) != NULL) { if ((cpa->cpu_type_info.pi_clock) >= 400) { warn_limit = cpu_warn_temp_4x; danger_limit = cpu_danger_temp_4x; } } if ((cpb = cpu_get(FHC_BOARD2CPU_B(board))) != NULL) { if ((cpb->cpu_type_info.pi_clock) >= 400) { warn_limit = cpu_warn_temp_4x; danger_limit = cpu_danger_temp_4x; } } mutex_exit(&cpu_lock); break; case IO_2SBUS_BOARD: case IO_SBUS_FFB_BOARD: case IO_PCI_BOARD: case IO_2SBUS_SOCPLUS_BOARD: case IO_SBUS_FFB_SOCPLUS_BOARD: warn_limit = io_warn_temp; danger_limit = io_danger_temp; break; case CLOCK_BOARD: warn_limit = clk_warn_temp; danger_limit = clk_danger_temp; break; case UNINIT_BOARD: case UNKNOWN_BOARD: case MEM_BOARD: default: warn_limit = dft_warn_temp; danger_limit = dft_danger_temp; break; } if (temp >= danger_limit) { state = TEMP_DANGER; } else if (temp >= warn_limit) { state = TEMP_WARN; } return (state); } static void fhc_add_kstats(struct fhc_soft_state *softsp) { struct kstat *fhc_ksp; struct fhc_kstat *fhc_named_ksp; if ((fhc_ksp = kstat_create("unix", softsp->list->sc.board, FHC_KSTAT_NAME, "misc", KSTAT_TYPE_NAMED, sizeof (struct fhc_kstat) / sizeof (kstat_named_t), KSTAT_FLAG_PERSISTENT)) == NULL) { cmn_err(CE_WARN, "fhc%d kstat_create failed", ddi_get_instance(softsp->dip)); return; } fhc_named_ksp = (struct fhc_kstat *)(fhc_ksp->ks_data); /* initialize the named kstats */ kstat_named_init(&fhc_named_ksp->csr, CSR_KSTAT_NAMED, KSTAT_DATA_UINT32); kstat_named_init(&fhc_named_ksp->bsr, BSR_KSTAT_NAMED, KSTAT_DATA_UINT32); fhc_ksp->ks_update = fhc_kstat_update; fhc_ksp->ks_private = (void *)softsp; softsp->fhc_ksp = fhc_ksp; kstat_install(fhc_ksp); } static int fhc_kstat_update(kstat_t *ksp, int rw) { struct fhc_kstat *fhcksp; struct fhc_soft_state *softsp; fhcksp = (struct fhc_kstat *)ksp->ks_data; softsp = (struct fhc_soft_state *)ksp->ks_private; /* this is a read-only kstat. Bail out on a write */ if (rw == KSTAT_WRITE) { return (EACCES); } else { /* * copy the current state of the hardware into the * kstat structure. */ fhcksp->csr.value.ui32 = *softsp->ctrl; fhcksp->bsr.value.ui32 = *softsp->bsr; } return (0); } static int cpu_on_board(int board) { int upa_a = board << 1; int upa_b = (board << 1) + 1; if ((cpunodes[upa_a].nodeid != 0) || (cpunodes[upa_b].nodeid != 0)) { return (1); } else { return (0); } } /* * This function uses the board list and toggles the OS green board * LED. The mask input tells which bit fields are being modified, * and the value input tells the states of the bits. */ void update_board_leds(fhc_bd_t *board, uint_t mask, uint_t value) { volatile uint_t temp; ASSERT(fhc_bdlist_locked()); /* mask off mask and value for only the LED bits */ mask &= (FHC_LED_LEFT|FHC_LED_MID|FHC_LED_RIGHT); value &= (FHC_LED_LEFT|FHC_LED_MID|FHC_LED_RIGHT); if (board != NULL) { mutex_enter(&board->softsp->ctrl_lock); /* read the current register state */ temp = *board->softsp->ctrl; /* * The EPDA bits are special since the register is * special. We don't want to set them, since setting * the bits on a shutdown cpu keeps the cpu permanently * powered off. Also, the CSR_SYNC bit must always be * set to 0 as it is an OBP semaphore that is expected to * be clear for cpu restart. */ temp &= ~(FHC_CSR_SYNC | FHC_EPDA_OFF | FHC_EPDB_OFF); /* mask off the bits to change */ temp &= ~mask; /* or in the new values of the bits. */ temp |= value; /* update the register */ *board->softsp->ctrl = temp; /* flush the hardware registers */ temp = *board->softsp->ctrl; #ifdef lint temp = temp; #endif mutex_exit(&board->softsp->ctrl_lock); } } static int check_for_chamber(void) { int chamber = 0; dev_info_t *options_dip; pnode_t options_node_id; int mfgmode_len; int retval; char *mfgmode; /* * The operator can disable overtemp powerdown from /etc/system or * boot -h. */ if (!enable_overtemp_powerdown) { cmn_err(CE_WARN, "Operator has disabled overtemp powerdown"); return (1); } /* * An OBP option, 'mfg-mode' is being used to inform us as to * whether we are in an enviromental chamber. It exists in * the 'options' node. This is where all OBP 'setenv' (eeprom) * parameters live. */ if ((options_dip = ddi_find_devinfo("options", -1, 0)) != NULL) { options_node_id = (pnode_t)ddi_get_nodeid(options_dip); mfgmode_len = prom_getproplen(options_node_id, "mfg-mode"); if (mfgmode_len == -1) { return (chamber); } mfgmode = kmem_alloc(mfgmode_len+1, KM_SLEEP); retval = prom_getprop(options_node_id, "mfg-mode", mfgmode); if (retval != -1) { mfgmode[retval] = 0; if (strcmp(mfgmode, CHAMBER_VALUE) == 0) { chamber = 1; cmn_err(CE_WARN, "System in Temperature" " Chamber Mode. Overtemperature" " Shutdown disabled"); } } kmem_free(mfgmode, mfgmode_len+1); } return (chamber); } static void build_bd_display_str(char *buffer, enum board_type type, int board) { if (buffer == NULL) { return; } /* fill in board type to display */ switch (type) { case UNINIT_BOARD: (void) sprintf(buffer, "Uninitialized Board type board %d", board); break; case UNKNOWN_BOARD: (void) sprintf(buffer, "Unknown Board type board %d", board); break; case CPU_BOARD: case MEM_BOARD: (void) sprintf(buffer, "CPU/Memory board %d", board); break; case IO_2SBUS_BOARD: (void) sprintf(buffer, "2 SBus IO board %d", board); break; case IO_SBUS_FFB_BOARD: (void) sprintf(buffer, "SBus FFB IO board %d", board); break; case IO_PCI_BOARD: (void) sprintf(buffer, "PCI IO board %d", board); break; case CLOCK_BOARD: (void) sprintf(buffer, "Clock board"); break; case IO_2SBUS_SOCPLUS_BOARD: (void) sprintf(buffer, "2 SBus SOC+ IO board %d", board); break; case IO_SBUS_FFB_SOCPLUS_BOARD: (void) sprintf(buffer, "SBus FFB SOC+ IO board %d", board); break; default: (void) sprintf(buffer, "Unrecognized board type board %d", board); break; } } void fhc_intrdist(void *arg) { struct fhc_soft_state *softsp; dev_info_t *dip = (dev_info_t *)arg; volatile uint_t *mondo_vec_reg; volatile uint_t *intr_state_reg; uint_t mondo_vec; uint_t tmp_reg; uint_t cpu_id; uint_t i; /* extract the soft state pointer */ softsp = ddi_get_soft_state(fhcp, ddi_get_instance(dip)); /* * Loop through all the interrupt mapping registers and reprogram * the target CPU for all valid registers. */ for (i = 0; i < FHC_MAX_INO; i++) { mondo_vec_reg = softsp->intr_regs[i].mapping_reg; intr_state_reg = softsp->intr_regs[i].clear_reg; if ((*mondo_vec_reg & IMR_VALID) == 0) continue; cpu_id = intr_dist_cpuid(); /* Check the current target of the mondo */ if (((*mondo_vec_reg & INR_PID_MASK) >> INR_PID_SHIFT) == cpu_id) { /* It is the same, don't reprogram */ return; } /* So it's OK to reprogram the CPU target */ /* turn off the valid bit */ *mondo_vec_reg &= ~IMR_VALID; /* flush the hardware registers */ tmp_reg = *softsp->id; /* * wait for the state machine to idle. Do not loop on panic, so * that system does not hang. */ while (((*intr_state_reg & INT_PENDING) == INT_PENDING) && !panicstr) ; /* re-target the mondo and turn it on */ mondo_vec = (cpu_id << INR_PID_SHIFT) | IMR_VALID; /* write it back to the hardware. */ *mondo_vec_reg = mondo_vec; /* flush the hardware buffers. */ tmp_reg = *(softsp->id); #ifdef lint tmp_reg = tmp_reg; #endif /* lint */ } } /* * reg_fault * * This routine registers a fault in the fault list. If the fault * is unique (does not exist in fault list) then a new fault is * added to the fault list, with the appropriate structure elements * filled in. */ void reg_fault(int unit, enum ft_type type, enum ft_class fclass) { struct ft_link_list *list; /* temporary list pointer */ if (type >= ft_max_index) { cmn_err(CE_WARN, "Illegal Fault type %x", type); return; } mutex_enter(&ftlist_mutex); /* Search for the requested fault. If it already exists, return. */ for (list = ft_list; list != NULL; list = list->next) { if ((list->f.unit == unit) && (list->f.type == type) && (list->f.fclass == fclass)) { mutex_exit(&ftlist_mutex); return; } } /* Allocate a new fault structure. */ list = kmem_zalloc(sizeof (struct ft_link_list), KM_SLEEP); /* fill in the fault list elements */ list->f.unit = unit; list->f.type = type; list->f.fclass = fclass; list->f.create_time = (time32_t)gethrestime_sec(); /* XX64 */ (void) strncpy(list->f.msg, ft_str_table[type], MAX_FT_DESC); /* link it into the list. */ list->next = ft_list; ft_list = list; /* Update the total fault count */ ft_nfaults++; mutex_exit(&ftlist_mutex); } /* * clear_fault * * This routine finds the fault list entry specified by the caller, * deletes it from the fault list, and frees up the memory used for * the entry. If the requested fault is not found, it exits silently. */ void clear_fault(int unit, enum ft_type type, enum ft_class fclass) { struct ft_link_list *list; /* temporary list pointer */ struct ft_link_list **vect; mutex_enter(&ftlist_mutex); list = ft_list; vect = &ft_list; /* * Search for the requested fault. If it exists, delete it * and relink the fault list. */ for (; list != NULL; vect = &list->next, list = list->next) { if ((list->f.unit == unit) && (list->f.type == type) && (list->f.fclass == fclass)) { /* remove the item from the list */ *vect = list->next; /* free the memory allocated */ kmem_free(list, sizeof (struct ft_link_list)); /* Update the total fault count */ ft_nfaults--; break; } } mutex_exit(&ftlist_mutex); } /* * process_fault_list * * This routine walks the global fault list and updates the board list * with the current status of each Yellow LED. If any faults are found * in the system, then a non-zero value is returned. Else zero is returned. */ int process_fault_list(void) { int fault = 0; struct ft_link_list *ftlist; /* fault list pointer */ fhc_bd_t *bdlist; /* board list pointer */ /* * Note on locking. The bdlist mutex is always acquired and * held around the ftlist mutex when both are needed for an * operation. This is to avoid deadlock. */ /* First lock the board list */ (void) fhc_bdlist_lock(-1); /* Grab the fault list lock first */ mutex_enter(&ftlist_mutex); /* clear the board list of all faults first */ for (bdlist = fhc_bd_first(); bdlist; bdlist = fhc_bd_next(bdlist)) bdlist->fault = 0; /* walk the fault list here */ for (ftlist = ft_list; ftlist != NULL; ftlist = ftlist->next) { fault++; /* * If this is a board level fault, find the board, The * unit number for all board class faults must be the * actual board number. The caller of reg_fault must * ensure this for FT_BOARD class faults. */ if (ftlist->f.fclass == FT_BOARD) { /* Sanity check the board first */ if (fhc_bd_valid(ftlist->f.unit)) { bdlist = fhc_bd(ftlist->f.unit); bdlist->fault = 1; } else { cmn_err(CE_WARN, "No board %d list entry found", ftlist->f.unit); } } } /* now unlock the fault list */ mutex_exit(&ftlist_mutex); /* unlock the board list before leaving */ fhc_bdlist_unlock(); return (fault); } /* * Add a new memloc to the database (and keep 'em sorted by PA) */ void fhc_add_memloc(int board, uint64_t pa, uint_t size) { struct fhc_memloc *p, **pp; uint_t ipa = pa >> FHC_MEMLOC_SHIFT; ASSERT(fhc_bdlist_locked()); ASSERT((size & (size-1)) == 0); /* size must be power of 2 */ /* look for a comparable memloc (as long as new PA smaller) */ for (p = fhc_base_memloc, pp = &fhc_base_memloc; p != NULL; pp = &p->next, p = p->next) { /* have we passed our place in the sort? */ if (ipa < p->pa) { break; } } p = kmem_alloc(sizeof (struct fhc_memloc), KM_SLEEP); p->next = *pp; p->board = board; p->pa = ipa; p->size = size; #ifdef DEBUG_MEMDEC cmn_err(CE_NOTE, "fhc_add_memloc: adding %d 0x%x 0x%x", p->board, p->pa, p->size); #endif /* DEBUG_MEMDEC */ *pp = p; } /* * Delete all memloc records for a board from the database */ void fhc_del_memloc(int board) { struct fhc_memloc *p, **pp; ASSERT(fhc_bdlist_locked()); /* delete all entries that match board */ pp = &fhc_base_memloc; while ((p = *pp) != NULL) { if (p->board == board) { #ifdef DEBUG_MEMDEC cmn_err(CE_NOTE, "fhc_del_memloc: removing %d " "0x%x 0x%x", board, p->pa, p->size); #endif /* DEBUG_MEMDEC */ *pp = p->next; kmem_free(p, sizeof (struct fhc_memloc)); } else { pp = &(p->next); } } } /* * Find a physical address range of sufficient size and return a starting PA */ uint64_t fhc_find_memloc_gap(uint_t size) { struct fhc_memloc *p; uint_t base_pa = 0; uint_t mask = ~(size-1); ASSERT(fhc_bdlist_locked()); ASSERT((size & (size-1)) == 0); /* size must be power of 2 */ /* * walk the list of known memlocs and measure the 'gaps'. * we will need a hole that can align the 'size' requested. * (e.g. a 256mb bank needs to be on a 256mb boundary). */ for (p = fhc_base_memloc; p != NULL; p = p->next) { if (base_pa != (base_pa & mask)) base_pa = (base_pa + size) & mask; if (base_pa + size <= p->pa) break; base_pa = p->pa + p->size; } /* * At this point, we assume that base_pa is good enough. */ ASSERT((base_pa + size) <= FHC_MEMLOC_MAX); if (base_pa != (base_pa & mask)) base_pa = (base_pa + size) & mask; /* align */ return ((uint64_t)base_pa << FHC_MEMLOC_SHIFT); } /* * This simple function to write the MCRs can only be used when * the contents of memory are not valid as there is a bug in the AC * ASIC concerning refresh. */ static void fhc_write_mcrs( uint64_t cpa, uint64_t dpa0, uint64_t dpa1, uint64_t c, uint64_t d0, uint64_t d1) { stdphysio(cpa, c & ~AC_CSR_REFEN); (void) lddphysio(cpa); if (GRP_SIZE_IS_SET(d0)) { stdphysio(dpa0, d0); (void) lddphysio(dpa0); } if (GRP_SIZE_IS_SET(d1)) { stdphysio(dpa1, d1); (void) lddphysio(dpa1); } stdphysio(cpa, c); (void) lddphysio(cpa); } /* compute the appropriate RASIZE for bank size */ static uint_t fhc_cvt_size(uint64_t bsz) { uint_t csz; csz = 0; bsz /= 64; while (bsz) { csz++; bsz /= 2; } csz /= 2; return (csz); } void fhc_program_memory(int board, uint64_t pa) { uint64_t cpa, dpa0, dpa1; uint64_t c, d0, d1; uint64_t b0_pa, b1_pa; uint64_t memdec0, memdec1; uint_t b0_size, b1_size; /* XXX gross hack to get to board via board number */ cpa = 0x1c0f9000060ull + (board * 0x400000000ull); #ifdef DEBUG_MEMDEC prom_printf("cpa = 0x%llx\n", cpa); #endif /* DEBUG_MEMDEC */ dpa0 = cpa + 0x10; dpa1 = cpa + 0x20; /* assume size is set by connect */ memdec0 = lddphysio(dpa0); #ifdef DEBUG_MEMDEC prom_printf("memdec0 = 0x%llx\n", memdec0); #endif /* DEBUG_MEMDEC */ memdec1 = lddphysio(dpa1); #ifdef DEBUG_MEMDEC prom_printf("memdec1 = 0x%llx\n", memdec1); #endif /* DEBUG_MEMDEC */ if (GRP_SIZE_IS_SET(memdec0)) { b0_size = GRP_SPANMB(memdec0); } else { b0_size = 0; } if (GRP_SIZE_IS_SET(memdec1)) { b1_size = GRP_SPANMB(memdec1); } else { b1_size = 0; } c = lddphysio(cpa); #ifdef DEBUG_MEMDEC prom_printf("c = 0x%llx\n", c); #endif /* DEBUG_MEMDEC */ if (b0_size) { b0_pa = pa; d0 = SETUP_DECODE(b0_pa, b0_size, 0, 0); d0 |= AC_MEM_VALID; c &= ~0x7; c |= 0; c &= ~(0x7 << 8); c |= (fhc_cvt_size(b0_size) << 8); /* match row size */ } else { d0 = memdec0; } if (b1_size) { b1_pa = pa + 0x80000000ull; /* XXX 2gb */ d1 = SETUP_DECODE(b1_pa, b1_size, 0, 0); d1 |= AC_MEM_VALID; c &= ~(0x7 << 3); c |= (0 << 3); c &= ~(0x7 << 11); c |= (fhc_cvt_size(b1_size) << 11); /* match row size */ } else { d1 = memdec1; } #ifdef DEBUG_MEMDEC prom_printf("c 0x%llx, d0 0x%llx, d1 0x%llx\n", c, d0, d1); #endif /* DEBUG_MEMDEC */ fhc_write_mcrs(cpa, dpa0, dpa1, c, d0, d1); } /* * Creates a variable sized virtual kstat with a snapshot routine in order * to pass the linked list fault list up to userland. Also creates a * virtual kstat to pass up the string table for faults. */ void create_ft_kstats(int instance) { struct kstat *ksp; ksp = kstat_create("unix", instance, FT_LIST_KSTAT_NAME, "misc", KSTAT_TYPE_RAW, 1, KSTAT_FLAG_VIRTUAL|KSTAT_FLAG_VAR_SIZE); if (ksp != NULL) { ksp->ks_data = NULL; ksp->ks_update = ft_ks_update; ksp->ks_snapshot = ft_ks_snapshot; ksp->ks_data_size = 1; ksp->ks_lock = &ftlist_mutex; kstat_install(ksp); } } /* * This routine creates a snapshot of all the fault list data. It is * called by the kstat framework when a kstat read is done. */ static int ft_ks_snapshot(struct kstat *ksp, void *buf, int rw) { struct ft_link_list *ftlist; if (rw == KSTAT_WRITE) { return (EACCES); } ksp->ks_snaptime = gethrtime(); for (ftlist = ft_list; ftlist != NULL; ftlist = ftlist->next) { bcopy(&ftlist->f, buf, sizeof (struct ft_list)); buf = ((struct ft_list *)buf) + 1; } return (0); } /* * Setup the kstat data size for the kstat framework. This is used in * conjunction with the ks_snapshot routine. This routine sets the size, * the kstat framework allocates the memory, and ks_shapshot does the * data transfer. */ static int ft_ks_update(struct kstat *ksp, int rw) { if (rw == KSTAT_WRITE) { return (EACCES); } else { if (ft_nfaults) { ksp->ks_data_size = ft_nfaults * sizeof (struct ft_list); } else { ksp->ks_data_size = 1; } } return (0); } /* * Power off any cpus on the board. */ int fhc_board_poweroffcpus(int board, char *errbuf, int cpu_flags) { cpu_t *cpa, *cpb; enum board_type type; int error = 0; ASSERT(MUTEX_HELD(&cpu_lock)); /* * what type of board are we dealing with? */ type = fhc_bd_type(board); switch (type) { case CPU_BOARD: /* * the shutdown sequence will be: * * idle both cpus then shut them off. * it looks like the hardware gets corrupted if one * cpu is busy while the other is shutting down... */ if ((cpa = cpu_get(FHC_BOARD2CPU_A(board))) != NULL && cpu_is_active(cpa)) { if (!cpu_intr_on(cpa)) { cpu_intr_enable(cpa); } if ((error = cpu_offline(cpa, cpu_flags)) != 0) { cmn_err(CE_WARN, "Processor %d failed to offline.", cpa->cpu_id); if (errbuf != NULL) { (void) snprintf(errbuf, SYSC_OUTPUT_LEN, "processor %d failed to offline", cpa->cpu_id); } } } if (error == 0 && (cpb = cpu_get(FHC_BOARD2CPU_B(board))) != NULL && cpu_is_active(cpb)) { if (!cpu_intr_on(cpb)) { cpu_intr_enable(cpb); } if ((error = cpu_offline(cpb, cpu_flags)) != 0) { cmn_err(CE_WARN, "Processor %d failed to offline.", cpb->cpu_id); if (errbuf != NULL) { (void) snprintf(errbuf, SYSC_OUTPUT_LEN, "processor %d failed to offline", cpb->cpu_id); } } } if (error == 0 && cpa != NULL && cpu_is_offline(cpa)) { if ((error = cpu_poweroff(cpa)) != 0) { cmn_err(CE_WARN, "Processor %d failed to power off.", cpa->cpu_id); if (errbuf != NULL) { (void) snprintf(errbuf, SYSC_OUTPUT_LEN, "processor %d failed to power off", cpa->cpu_id); } } else { cmn_err(CE_NOTE, "Processor %d powered off.", cpa->cpu_id); } } if (error == 0 && cpb != NULL && cpu_is_offline(cpb)) { if ((error = cpu_poweroff(cpb)) != 0) { cmn_err(CE_WARN, "Processor %d failed to power off.", cpb->cpu_id); if (errbuf != NULL) { (void) snprintf(errbuf, SYSC_OUTPUT_LEN, "processor %d failed to power off", cpb->cpu_id); } } else { cmn_err(CE_NOTE, "Processor %d powered off.", cpb->cpu_id); } } /* * If all the shutdowns completed, ONLY THEN, clear the * incorrectly valid dtags... * * IMPORTANT: it is an error to read or write dtags while * they are 'active' */ if (error == 0 && (cpa != NULL || cpb != NULL)) { u_longlong_t base = 0; int i; #ifdef DEBUG int nonz0 = 0; int nonz1 = 0; #endif if (cpa != NULL) base = FHC_DTAG_BASE(cpa->cpu_id); if (cpb != NULL) base = FHC_DTAG_BASE(cpb->cpu_id); ASSERT(base != 0); for (i = 0; i < FHC_DTAG_SIZE; i += FHC_DTAG_SKIP) { u_longlong_t value = lddphysio(base+i); #ifdef lint value = value; #endif #ifdef DEBUG if (cpa != NULL && (value & FHC_DTAG_LOW)) nonz0++; if (cpb != NULL && (value & FHC_DTAG_HIGH)) nonz1++; #endif /* always clear the dtags */ stdphysio(base + i, 0ull); } #ifdef DEBUG if (nonz0 || nonz1) { cmn_err(CE_NOTE, "!dtag results: " "cpua valid %d, cpub valid %d", nonz0, nonz1); } #endif } break; default: break; } return (error); } /* * platform code for shutting down cpus. */ int fhc_cpu_poweroff(struct cpu *cp) { int board; fhc_bd_t *bd_list; int delays; extern void idle_stop_xcall(void); ASSERT(MUTEX_HELD(&cpu_lock)); ASSERT((cp->cpu_flags & (CPU_EXISTS | CPU_OFFLINE | CPU_QUIESCED)) == (CPU_EXISTS | CPU_OFFLINE | CPU_QUIESCED)); /* * Lock the board so that we can safely access the * registers. This cannot be done inside the pause_cpus(). */ board = FHC_CPU2BOARD(cp->cpu_id); bd_list = fhc_bdlist_lock(board); ASSERT(fhc_bd_valid(board) && (bd_list->sc.type == CPU_BOARD)); /* * Capture all CPUs (except for detaching proc) to prevent * crosscalls to the detaching proc until it has cleared its * bit in cpu_ready_set. * * The CPU's remain paused and the prom_mutex is known to be free. * This prevents the x-trap victim from blocking when doing prom * IEEE-1275 calls at a high PIL level. */ promsafe_pause_cpus(); /* * Quiesce interrupts on the target CPU. We do this by setting * the CPU 'not ready'- (i.e. removing the CPU from cpu_ready_set) to * prevent it from receiving cross calls and cross traps. * This prevents the processor from receiving any new soft interrupts. */ mp_cpu_quiesce(cp); xt_one_unchecked(cp->cpu_id, (xcfunc_t *)idle_stop_xcall, (uint64_t)fhc_cpu_shutdown_self, (uint64_t)NULL); /* * Wait for slave cpu to shutdown. * Sense this by watching the hardware EPDx bit. */ for (delays = FHC_SHUTDOWN_WAIT_MSEC; delays != 0; delays--) { uint_t temp; DELAY(1000); /* get the current cpu power status */ temp = *bd_list->softsp->ctrl; /* has the cpu actually signalled shutdown? */ if (FHC_CPU_IS_A(cp->cpu_id)) { if (temp & FHC_EPDA_OFF) break; } else { if (temp & FHC_EPDB_OFF) break; } } start_cpus(); fhc_bdlist_unlock(); /* A timeout means we've lost control of the cpu. */ if (delays == 0) panic("Processor %d failed during shutdown", cp->cpu_id); return (0); } /* * shutdown_self * slave side shutdown. clean up and execute the shutdown sequence. */ static void fhc_cpu_shutdown_self(void) { extern void flush_windows(void); flush_windows(); ASSERT(CPU->cpu_intr_actv == 0); ASSERT(CPU->cpu_thread == CPU->cpu_idle_thread || CPU->cpu_thread == CPU->cpu_startup_thread); CPU->cpu_flags = CPU_POWEROFF | CPU_OFFLINE | CPU_QUIESCED; (void) prom_sunfire_cpu_off(); /* inform Ultra Enterprise prom */ os_completes_shutdown(); panic("fhc_cpu_shutdown_self: cannot return"); /*NOTREACHED*/ } /* * Warm start CPU. */ static int fhc_cpu_start(struct cpu *cp) { int rv; int cpuid = cp->cpu_id; pnode_t nodeid; extern void restart_other_cpu(int); ASSERT(MUTEX_HELD(&cpu_lock)); /* power on cpu */ nodeid = cpunodes[cpuid].nodeid; ASSERT(nodeid != (pnode_t)0); rv = prom_wakeupcpu(nodeid); if (rv != 0) { cmn_err(CE_WARN, "Processor %d failed to power on.", cpuid); return (EBUSY); } cp->cpu_flags &= ~CPU_POWEROFF; /* * NOTE: restart_other_cpu pauses cpus during the slave cpu start. * This helps to quiesce the bus traffic a bit which makes * the tick sync routine in the prom more robust. */ restart_other_cpu(cpuid); return (0); } /* * Power on CPU. */ int fhc_cpu_poweron(struct cpu *cp) { fhc_bd_t *bd_list; enum temp_state state; int board; int status; int status_other; struct cpu *cp_other; ASSERT(MUTEX_HELD(&cpu_lock)); ASSERT(cpu_is_poweredoff(cp)); /* do not power on overtemperature cpu */ board = FHC_CPU2BOARD(cp->cpu_id); bd_list = fhc_bdlist_lock(board); ASSERT(bd_list != NULL); ASSERT(bd_list->sc.type == CPU_BOARD); ASSERT(bd_list->dev_softsp != NULL); state = ((struct environ_soft_state *) bd_list->dev_softsp)->tempstat.state; fhc_bdlist_unlock(); if ((state == TEMP_WARN) || (state == TEMP_DANGER)) return (EBUSY); status = fhc_cpu_start(cp); /* policy for dual cpu boards */ if ((status == 0) && ((cp_other = cpu_get(FHC_OTHER_CPU_ID(cp->cpu_id))) != NULL)) { /* * Do not leave board's other cpu idling in the prom. * Start the other cpu and set its state to P_OFFLINE. */ status_other = fhc_cpu_start(cp_other); if (status_other != 0) { panic("fhc: failed to start second CPU" " in pair %d & %d, error %d", cp->cpu_id, cp_other->cpu_id, status_other); } } return (status); } /* * complete the shutdown sequence in case the firmware doesn't. * * If the firmware returns, then complete the shutdown code. * (sunfire firmware presently only updates its status. the * OS must flush the D-tags and execute the shutdown instruction.) */ static void os_completes_shutdown(void) { pfn_t pfn; tte_t tte; volatile uint_t *src; volatile uint_t *dst; caddr_t copy_addr; extern void fhc_shutdown_asm(u_longlong_t, int); extern void fhc_shutdown_asm_end(void); copy_addr = shutdown_va + FHC_SRAM_OS_OFFSET; /* compute sram global address for this operation */ pfn = FHC_LOCAL_OS_PAGEBASE >> MMU_PAGESHIFT; /* force load i and d translations */ tte.tte_inthi = TTE_VALID_INT | TTE_SZ_INT(TTE8K) | TTE_PFN_INTHI(pfn); tte.tte_intlo = TTE_PFN_INTLO(pfn) | TTE_HWWR_INT | TTE_PRIV_INT | TTE_LCK_INT; /* un$ */ sfmmu_dtlb_ld_kva(shutdown_va, &tte); /* load dtlb */ sfmmu_itlb_ld_kva(shutdown_va, &tte); /* load itlb */ /* * copy the special shutdown function to sram * (this is a special integer copy that synchronizes with localspace * accesses. we need special throttling to ensure copy integrity) */ for (src = (uint_t *)fhc_shutdown_asm, dst = (uint_t *)copy_addr; src < (uint_t *)fhc_shutdown_asm_end; src++, dst++) { volatile uint_t dummy; *dst = *src; /* * ensure non corrupting single write operations to * localspace sram by interleaving reads with writes. */ dummy = *dst; #ifdef lint dummy = dummy; #endif } /* * Call the shutdown sequencer. * NOTE: the base flush address must be unique for each MID. */ ((void (*)(u_longlong_t, int))copy_addr)( FHC_BASE_NOMEM + CPU->cpu_id * FHC_MAX_ECACHE_SIZE, cpunodes[CPU->cpu_id].ecache_size); } enum temp_state fhc_env_temp_state(int board) { fhc_bd_t *bdp; struct environ_soft_state *envp; ASSERT(fhc_bd_valid(board)); bdp = fhc_bd(board); /* * Due to asynchronous attach of environ, environ may * not be attached by the time we start calling this routine * to check the temperature state. Environ not attaching is * pathological so this will only cover the time between * board connect and environ attach. */ if (!bdp->dev_softsp) { return (TEMP_OK); } envp = (struct environ_soft_state *)bdp->dev_softsp; return (envp->tempstat.state); } static void fhc_tod_fault(enum tod_fault_type tod_bad) { int board_num = 0; enum ft_class class = FT_SYSTEM; uint64_t addr; addr = (va_to_pa((void *)v_eeprom_addr)) >> BOARD_PHYADDR_SHIFT; if ((addr & CLOCKBOARD_PHYADDR_BITS) != CLOCKBOARD_PHYADDR_BITS) { /* if tod is not on clock board, */ /* it'd be on one of io boards */ board_num = (addr >> IO_BOARD_NUMBER_SHIFT) & IO_BOARD_NUMBER_MASK; class = FT_BOARD; } switch (tod_bad) { case TOD_NOFAULT: clear_fault(board_num, FT_TODFAULT, class); break; case TOD_REVERSED: case TOD_STALLED: case TOD_JUMPED: case TOD_RATECHANGED: reg_fault(board_num, FT_TODFAULT, class); break; default: break; } }