/* * 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 extern int plat_max_mc_units_per_board(void); extern int plat_ecc_dispatch_task(plat_ecc_message_t *); /* Platform specific function to get DIMM offset information */ int (*p2get_mem_offset)(uint64_t, uint64_t *); /* Platform specific function to get dimm serial id information */ int (*p2get_mem_sid)(int, int, char *, int, int *); /* * Platform specific function to convert a DIMM location/serial id and * offset into a physical address. */ int (*p2get_mem_addr)(int, char *, uint64_t, uint64_t *); /* * Timeouts variable for determining when to give up waiting for a * response from the SC. The value is in seconds and the default is * based on the current default mailbox timeout used for Serengeti * mailbox requests which is 30 seconds (Starcat uses a smaller value). */ int plat_dimm_req_timeout = 30; int plat_dimm_req_min_timeout = 6; /* Number of times to retries DIMM serial id requests */ int plat_dimm_req_max_retries = 1; static void plat_request_all_mem_sids(uint32_t); int plat_get_mem_sid(char *unum, char *buf, int buflen, int *lenp) { int board, pos, bank, dimm, jnumber; int mcid; if (p2get_mem_sid == NULL || (plat_ecc_capability_sc_get(PLAT_ECC_DIMM_SID_MESSAGE) == 0)) return (ENOTSUP); if (parse_unum_memory(unum, &board, &pos, &bank, &dimm, &jnumber) != 0) return (EINVAL); if (dimm < 0) return (EINVAL); mcid = plat_make_fru_cpuid(board, 0, pos); dimm += (bank * 4); /* convert dimm from 0-3 to 0-7 value */ return (p2get_mem_sid(mcid, dimm, buf, buflen, lenp)); } int plat_get_mem_offset(uint64_t paddr, uint64_t *offp) { if (p2get_mem_offset != NULL) { return (p2get_mem_offset(paddr, offp)); } else return (ENOTSUP); } int plat_get_mem_addr(char *unum, char *sid, uint64_t offset, uint64_t *addrp) { int board, pos, bank, dimm, jnumber; int mcid; if (p2get_mem_addr == NULL || (plat_ecc_capability_sc_get(PLAT_ECC_DIMM_SID_MESSAGE) == 0)) return (ENOTSUP); if (parse_unum_memory(unum, &board, &pos, &bank, &dimm, &jnumber) != 0) return (EINVAL); mcid = plat_make_fru_cpuid(board, 0, pos); return (p2get_mem_addr(mcid, sid, offset, addrp)); } dimm_sid_cache_t * plat_alloc_sid_cache(int *max_entries) { dimm_sid_cache_t *cache; int i, bd, p; int max_mc_per_bd = plat_max_mc_units_per_board(); *max_entries = plat_max_cpumem_boards() * max_mc_per_bd; cache = (dimm_sid_cache_t *)kmem_zalloc(sizeof (dimm_sid_cache_t) * *max_entries, KM_SLEEP); for (i = 0; i < *max_entries; i++) { bd = i / max_mc_per_bd; p = i % max_mc_per_bd; cache[i].mcid = plat_make_fru_cpuid(bd, 0, p); } return (cache); } static void plat_populate_sid_cache_one(dimm_sid_cache_t *cache, int bd) { int i, j; uint8_t valid; dimm_sid_t *dimmsidsp; int max_mc_per_bd = plat_max_mc_units_per_board(); /* * There must be at least one dimm on the board for this * code to be called. */ ASSERT(domain_dimm_sids[bd].pdsb_valid_bitmap); for (i = 0; i < max_mc_per_bd; i++) { int index = bd * max_mc_per_bd + i; /* * Each entry in the cache represents one mc. * If state is not MC_DIMM_SIDS_REQUESTED, then that mc * either has no DIMMs, is not present, or already has * DIMM serial ids available from a previous call to this * function. */ if (cache[index].state != MC_DIMM_SIDS_REQUESTED) continue; valid = domain_dimm_sids[bd].pdsb_valid_bitmap >> (i * 8) & 0xff; dimmsidsp = cache[index].sids; /* * Copy the valid DIMM serial ids. Each mc can have up to * eight DIMMs. */ for (j = 0; j < 8; j++) { if (((1 << j) & valid) == 0) continue; (void) strncpy(dimmsidsp[j], domain_dimm_sids[bd].pdsb_dimm_sids[(i * 8) + j], PLAT_MAX_DIMM_SID_LEN); } cache[index].state = MC_DIMM_SIDS_AVAILABLE; } } int plat_populate_sid_cache(dimm_sid_cache_t *cache, int max_entries) { int i; int bd; uint32_t bds = 0, retry_bds = 0; int max_mc_per_bd = plat_max_mc_units_per_board(); clock_t start_lbolt, current_lbolt; ulong_t elapsed_sec; int max_retries = plat_dimm_req_max_retries; for (i = 0; i < max_entries; i++) { if (cache[i].state == MC_DIMM_SIDS_REQUESTED) { bd = i / max_mc_per_bd; bds |= (1 << bd); } } retry: plat_request_all_mem_sids(bds); /* * Wait for mailbox messages from SC. * Keep track of elapsed time in order to avoid getting * stuck here if something is wrong with the SC. */ if (plat_dimm_req_timeout < plat_dimm_req_min_timeout) { cmn_err(CE_WARN, "plat_dimm_req_timeout (%d secs) is less " "than the minimum value (%d secs). Resetting to " "minimum.", plat_dimm_req_timeout, plat_dimm_req_min_timeout); plat_dimm_req_timeout = plat_dimm_req_min_timeout; } start_lbolt = ddi_get_lbolt(); while (bds) { for (bd = 0; bd < plat_max_cpumem_boards(); bd++) { if (((1 << bd) & bds) == 0) continue; switch (domain_dimm_sids[bd].pdsb_state) { case PDSB_STATE_STORE_IN_PROGRESS: /* Check elapsed time for possible timeout. */ current_lbolt = ddi_get_lbolt(); elapsed_sec = TICK_TO_SEC(current_lbolt - start_lbolt); if (elapsed_sec > plat_dimm_req_timeout) { mutex_enter(&domain_dimm_sids[bd]. pdsb_lock); domain_dimm_sids[bd].pdsb_state = PDSB_STATE_FAILED_TO_STORE; mutex_exit(&domain_dimm_sids[bd]. pdsb_lock); } continue; case PDSB_STATE_FAILED_TO_STORE: /* Record board# for possible retry */ retry_bds |= (1 << bd); break; case PDSB_STATE_STORED: /* Success! */ plat_populate_sid_cache_one(cache, bd); break; default: cmn_err(CE_PANIC, "Unknown state (0x%x) for " "domain_dimm_sids[%d]", domain_dimm_sids[bd].pdsb_state, bd); } bds &= ~(1 << bd); } /* * If there are still outstanding requests, delay for one half * second to avoid excessive busy waiting. */ if (bds != 0) delay(drv_usectohz(500000)); } if (max_retries-- && retry_bds) { bds = retry_bds; retry_bds = 0; goto retry; } else if (!max_retries && retry_bds) { cmn_err(CE_WARN, "!Unable to retrieve DIMM serial ids for " "boards 0x%x", retry_bds); return (ETIMEDOUT); } return (0); } /* * Functions for requesting DIMM serial id information from the SC and * updating and storing it on the domain for use by the Memory Controller * driver. */ /* * Adds DIMM serial id data received from the SC to the domain_dimm_sids[] * array. Called by the Serengeti and Starcat mailbox code that handles the * reply message from the SC containing a plat_dimm_sid_board_data_t. */ int plat_store_mem_sids(plat_dimm_sid_board_data_t *data) { int bd; int i; bd = data->pdsbd_board_num; mutex_enter(&domain_dimm_sids[bd].pdsb_lock); if (data->pdsbd_errno) { domain_dimm_sids[bd].pdsb_state = PDSB_STATE_FAILED_TO_STORE; mutex_exit(&domain_dimm_sids[bd].pdsb_lock); cmn_err(CE_WARN, "!plat_store_mem_sids: bd %d errno %d", bd, data->pdsbd_errno); return (data->pdsbd_errno); } domain_dimm_sids[bd].pdsb_valid_bitmap = data->pdsbd_valid_bitmap; for (i = 0; i < PLAT_MAX_DIMMS_PER_BOARD; i++) { if ((1 << i) & domain_dimm_sids[bd].pdsb_valid_bitmap) { (void) strncpy(domain_dimm_sids[bd].pdsb_dimm_sids[i], data->pdsbd_dimm_sids[i], PLAT_MAX_DIMM_SID_LEN); } } domain_dimm_sids[bd].pdsb_state = PDSB_STATE_STORED; mutex_exit(&domain_dimm_sids[bd].pdsb_lock); return (0); } /* * Calls plat_request_mem_sids(bd) for each board number present in the domain. * Called the first time the capability exchange is successful and the SC * capability indicates support for providing DIMM serial ids. * * The input argument is a bitmask of cpu/mem boards that are present and * have at least one memory controller configured. */ static void plat_request_all_mem_sids(uint32_t bds) { int bd; int ret; for (bd = 0; bd < plat_max_cpumem_boards(); bd++) { if (!((1 << bd) & bds)) continue; ret = plat_request_mem_sids(bd); if (ret) { mutex_enter(&domain_dimm_sids[bd].pdsb_lock); domain_dimm_sids[bd].pdsb_state = PDSB_STATE_FAILED_TO_STORE; mutex_exit(&domain_dimm_sids[bd].pdsb_lock); } } } /* * Initiates a mailbox request to SC for DIMM serial ids for the specified * board number. Called by DR when a CPU/Mem board is connected. Also * called by plat_request_all_mem_sids(). */ int plat_request_mem_sids(int boardnum) { plat_ecc_message_t *wrapperp; plat_dimm_sid_request_data_t *dreqp; if (domain_dimm_sids[boardnum].pdsb_state == PDSB_STATE_STORED) return (0); mutex_enter(&domain_dimm_sids[boardnum].pdsb_lock); domain_dimm_sids[boardnum].pdsb_state = PDSB_STATE_STORE_IN_PROGRESS; mutex_exit(&domain_dimm_sids[boardnum].pdsb_lock); wrapperp = kmem_zalloc(sizeof (plat_ecc_message_t), KM_SLEEP); /* Initialize the wrapper */ wrapperp->ecc_msg_status = PLAT_ECC_NO_MSG_ACTIVE; wrapperp->ecc_msg_type = PLAT_ECC_DIMM_SID_MESSAGE; wrapperp->ecc_msg_len = sizeof (plat_dimm_sid_request_data_t); wrapperp->ecc_msg_data = kmem_zalloc(wrapperp->ecc_msg_len, KM_SLEEP); dreqp = (plat_dimm_sid_request_data_t *)wrapperp->ecc_msg_data; /* Fill the header */ dreqp->pdsrd_major_version = PLAT_ECC_DIMM_SID_VERSION_MAJOR; dreqp->pdsrd_minor_version = PLAT_ECC_DIMM_SID_VERSION_MINOR; dreqp->pdsrd_msg_type = PLAT_ECC_DIMM_SID_MESSAGE; dreqp->pdsrd_msg_length = wrapperp->ecc_msg_len; /* Set board number DIMM serial ids are requested for */ dreqp->pdsrd_board_num = boardnum; /* * Send the data on to the queuing function */ return (plat_ecc_dispatch_task(wrapperp)); } /* * Discards DIMM serial id information from domain_dimm_sids[] * for a particular board. * Called by DR when a CPU/Mem board is disconnected. */ int plat_discard_mem_sids(int boardnum) { mutex_enter(&domain_dimm_sids[boardnum].pdsb_lock); domain_dimm_sids[boardnum].pdsb_state = PDSB_STATE_INVALID; mutex_exit(&domain_dimm_sids[boardnum].pdsb_lock); return (0); }