root/usr/src/uts/sun4u/os/plat_ecc_dimm.c
/*
 * 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 <sys/ddi.h>
#include <sys/plat_ecc_dimm.h>

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);
}