/* * 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. */ /* * Interface for Serengeti IOSRAM mailbox * OS <-> SC communication protocol */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include sbbc_mailbox_t *master_mbox = NULL; /* * Panic Shutdown event support */ static kmutex_t panic_hdlr_lock; /* * The ID of the soft interrupt which triggers the bringing down of a Domain * when a PANIC_SHUTDOWN event is received. */ static ddi_softintr_t panic_softintr_id = 0; static sg_panic_shutdown_t panic_payload; static sbbc_msg_t panic_payload_msg; /* * A queue for making sure outgoing messages are in order as ScApp * does not support interleaving messages. */ static kcondvar_t outbox_queue; static kmutex_t outbox_queue_lock; /* * Handle unsolicited capability message. */ static plat_capability_data_t cap_payload; static sbbc_msg_t cap_payload_msg; static kmutex_t cap_msg_hdlr_lock; /* * Datapath error and fault messages arrive unsolicited. The message data * is contained in a plat_datapath_info_t structure. */ typedef struct { uint8_t type; /* CDS, DX, CP */ uint8_t pad; /* for alignment */ uint16_t cpuid; /* Safari ID of base CPU */ uint32_t t_value; /* SERD timeout threshold (seconds) */ } plat_datapath_info_t; /* * Unsolicited datapath error messages are processed via a soft interrupt, * triggered in unsolicited interrupt processing. */ static ddi_softintr_t dp_softintr_id = 0; static kmutex_t dp_hdlr_lock; static plat_datapath_info_t dp_payload; static sbbc_msg_t dp_payload_msg; static char *dperrtype[] = { DP_ERROR_CDS, DP_ERROR_DX, DP_ERROR_RP }; /* * Variable indicating if we are already processing requests. * Setting this value must be protected by outbox_queue_lock. */ static int outbox_busy = 0; /* * local stuff */ static int sbbc_mbox_send_msg(sbbc_msg_t *, int, uint_t, time_t, clock_t); static int sbbc_mbox_recv_msg(); static int mbox_write(struct sbbc_mbox_header *, struct sbbc_fragment *, sbbc_msg_t *); static int mbox_read(struct sbbc_mbox_header *, struct sbbc_fragment *, sbbc_msg_t *); static int mbox_has_free_space(struct sbbc_mbox_header *); static void mbox_skip_next_msg(struct sbbc_mbox_header *); static int mbox_read_header(uint32_t, struct sbbc_mbox_header *); static void mbox_update_header(uint32_t, struct sbbc_mbox_header *); static int mbox_read_frag(struct sbbc_mbox_header *, struct sbbc_fragment *); static struct sbbc_msg_waiter *mbox_find_waiter(uint16_t, uint32_t); static void wakeup_next(void); static uint_t sbbc_panic_shutdown_handler(char *arg); static uint_t sbbc_do_fast_shutdown(char *arg); static void sbbc_mbox_post_reg(sbbc_softstate_t *softsp); static uint_t cap_ecc_msg_handler(char *); static uint_t sbbc_datapath_error_msg_handler(char *arg); static uint_t sbbc_datapath_fault_msg_handler(char *arg); static uint_t sbbc_dp_trans_event(char *arg); /* * Interrupt handlers */ static int sbbc_mbox_msgin(void); static int sbbc_mbox_msgout(void); static int sbbc_mbox_spacein(void); static int sbbc_mbox_spaceout(void); /* * ECC event mailbox message taskq and parameters */ static taskq_t *sbbc_ecc_mbox_taskq = NULL; static int sbbc_ecc_mbox_taskq_errs = 0; static int sbbc_ecc_mbox_send_errs = 0; static int sbbc_ecc_mbox_inval_errs = 0; static int sbbc_ecc_mbox_other_errs = 0; int sbbc_ecc_mbox_err_throttle = ECC_MBOX_TASKQ_ERR_THROTTLE; /* * Called when SBBC driver is loaded * Initialise global mailbox stuff, etc */ void sbbc_mbox_init() { int i; master_mbox = kmem_zalloc(sizeof (sbbc_mailbox_t), KM_NOSLEEP); if (master_mbox == NULL) { cmn_err(CE_PANIC, "Can't allocate memory for mailbox\n"); } /* * mutex'es for the wait-lists */ for (i = 0; i < SBBC_MBOX_MSG_TYPES; i++) { mutex_init(&master_mbox->mbox_wait_lock[i], NULL, MUTEX_DEFAULT, NULL); master_mbox->mbox_wait_list[i] = NULL; } for (i = 0; i < SBBC_MBOX_MSG_TYPES; i++) master_mbox->intrs[i] = NULL; /* * Two mailbox channels SC -> OS , read-only * OS -> SC, read/write */ master_mbox->mbox_in = kmem_zalloc(sizeof (sbbc_mbox_t), KM_NOSLEEP); if (master_mbox->mbox_in == NULL) { cmn_err(CE_PANIC, "Can't allocate memory for inbound mailbox\n"); } master_mbox->mbox_out = kmem_zalloc(sizeof (sbbc_mbox_t), KM_NOSLEEP); if (master_mbox->mbox_out == NULL) { cmn_err(CE_PANIC, "Can't allocate memory for outbound mailbox\n"); } mutex_init(&master_mbox->mbox_in->mb_lock, NULL, MUTEX_DEFAULT, NULL); mutex_init(&master_mbox->mbox_out->mb_lock, NULL, MUTEX_DEFAULT, NULL); /* * Add PANIC_SHUTDOWN Event mutex */ mutex_init(&panic_hdlr_lock, NULL, MUTEX_DEFAULT, NULL); /* Initialize datapath error message handler mutex */ mutex_init(&dp_hdlr_lock, NULL, MUTEX_DEFAULT, NULL); /* Initialize capability message handler event mutex */ mutex_init(&cap_msg_hdlr_lock, NULL, MUTEX_DEFAULT, NULL); /* * NOT USED YET */ master_mbox->mbox_in->mb_type = master_mbox->mbox_out->mb_type = 0; cv_init(&outbox_queue, NULL, CV_DEFAULT, NULL); mutex_init(&outbox_queue_lock, NULL, MUTEX_DEFAULT, NULL); } /* * called when the SBBC driver is unloaded */ void sbbc_mbox_fini() { int i; int err; /* * destroy ECC event mailbox taskq */ if (sbbc_ecc_mbox_taskq != NULL) { taskq_destroy(sbbc_ecc_mbox_taskq); sbbc_ecc_mbox_taskq = NULL; sbbc_ecc_mbox_taskq_errs = 0; } /* * unregister interrupts */ (void) iosram_unreg_intr(SBBC_MAILBOX_IN); (void) iosram_unreg_intr(SBBC_MAILBOX_IN); (void) iosram_unreg_intr(SBBC_MAILBOX_SPACE_IN); (void) iosram_unreg_intr(SBBC_MAILBOX_SPACE_OUT); /* * Remove Panic Shutdown and Datapath Error event support. * * NOTE: If we have not added the soft interrupt handlers for these * then we know that we have not registered the event handlers either. */ if (panic_softintr_id != 0) { ddi_remove_softintr(panic_softintr_id); err = sbbc_mbox_unreg_intr(MBOX_EVENT_PANIC_SHUTDOWN, sbbc_panic_shutdown_handler); if (err != 0) { cmn_err(CE_WARN, "Failed to unreg Panic Shutdown " "handler. Err=%d", err); } } if (dp_softintr_id != 0) { ddi_remove_softintr(dp_softintr_id); err = sbbc_mbox_unreg_intr(MBOX_EVENT_DP_ERROR, sbbc_datapath_error_msg_handler); err |= sbbc_mbox_unreg_intr(MBOX_EVENT_DP_FAULT, sbbc_datapath_fault_msg_handler); if (err != 0) { cmn_err(CE_WARN, "Failed to unreg Datapath Error " "handler. Err=%d", err); } } /* * destroy all its mutex'es, lists etc */ /* * mutex'es for the wait-lists */ for (i = 0; i < SBBC_MBOX_MSG_TYPES; i++) { mutex_destroy(&master_mbox->mbox_wait_lock[i]); } mutex_destroy(&master_mbox->mbox_in->mb_lock); mutex_destroy(&master_mbox->mbox_out->mb_lock); mutex_destroy(&panic_hdlr_lock); mutex_destroy(&dp_hdlr_lock); kmem_free(master_mbox->mbox_in, sizeof (sbbc_mbox_t)); kmem_free(master_mbox->mbox_out, sizeof (sbbc_mbox_t)); kmem_free(master_mbox, sizeof (sbbc_mailbox_t)); cv_destroy(&outbox_queue); mutex_destroy(&outbox_queue_lock); err = sbbc_mbox_unreg_intr(INFO_MBOX, cap_ecc_msg_handler); if (err != 0) { cmn_err(CE_WARN, "Failed to unregister capability message " "handler. Err=%d", err); } mutex_destroy(&cap_msg_hdlr_lock); } /* * Update iosram_sbbc to the new softstate after a tunnel switch. * Move software interrupts from the old dip to the new dip. */ int sbbc_mbox_switch(sbbc_softstate_t *softsp) { sbbc_intrs_t *intr; int msg_type; int rc = 0; int err; if (master_mbox == NULL) return (ENXIO); ASSERT(MUTEX_HELD(&master_iosram->iosram_lock)); for (msg_type = 0; msg_type < SBBC_MBOX_MSG_TYPES; msg_type++) { for (intr = master_mbox->intrs[msg_type]; intr != NULL; intr = intr->sbbc_intr_next) { if (intr->sbbc_intr_id) { ddi_remove_softintr(intr->sbbc_intr_id); if (ddi_add_softintr(softsp->dip, DDI_SOFTINT_HIGH, &intr->sbbc_intr_id, NULL, NULL, intr->sbbc_handler, intr->sbbc_arg) != DDI_SUCCESS) { cmn_err(CE_WARN, "Can't add SBBC mailbox " "softint for msg_type %x\n", msg_type); rc = ENXIO; } } } } /* * Add PANIC_SHUTDOWN Event handler */ if (panic_softintr_id) { ddi_remove_softintr(panic_softintr_id); err = ddi_add_softintr(softsp->dip, DDI_SOFTINT_LOW, &panic_softintr_id, NULL, NULL, sbbc_do_fast_shutdown, NULL); if (err != DDI_SUCCESS) { cmn_err(CE_WARN, "Failed to register Panic " "Shutdown handler. Err=%d", err); (void) sbbc_mbox_unreg_intr(MBOX_EVENT_PANIC_SHUTDOWN, sbbc_panic_shutdown_handler); rc = ENXIO; } } /* * Add Datapath Error Event handler */ if (dp_softintr_id) { ddi_remove_softintr(dp_softintr_id); err = ddi_add_softintr(softsp->dip, DDI_SOFTINT_LOW, &dp_softintr_id, NULL, NULL, sbbc_dp_trans_event, NULL); if (err != DDI_SUCCESS) { cmn_err(CE_WARN, "Failed to register Datapath " "Error Event handler. Err=%d", err); (void) sbbc_mbox_unreg_intr(MBOX_EVENT_DP_ERROR, sbbc_datapath_error_msg_handler); (void) sbbc_mbox_unreg_intr(MBOX_EVENT_DP_FAULT, sbbc_datapath_fault_msg_handler); rc = ENXIO; } } return (rc); } /* * Called when the IOSRAM tunnel is created for the 'chosen' node. * * Read the mailbox header from the IOSRAM * tunnel[SBBC_MAILBOX_KEY] * Register the mailbox interrupt handlers * for messages in/space etc */ int sbbc_mbox_create(sbbc_softstate_t *softsp) { struct sbbc_mbox_header header; int i; int err; int rc = 0; /* * This function should only be called once when * the chosen node is initialized. */ ASSERT(MUTEX_HELD(&chosen_lock)); if (master_mbox == NULL) return (ENXIO); /* * read the header at offset 0 * check magic/version etc */ if (rc = iosram_read(SBBC_MAILBOX_KEY, 0, (caddr_t)&header, sizeof (struct sbbc_mbox_header))) { return (rc); } /* * add the interrupt handlers for the mailbox * interrupts */ for (i = 0; i < MBOX_INTRS; i++) { sbbc_intrfunc_t intr_handler; uint_t *state; kmutex_t *lock; uint32_t intr_num; switch (i) { case MBOX_MSGIN_INTR: intr_handler = (sbbc_intrfunc_t)sbbc_mbox_msgin; intr_num = SBBC_MAILBOX_IN; break; case MBOX_MSGOUT_INTR: intr_handler = (sbbc_intrfunc_t)sbbc_mbox_msgout; intr_num = SBBC_MAILBOX_OUT; break; case MBOX_SPACEIN_INTR: intr_handler = (sbbc_intrfunc_t)sbbc_mbox_spacein; intr_num = SBBC_MAILBOX_SPACE_IN; break; case MBOX_SPACEOUT_INTR: intr_handler = (sbbc_intrfunc_t)sbbc_mbox_spaceout; intr_num = SBBC_MAILBOX_SPACE_OUT; break; } state = (uint_t *)&master_mbox->intr_state[i].mbox_intr_state; lock = &master_mbox->intr_state[i].mbox_intr_lock; if (iosram_reg_intr(intr_num, intr_handler, (caddr_t)NULL, state, lock)) { cmn_err(CE_WARN, "Can't register Mailbox interrupts \n"); } } /* * Add PANIC_SHUTDOWN Event handler */ panic_payload_msg.msg_buf = (caddr_t)&panic_payload; panic_payload_msg.msg_len = sizeof (panic_payload); err = ddi_add_softintr(softsp->dip, DDI_SOFTINT_LOW, &panic_softintr_id, NULL, NULL, sbbc_do_fast_shutdown, NULL); if (err == DDI_SUCCESS) { err = sbbc_mbox_reg_intr(MBOX_EVENT_PANIC_SHUTDOWN, sbbc_panic_shutdown_handler, &panic_payload_msg, NULL, &panic_hdlr_lock); if (err != 0) { cmn_err(CE_WARN, "Failed to register Panic " "Shutdown handler. Err=%d", err); } } else { cmn_err(CE_WARN, "Failed to add Panic Shutdown " "softintr handler"); } /* * Add Unsolicited Datapath Error Events handler */ dp_payload_msg.msg_buf = (caddr_t)&dp_payload; dp_payload_msg.msg_len = sizeof (dp_payload); err = ddi_add_softintr(softsp->dip, DDI_SOFTINT_LOW, &dp_softintr_id, NULL, NULL, sbbc_dp_trans_event, NULL); if (err == DDI_SUCCESS) { err = sbbc_mbox_reg_intr(MBOX_EVENT_DP_ERROR, sbbc_datapath_error_msg_handler, &dp_payload_msg, NULL, &dp_hdlr_lock); err |= sbbc_mbox_reg_intr(MBOX_EVENT_DP_FAULT, sbbc_datapath_fault_msg_handler, &dp_payload_msg, NULL, &dp_hdlr_lock); if (err != 0) { cmn_err(CE_WARN, "Failed to register Datapath " "error handler. Err=%d", err); } } else { cmn_err(CE_WARN, "Failed to add Datapath error " "softintr handler"); } /* * Register an interrupt handler with the sgbbc driver for the * unsolicited INFO_MBOX response for the capability bitmap. * This message is expected whenever the SC is (re)booted or * failed over. */ cap_payload_msg.msg_buf = (caddr_t)&cap_payload; cap_payload_msg.msg_len = sizeof (cap_payload); err = sbbc_mbox_reg_intr(INFO_MBOX, cap_ecc_msg_handler, &cap_payload_msg, NULL, &cap_msg_hdlr_lock); if (err != 0) { cmn_err(CE_WARN, "Failed to register capability message" " handler with Err=%d", err); } /* * Now is the opportunity to register * the deferred mbox intrs. */ sbbc_mbox_post_reg(softsp); return (rc); } /* * Called when chosen IOSRAM is initialized * to register the deferred mbox intrs. */ static void sbbc_mbox_post_reg(sbbc_softstate_t *softsp) { uint32_t msg_type; sbbc_intrs_t *intr; ASSERT(master_mbox); for (msg_type = 0; msg_type < SBBC_MBOX_MSG_TYPES; msg_type++) { intr = master_mbox->intrs[msg_type]; while (intr != NULL) { if (!intr->registered) { SGSBBC_DBG_INTR(CE_CONT, "sbbc_mbox_post_reg: " "postreg for msgtype=%x\n", msg_type); if (ddi_add_softintr(softsp->dip, DDI_SOFTINT_HIGH, &intr->sbbc_intr_id, NULL, NULL, intr->sbbc_handler, (caddr_t)intr->sbbc_arg) != DDI_SUCCESS) { cmn_err(CE_WARN, "Can't add SBBC " "deferred mailbox softint \n"); } else intr->registered = 1; } intr = intr->sbbc_intr_next; } } } /* * Register a handler for a message type * NB NB NB * arg must be either NULL or the address of a sbbc_fragment * pointer */ int sbbc_mbox_reg_intr(uint32_t msg_type, sbbc_intrfunc_t intr_handler, sbbc_msg_t *arg, uint_t *state, kmutex_t *lock) { sbbc_intrs_t *intr, *previntr; int rc = 0; /* * Validate arguments */ if (msg_type >= SBBC_MBOX_MSG_TYPES) return (EINVAL); /* * Verify that we have already set up the master sbbc */ if (master_iosram == NULL || master_mbox == NULL) return (ENXIO); mutex_enter(&master_iosram->iosram_lock); msg_type &= SBBC_MSG_TYPE_MASK; previntr = intr = master_mbox->intrs[msg_type]; /* Find the end of the link list */ while (intr != NULL && intr->sbbc_handler != intr_handler) { previntr = intr; intr = intr->sbbc_intr_next; } /* Return if the handler has been registered */ if (intr != NULL) { mutex_exit(&master_iosram->iosram_lock); return (EBUSY); } /* * The requested handler has not been installed. * Allocate some memory. */ intr = kmem_zalloc(sizeof (sbbc_intrs_t), KM_SLEEP); intr->sbbc_handler = intr_handler; intr->sbbc_arg = (caddr_t)arg; intr->sbbc_intr_state = state; intr->sbbc_intr_lock = lock; intr->sbbc_intr_next = NULL; /* not registered yet */ intr->registered = 0; if (previntr != NULL) previntr->sbbc_intr_next = intr; else master_mbox->intrs[msg_type] = intr; /* * register only if the chosen IOSRAM is * initialized, otherwise defer the registration * until IOSRAM initialization. */ if (master_iosram->iosram_sbbc) { if (ddi_add_softintr(master_iosram->iosram_sbbc->dip, DDI_SOFTINT_HIGH, &intr->sbbc_intr_id, NULL, NULL, intr_handler, (caddr_t)arg) != DDI_SUCCESS) { cmn_err(CE_WARN, "Can't add SBBC mailbox softint \n"); rc = ENXIO; } else intr->registered = 1; } else { SGSBBC_DBG_INTR(CE_CONT, "sbbc_mbox_reg_intr: " "deferring msg=%x registration\n", msg_type); } mutex_exit(&master_iosram->iosram_lock); return (rc); } /* * Unregister a handler for a message type */ int sbbc_mbox_unreg_intr(uint32_t msg_type, sbbc_intrfunc_t intr_handler) { sbbc_intrs_t *intr, *previntr, *nextintr; /* * Verify that we have already set up the master sbbc */ if (master_iosram == NULL || master_mbox == NULL) return (ENXIO); msg_type &= SBBC_MSG_TYPE_MASK; if (msg_type >= SBBC_MBOX_MSG_TYPES || intr_handler == (sbbc_intrfunc_t)NULL) { return (EINVAL); } mutex_enter(&master_iosram->iosram_lock); previntr = intr = master_mbox->intrs[msg_type]; /* * No handlers installed */ if (intr == NULL) { mutex_exit(&master_iosram->iosram_lock); return (EINVAL); } while (intr != NULL) { /* Save the next pointer */ nextintr = intr->sbbc_intr_next; /* Found a match. Remove it from the link list */ if (intr->sbbc_handler == intr_handler) { if (intr->sbbc_intr_id) ddi_remove_softintr(intr->sbbc_intr_id); kmem_free(intr, sizeof (sbbc_intrs_t)); if (previntr != master_mbox->intrs[msg_type]) previntr->sbbc_intr_next = nextintr; else master_mbox->intrs[msg_type] = nextintr; break; } /* update pointers */ previntr = intr; intr = nextintr; } mutex_exit(&master_iosram->iosram_lock); return (0); } /* * Interrupt handlers - one for each mailbox * interrupt type */ /* * mailbox message received */ static int sbbc_mbox_msgin() { mutex_enter(&master_mbox->intr_state[MBOX_MSGIN_INTR].mbox_intr_lock); master_mbox->intr_state[MBOX_MSGIN_INTR].mbox_intr_state = SBBC_INTR_RUNNING; mutex_exit(&master_mbox->intr_state[MBOX_MSGIN_INTR].mbox_intr_lock); /* * We are only locking the InBox here, not the whole * mailbox. This is based on the assumption of * complete separation of mailboxes - outbox is * read/write, inbox is read-only. * We only ever update the producer for the * outbox and the consumer for the inbox. */ mutex_enter(&master_mbox->mbox_in->mb_lock); for (;;) { /* * Get as many incoming messages as possible */ while (sbbc_mbox_recv_msg() == 0) /* empty */; /* * send interrupt to SC to let it know that * space is available over here */ (void) iosram_send_intr(SBBC_MAILBOX_SPACE_IN); mutex_enter(&master_mbox->intr_state[MBOX_MSGIN_INTR]. mbox_intr_lock); /* * Read the inbox one more time to see if new messages * has come in after we exit the loop. */ if (sbbc_mbox_recv_msg() == 0) { mutex_exit(&master_mbox->intr_state[MBOX_MSGIN_INTR]. mbox_intr_lock); } else { master_mbox->intr_state[MBOX_MSGIN_INTR]. mbox_intr_state = SBBC_INTR_IDLE; mutex_exit(&master_mbox->intr_state[MBOX_MSGIN_INTR]. mbox_intr_lock); break; } } mutex_exit(&master_mbox->mbox_in->mb_lock); return (DDI_INTR_CLAIMED); } /* * mailbox message sent */ static int sbbc_mbox_msgout() { /* * Should never get this */ return (DDI_INTR_CLAIMED); } /* * space in the inbox */ static int sbbc_mbox_spacein() { /* * Should never get this */ return (DDI_INTR_CLAIMED); } /* * space in the outbox */ static int sbbc_mbox_spaceout() { /* * cv_broadcast() the threads waiting on the * outbox's mb_full */ mutex_enter(&master_mbox->mbox_out->mb_lock); cv_broadcast(&master_mbox->mbox_out->mb_full); mutex_exit(&master_mbox->mbox_out->mb_lock); return (DDI_INTR_CLAIMED); } /* * Client Interface * * The main interface will be * * sbbc_mbox_request_response(sbbc_msg_t *request, * sbbc_msg_t *response, time_t wait_time) * * 1) the client calls request_response * 2) a new unique msg ID is assigned for that msg * 3) if there is space available in the outbox * - the request msg is written to the mbox_out mailbox * and the mailbox info updated. * - allocate a sbbc_msg_waiter struct for this * message, initialise the w_cv condvar. * - get the mailbox mbox_wait_lock mutex for this * message type * - the response msg is put on the mbox_wait_list for * that message type to await the SC's response * - wait on the w_cv condvar protected by the * mbox_wait_lock * - SBBC_MAILBOX_OUT interrupt is sent to the SC * * 4) if no space in the outbox, * - the request message blocks waiting * for a SBBC_MAILBOX_SPACE_OUT interrupt * It will block on the mailbox mb_full condvar. * - go to (3) above * 5) When we get a SBBC_MAILBOX_IN interrupt. * - read the message ID of the next message (FIFO) * - find that ID on the wait list * - no wait list entry => unsolicited message. If theres * a handler, trigger it * - if someone is waiting, read the message in from * SRAM, handling fragmentation, wraparound, etc * - if the whole message has been read, signal * the waiter * - read next message until mailbox empty * - send SBBC_MAILBOX_SPACE_IN interrupt to the SC * * 6) If a response is required and none is received, the client * will timeout after seconds and the message * status will be set to ETIMEDOUT. */ int sbbc_mbox_request_response(sbbc_msg_t *request, sbbc_msg_t *response, time_t wait_time) { struct sbbc_msg_waiter *waiter; uint_t msg_id; int rc = 0; int flags; uint16_t msg_type; clock_t stop_time; clock_t clockleft; kmutex_t *mbox_wait_lock; kmutex_t *mb_lock; static fn_t f = "sbbc_mbox_request_response"; if ((request == NULL) || (request->msg_type.type >= SBBC_MBOX_MSG_TYPES) || ((response != NULL) && (response->msg_type.type >= SBBC_MBOX_MSG_TYPES))) return (EINVAL); msg_type = request->msg_type.type; /* * Verify that we have already set up the master sbbc */ if (master_mbox == NULL) return (ENXIO); mbox_wait_lock = &master_mbox->mbox_wait_lock[msg_type]; flags = WAIT_FOR_REPLY|WAIT_FOR_SPACE; /* * We want to place a lower limit on the shortest amount of time we * will wait before timing out while communicating with the SC via * the mailbox. */ if (wait_time < sbbc_mbox_min_timeout) wait_time = sbbc_mbox_default_timeout; stop_time = ddi_get_lbolt() + wait_time * drv_usectohz(MICROSEC); /* * If there is a message being processed, sleep until it is our turn. */ mutex_enter(&outbox_queue_lock); /* * allocate an ID for this message, let it wrap * around transparently. * msg_id == 0 is unsolicited message */ msg_id = ++(master_mbox->mbox_msg_id); if (msg_id == 0) msg_id = ++(master_mbox->mbox_msg_id); SGSBBC_DBG_MBOX("%s: msg_id = 0x%x, msg_len = 0x%x\n", f, msg_id, request->msg_len); /* * A new message can actually grab the lock before the thread * that has just been signaled. Therefore, we need to double * check to make sure that outbox_busy is not already set * after we wake up. * * Potentially this could mean starvation for certain unfortunate * threads that keep getting woken up and putting back to sleep. * But the window of such contention is very small to begin with. */ while (outbox_busy) { clockleft = cv_timedwait(&outbox_queue, &outbox_queue_lock, stop_time); SGSBBC_DBG_MBOX("%s: msg_id = 0x%x is woken up\n", f, msg_id); /* * If we have timed out, set status to ETIMEOUT and return. */ if (clockleft < 0) { SGSBBC_DBG_MBOX("%s: msg_id = 0x%x has timed out\n", f, msg_id); cmn_err(CE_NOTE, "Timed out obtaining SBBC outbox lock"); request->msg_status = ETIMEDOUT; if (response != NULL) response->msg_status = ETIMEDOUT; mutex_exit(&outbox_queue_lock); return (ETIMEDOUT); } } outbox_busy = 1; mutex_exit(&outbox_queue_lock); /* * We are only locking the OutBox from here, not the whole * mailbox. This is based on the assumption of * complete separation of mailboxes - outbox is * read/write, inbox is read-only. * We only ever update the producer for the * outbox and the consumer for the inbox. */ mb_lock = &master_mbox->mbox_out->mb_lock; mutex_enter(mb_lock); /* * No response expected ? Just send the message and return */ if (response == NULL) { rc = sbbc_mbox_send_msg(request, flags, msg_id, wait_time, stop_time); SGSBBC_DBG_MBOX("%s: msg_id = 0x%x send rc = %d\n", f, msg_id, rc); wakeup_next(); mutex_exit(mb_lock); request->msg_status = rc; return (rc); } /* * allocate/initialise a waiter */ waiter = kmem_zalloc(sizeof (struct sbbc_msg_waiter), KM_NOSLEEP); if (waiter == (struct sbbc_msg_waiter *)NULL) { cmn_err(CE_WARN, "SBBC Mailbox can't allocate waiter\n"); wakeup_next(); mutex_exit(mb_lock); return (ENOMEM); } waiter->w_id = 0; /* Until we get an ID from the send */ waiter->w_msg = response; waiter->w_msg->msg_status = EINPROGRESS; cv_init(&waiter->w_cv, NULL, CV_DEFAULT, NULL); rc = sbbc_mbox_send_msg(request, flags, msg_id, wait_time, stop_time); wakeup_next(); if (rc != 0) { request->msg_status = response->msg_status = rc; mutex_exit(mb_lock); /* Free the waiter */ cv_destroy(&waiter->w_cv); kmem_free(waiter, sizeof (struct sbbc_msg_waiter)); SGSBBC_DBG_MBOX("%s: msg_id = 0x%x send rc = %d\n", f, msg_id, rc); return (rc); } waiter->w_id = msg_id; /* * Lock this waiter list and add the waiter */ mutex_enter(mbox_wait_lock); if (master_mbox->mbox_wait_list[msg_type] == NULL) { master_mbox->mbox_wait_list[msg_type] = waiter; waiter->w_next = NULL; } else { struct sbbc_msg_waiter *tmp; tmp = master_mbox->mbox_wait_list[msg_type]; master_mbox->mbox_wait_list[msg_type] = waiter; waiter->w_next = tmp; } mutex_exit(mb_lock); /* * wait here for a response to our message * holding the mbox_wait_lock for the list ensures * that the interrupt handler can't get in before * we block. * NOTE: We use the request msg_type for the * the wait_list. This ensures that the * msg_type won't change. */ clockleft = cv_timedwait(&waiter->w_cv, mbox_wait_lock, stop_time); SGSBBC_DBG_MBOX("%s: msg_id = 0x%x is woken up for response\n", f, msg_id); /* * If we have timed out, set msg_status to ETIMEDOUT, * and remove the waiter from the waiter list. */ if (clockleft < 0) { /* * Remove the waiter from the waiter list. * If we can't find the waiter in the list, * 1. msg_status == EINPROGRESS * It is being processed. We will give it * a chance to finish. * 2. msg_status != EINPROGRESS * It is done processing. We can safely * remove it. * If we can find the waiter, it has timed out. */ SGSBBC_DBG_MBOX("%s: msg_id = 0x%x has timed out\n", f, msg_id); if (mbox_find_waiter(msg_type, msg_id) == NULL) { if (waiter->w_msg->msg_status == EINPROGRESS) { SGSBBC_DBG_MBOX("%s: Waiting for msg_id = 0x%x " "complete.\n", f, msg_id); cv_wait(&waiter->w_cv, mbox_wait_lock); } } else { SGSBBC_DBG_MBOX("%s: setting msg_id = 0x%x " "to ETIMEDOUT\n", f, msg_id); cmn_err(CE_NOTE, "Timed out waiting for SC response"); rc = waiter->w_msg->msg_status = ETIMEDOUT; } } /* * lose the waiter */ cv_destroy(&waiter->w_cv); kmem_free(waiter, sizeof (struct sbbc_msg_waiter)); mutex_exit(mbox_wait_lock); return (rc); } static void wakeup_next() { /* * Done sending the current message or encounter an error. * Wake up the one request in the outbox_queue. */ mutex_enter(&outbox_queue_lock); outbox_busy = 0; cv_signal(&outbox_queue); mutex_exit(&outbox_queue_lock); } /* ARGSUSED */ int sbbc_mbox_send_msg(sbbc_msg_t *msg, int flags, uint_t msg_id, time_t wait_time, clock_t stop_time) { struct sbbc_mbox_header header; struct sbbc_fragment frag; int rc = 0; int bytes_written; uint32_t intr_enabled; clock_t clockleft; static fn_t f = "sbbc_mbox_send_msg"; /* * First check that the SC has enabled its mailbox */ rc = iosram_read(SBBC_INTR_SC_ENABLED_KEY, 0, (caddr_t)&intr_enabled, sizeof (intr_enabled)); if (rc) return (rc); if (!(intr_enabled & SBBC_MAILBOX_OUT)) return (ENOTSUP); /* * read the mailbox header */ if (rc = mbox_read_header(SBBC_OUTBOX, &header)) return (rc); /* * Allocate/initialise a fragment for this message */ frag.f_id = msg_id; frag.f_type = msg->msg_type; frag.f_status = 0; frag.f_total_len = msg->msg_len; frag.f_frag_offset = 0; /* * Throw in the message data */ bcopy(&msg->msg_data, &frag.f_data, sizeof (msg->msg_data)); /* * If not enough space is available * write what we can and wait for * an interrupt to tell us that more * space is available */ bytes_written = 0; do { rc = mbox_write(&header, &frag, msg); if (rc != 0 && rc != ENOSPC) { return (rc); } if (rc == 0) { /* * Always tell the SC when there is a message. * Ignore returned value as not being able to * signal the SC about space available does * not stop the SC from processing input. */ (void) iosram_send_intr(SBBC_MAILBOX_OUT); } bytes_written += frag.f_frag_len; frag.f_frag_offset += frag.f_frag_len; if ((bytes_written < msg->msg_len) || (rc == ENOSPC)) { if (mbox_has_free_space(&header) <= sizeof (struct sbbc_fragment)) { int tmprc; clockleft = cv_timedwait( &master_mbox->mbox_out->mb_full, &master_mbox->mbox_out->mb_lock, stop_time); /* Return ETIMEDOUT if we timed out */ if (clockleft < 0) { SGSBBC_DBG_MBOX("%s: msg_id = 0x%x " "has timed out\n", f, msg_id); cmn_err(CE_NOTE, "Timed out sending message " "to SC"); return (ETIMEDOUT); } /* Read updated header from IOSRAM */ if (tmprc = mbox_read_header(SBBC_OUTBOX, &header)) { return (tmprc); } } } SGSBBC_DBG_MBOX("%s: msg_id = 0x%x, bytes_written = 0x%x, " "msg_len = 0x%x\n", f, msg_id, bytes_written, msg->msg_len); } while ((bytes_written < msg->msg_len) || (rc == ENOSPC)); /* * this could be a spurious interrupt * as the SC may be merrily readings its * mail even as send, but what can you do ? No * synchronization method between SC <-> OS * SRAM data eaters means that this is inevitable. * It would take a bigger brain to fix this. * */ (void) iosram_send_intr(SBBC_MAILBOX_OUT); return (rc); } /* * get next message * Read the next message from SRAM * Check if theres an entry on the wait queue * for this message * If yes, read the message in and signal * the waiter (if all the message has been received) * No, its unsolicited, if theres a handler installed for * this message type trigger it, otherwise toss * the message */ int sbbc_mbox_recv_msg() { struct sbbc_mbox_header header; struct sbbc_fragment frag; sbbc_msg_t tmpmsg; /* Temporary msg storage */ int rc = 0, i, first_hdlr, last_hdlr; uint32_t intr_enabled; sbbc_intrs_t *intr; struct sbbc_msg_waiter *waiter; uint16_t type; /* frag.f_type.type */ uint32_t f_id; /* frag.f_id */ uint32_t f_frag_offset, f_frag_len; kmutex_t *mbox_wait_lock; static fn_t f = "sbbc_mbox_recv_msg"; /* * First check that the OS has enabled its mailbox */ rc = iosram_read(SBBC_SC_INTR_ENABLED_KEY, 0, (caddr_t)&intr_enabled, sizeof (intr_enabled)); if (rc) { return (rc); } if (!(intr_enabled & SBBC_MAILBOX_IN)) return (ENOTSUP); /* * read the mailbox header */ if (rc = mbox_read_header(SBBC_INBOX, &header)) return (rc); /* * check if any messages available. If * consumer == producer then no more * messages */ if ((header.mailboxes[SBBC_INBOX].mbox_consumer == header.mailboxes[SBBC_INBOX].mbox_producer)) { return (-1); } /* * read the fragment header for this message */ if (rc = mbox_read_frag(&header, &frag)) { return (rc); } /* Save to local variable for easy reading */ type = frag.f_type.type; f_id = frag.f_id; SGSBBC_DBG_MBOX("%s: f_id = 0x%x\n", f, f_id); /* * check the message type. If its invalid, we will * just toss the message */ if (type >= SBBC_MBOX_MSG_TYPES) { goto done; } /* * if theres no waiters for this message type, and theres * no message handler installed, toss it. * * Unsolicited messages (f_id == 0) are tricky because we won't know * when the handler has finished so that we can * remove the message, so, given the small brains in operation * here, what we do is restrict junk mail to zero-length * messages, then we allocate a fragment using kmem, * make a copy of the fragment in this memory, * pass this pointer to the fragment, then skip the message. * So even if there is data associated with the junkmail, * the message handler doesn't get to see it * We expect the mesaage handler to free the memory. */ if (type == SBBC_BROADCAST_MSG) { /* * Broadcast message, trigger all handlers */ first_hdlr = 0; last_hdlr = SBBC_MBOX_MSG_TYPES - 1; } else if ((master_mbox->mbox_wait_list[type] == NULL) || (f_id == 0)) { /* * Theres no waiters, or its unsolicited anyway */ first_hdlr = last_hdlr = type; } else { /* * check the fragment message type, look at the wait list for * that type to find its associated message * * First find the message. If we get it, take it off * the waiter list and read the data. We will * put it back on the list if necessary. * This avoids the problem of a second message-in * interrupt playing with this waiter. * This will cut down on mutex spinning on the wait * list locks, also, expect the next fragment to be * for this messageso we might as well have it at the * start of the list. * * its possible that a return message has a different type, * (possible but not recommended!). So, if we don't find * it on the list pointed to by the request type, * go look at all the other lists */ mbox_wait_lock = &master_mbox->mbox_wait_lock[type]; mutex_enter(mbox_wait_lock); if ((waiter = mbox_find_waiter(type, f_id)) == NULL) { for (i = 0; i < SBBC_MBOX_MSG_TYPES; i++) { if (i == type) continue; if ((waiter = mbox_find_waiter(i, f_id)) != NULL) break; } } mutex_exit(mbox_wait_lock); if (waiter == NULL) { rc = -1; /* * there's no waiter for this message, but that * could mean that this message is the start of * a send/receive to us, and every 'first' request * must by definition be unsolicited, * so trigger the handler */ first_hdlr = last_hdlr = type; } else { SGSBBC_DBG_MBOX("%s: f_id = 0x%x, msg_id = 0x%x, " "msg_len = 0x%x\n", f, f_id, waiter->w_id, waiter->w_msg->msg_len); rc = mbox_read(&header, &frag, waiter->w_msg); SGSBBC_DBG_MBOX("%s: f_id = 0x%x, offset = 0x%x, " "len = 0x%x, total_len = 0x%x\n", f, frag.f_id, frag.f_frag_offset, frag.f_frag_len, frag.f_total_len); if (rc || ((frag.f_frag_offset + frag.f_frag_len) == frag.f_total_len)) { /* * failed or all the message has been read in */ mutex_enter(mbox_wait_lock); waiter->w_msg->msg_status = (rc == ENOMEM)? rc : frag.f_status; SGSBBC_DBG_MBOX("%s: msg_status = %d\n", f, waiter->w_msg->msg_status); cv_signal(&waiter->w_cv); mutex_exit(mbox_wait_lock); } else { /* * back on the wait list */ mutex_enter(mbox_wait_lock); if (waiter->w_msg->msg_status == ETIMEDOUT) { cv_signal(&waiter->w_cv); mutex_exit(mbox_wait_lock); goto done; } if (master_mbox->mbox_wait_list[type] == NULL) { master_mbox->mbox_wait_list[type] = waiter; waiter->w_next = NULL; } else { struct sbbc_msg_waiter *tmp; tmp = master_mbox->mbox_wait_list[type]; master_mbox->mbox_wait_list[type] = waiter; waiter->w_next = tmp; } mutex_exit(mbox_wait_lock); } goto done; } } /* * Set msg_len to f_frag_len so msg_buf will be large enough * to contain what is in the fragment. */ f_frag_len = tmpmsg.msg_len = frag.f_frag_len; /* * Save the f_frag_offset for copying into client's space. * Set frag.f_frag_offset to 0 so we don't have to allocate * too much space for reading in the message. */ f_frag_offset = frag.f_frag_offset; frag.f_frag_offset = 0; /* Allocate space for msg_buf */ if (f_frag_len != 0 && (tmpmsg.msg_buf = kmem_alloc(f_frag_len, KM_NOSLEEP)) == NULL) { rc = ENOMEM; cmn_err(CE_WARN, "Can't allocate memory" " for unsolicited messages\n"); } else { /* Save the incoming message in tmpmsg */ rc = mbox_read(&header, &frag, &tmpmsg); for (i = first_hdlr; rc == 0 && i <= last_hdlr; i++) { intr = master_mbox->intrs[i]; if ((intr == NULL) || (intr->sbbc_intr_id == 0)) { continue; } while (intr != NULL) { /* * If the client has allocated enough space * for incoming message, copy into the * client buffer. */ sbbc_msg_t *arg = (sbbc_msg_t *)intr->sbbc_arg; if (arg != (void *)NULL) { if (arg->msg_len >= frag.f_total_len) { if (f_frag_len > 0) bcopy(tmpmsg.msg_buf, arg->msg_buf + f_frag_offset, f_frag_len); } else { arg->msg_status = ENOMEM; } } /* * Only trigger the interrupt when we * have received the whole message. */ if (f_frag_offset + f_frag_len == frag.f_total_len) { ddi_trigger_softintr( intr->sbbc_intr_id); } intr = intr->sbbc_intr_next; } } if (f_frag_len != 0) { /* Don't forget to free the buffer */ kmem_free(tmpmsg.msg_buf, f_frag_len); } } done: mbox_skip_next_msg(&header); return (rc); } /* * available free space in the outbox */ static int mbox_has_free_space(struct sbbc_mbox_header *header) { uint32_t space = 0; ASSERT(MUTEX_HELD(&master_mbox->mbox_out->mb_lock)); if (header->mailboxes[SBBC_OUTBOX].mbox_producer == header->mailboxes[SBBC_OUTBOX].mbox_consumer) { /* * mailbox is empty */ space += header->mailboxes[SBBC_OUTBOX].mbox_len - header->mailboxes[SBBC_OUTBOX].mbox_producer; space += header->mailboxes[SBBC_OUTBOX].mbox_producer; } else if (header->mailboxes[SBBC_OUTBOX].mbox_producer > header->mailboxes[SBBC_OUTBOX].mbox_consumer) { space += header->mailboxes[SBBC_OUTBOX].mbox_len - header->mailboxes[SBBC_OUTBOX].mbox_producer; space += header->mailboxes[SBBC_OUTBOX].mbox_consumer; } else { /* * mailbox wrapped around */ space += header->mailboxes[SBBC_OUTBOX].mbox_consumer - header->mailboxes[SBBC_OUTBOX].mbox_producer; } /* * Need to make sure that the mailbox never * gets completely full, as consumer == producer is * our test for empty, so we drop MBOX_ALIGN_BYTES. */ if (space >= MBOX_ALIGN_BYTES) space -= MBOX_ALIGN_BYTES; else space = 0; return (space); } /* * Write the data to IOSRAM * Update the SRAM mailbox header * Update the local mailbox pointers * Only write a single fragment. If possible, * put the whole message into a fragment. * * Note: We assume that there is no 'max' message * size. We will just keep fragmenting. * Note: We always write to SBBC_OUTBOX and * read from SBBC_INBOX * * If we get an error at any time, return immediately * without updating the mailbox header in SRAM */ static int mbox_write(struct sbbc_mbox_header *header, struct sbbc_fragment *frag, sbbc_msg_t *msg) { int bytes_written, bytes_remaining, free_space; int rc = 0; caddr_t src; uint32_t sram_dst; int space_at_end, space_at_start; uint32_t mbox_offset, mbox_len; uint32_t mbox_producer, mbox_consumer; uint32_t f_total_len, f_frag_offset; uint32_t frag_header_size; static fn_t f = "mbox_write"; ASSERT(MUTEX_HELD(&master_mbox->mbox_out->mb_lock)); /* * Save to local variables to make code more readable */ mbox_offset = header->mailboxes[SBBC_OUTBOX].mbox_offset; mbox_len = header->mailboxes[SBBC_OUTBOX].mbox_len; mbox_producer = header->mailboxes[SBBC_OUTBOX].mbox_producer; mbox_consumer = header->mailboxes[SBBC_OUTBOX].mbox_consumer; f_total_len = frag->f_total_len; f_frag_offset = frag->f_frag_offset; frag_header_size = sizeof (struct sbbc_fragment); SGSBBC_DBG_MBOX("%s: mbox_consumer = 0x%x, " "mbox_producer = 0x%x\n", f, mbox_consumer, mbox_producer); /* * Write pointer in SRAM */ sram_dst = mbox_offset + mbox_producer; /* * NB We assume that the consumer stays constant * during the write. It may not necessarily * be the case but it won't cause us any problems, just means * we fragment more than is absolutely necessary * * possible cases * 1) consumer == producer, mailbox empty * space_at_end == mailbox end - producer * space_at_start == producer - MBOX_ALIGN_BYTES * 2) producer < consumer * space_at_end = (consumer - producer - MBOX_ALIGN_BYTES) * space_at_start == 0 * 3) producer > consumer * space_at_end = mailbox end - producer * space_at_start = consumer - MBOX_ALIGN_BYTES * * (space - MBOX_ALIGN_BYTES) because we need to avoid the * scenario where the producer wraps around completely and * producer == consumer, as this is our test for 'empty'. * Also we want it to be 8-byte aligned. * Note: start is assumed = 0 */ if (mbox_producer < mbox_consumer) { space_at_end = mbox_consumer - mbox_producer - MBOX_ALIGN_BYTES; if (space_at_end < 0) space_at_end = 0; space_at_start = 0; } else { space_at_end = mbox_len - mbox_producer; if (mbox_consumer == 0) space_at_end -= MBOX_ALIGN_BYTES; space_at_start = mbox_consumer - MBOX_ALIGN_BYTES; if (space_at_start < 0) space_at_start = 0; } SGSBBC_DBG_MBOX("%s: space_at_end = 0x%x, space_at_start = 0x%x\n", f, space_at_end, space_at_start); free_space = space_at_end + space_at_start; if (free_space < frag_header_size) { /* * can't even write a fragment header, so just return * the caller will block waiting for space */ frag->f_frag_len = 0; return (ENOSPC); } /* * How many bytes will be in the fragment ? */ bytes_remaining = f_total_len - f_frag_offset; frag->f_frag_len = min(bytes_remaining, free_space - frag_header_size); SGSBBC_DBG_MBOX("%s: writing header:sram_dst = 0x%x\n", f, sram_dst); /* * we can write the fragment header and some data * First, the fragment header */ if (space_at_end >= frag_header_size) { rc = iosram_write(SBBC_MAILBOX_KEY, sram_dst, (caddr_t)frag, frag_header_size); if (rc) return (rc); sram_dst = (uint32_t)(sram_dst + frag_header_size); /* * Wrap around if we reach the end */ if (sram_dst >= (mbox_len + mbox_offset)) { sram_dst = mbox_offset; } space_at_end -= frag_header_size; } else { /* wraparound */ if (space_at_end) { rc = iosram_write(SBBC_MAILBOX_KEY, sram_dst, (caddr_t)frag, space_at_end); if (rc) return (rc); sram_dst = (uint32_t)mbox_offset; } rc = iosram_write(SBBC_MAILBOX_KEY, sram_dst, (caddr_t)((caddr_t)frag + space_at_end), (frag_header_size - space_at_end)); if (rc) return (rc); sram_dst += frag_header_size - space_at_end; space_at_start -= (frag_header_size - space_at_end); space_at_end = 0; } SGSBBC_DBG_MBOX("%s: space_at_end = 0x%x, space_at_start = 0x%x\n", f, space_at_end, space_at_start); /* * Now the fragment data */ free_space -= frag_header_size; src = (caddr_t)(msg->msg_buf + f_frag_offset); bytes_written = 0; if (space_at_end) { SGSBBC_DBG_MBOX("%s: writing data:sram_dst = 0x%x, " "bytes_remaining = 0x%x\n", f, sram_dst, bytes_remaining); if (space_at_end < bytes_remaining) bytes_written = space_at_end; else bytes_written = bytes_remaining; rc = iosram_write(SBBC_MAILBOX_KEY, sram_dst, src, bytes_written); if (rc) return (rc); sram_dst = (uint32_t)(sram_dst + bytes_written); /* * Wrap around if we reach the end */ if (sram_dst >= (mbox_len + mbox_offset)) { sram_dst = mbox_offset; } src = (caddr_t)(src + bytes_written); bytes_remaining -= bytes_written; } if ((bytes_remaining > 0) && space_at_start) { SGSBBC_DBG_MBOX("%s: writing the rest:sram_dst = 0x%x, " "bytes_remaining = 0x%x\n", f, sram_dst, bytes_remaining); if (space_at_start < bytes_remaining) { rc = iosram_write(SBBC_MAILBOX_KEY, sram_dst, src, space_at_start); bytes_written += space_at_start; } else { rc = iosram_write(SBBC_MAILBOX_KEY, sram_dst, src, bytes_remaining); bytes_written += bytes_remaining; } if (rc) return (rc); } frag->f_frag_len = bytes_written; /* * update header->mbox_producer (bytes_written + frag_size) */ sram_dst = mbox_producer + bytes_written + frag_header_size; if (sram_dst >= mbox_len) { sram_dst = sram_dst % mbox_len; } SGSBBC_DBG_MBOX("%s: after writing data:sram_dst = 0x%x, " "bytes_written = 0x%x\n", f, sram_dst, bytes_written); header->mailboxes[SBBC_OUTBOX].mbox_producer = sram_dst; mbox_update_header(SBBC_OUTBOX, header); return (rc); } /* * Get the next frag from IOSRAM. * Write it to the corresponding msg buf. * The caller must update the SRAM pointers etc. */ static int mbox_read(struct sbbc_mbox_header *header, struct sbbc_fragment *frag, sbbc_msg_t *msg) { int rc = 0; uint32_t sram_src, sram_end; caddr_t msg_buf; int bytes_at_start, bytes_at_end; int bytes_to_read; uint32_t frag_header_size, frag_total_size; uint32_t f_frag_offset, f_frag_len; uint32_t mbox_producer, mbox_consumer; uint32_t mbox_len, mbox_offset; static fn_t f = "mbox_read"; ASSERT(MUTEX_HELD(&master_mbox->mbox_in->mb_lock)); /* * Save to local variables to make code more readable */ mbox_producer = header->mailboxes[SBBC_INBOX].mbox_producer; mbox_consumer = header->mailboxes[SBBC_INBOX].mbox_consumer; mbox_len = header->mailboxes[SBBC_INBOX].mbox_len; mbox_offset = header->mailboxes[SBBC_INBOX].mbox_offset; frag_header_size = sizeof (struct sbbc_fragment); f_frag_offset = frag->f_frag_offset; f_frag_len = frag->f_frag_len; frag_total_size = frag_header_size + f_frag_len; /* * If the message buffer size is smaller than the fragment * size, return an error. */ if (msg->msg_len < f_frag_len) { rc = ENOMEM; goto done; } msg_buf = (caddr_t)(msg->msg_buf + f_frag_offset); /* * Throw in the message data */ bcopy(&frag->f_data, &msg->msg_data, sizeof (msg->msg_data)); /* * We have it all, waiter, message, so lets * go get that puppy! * Message could be in one or two chunks - * consumer < producer: 1 chunk, (producer - consumer) * consumer > producer: 2 chunks, (end - consumer) * (producer - start) */ sram_end = (uint32_t)(mbox_offset + mbox_len); sram_src = (uint32_t)(mbox_offset + mbox_consumer + frag_header_size); /* * wraparound */ if (sram_src >= sram_end) sram_src -= mbox_len; /* * find where the data is * possible cases * 1) consumer == producer, mailbox empty * error * 2) producer < consumer * bytes_at_end = mailbox end - consumer * bytes_at_start = producer * 3) producer > consumer * bytes_at_end = producer - consumer * bytes_at_start = 0 */ SGSBBC_DBG_MBOX("%s: mbox_consumer = 0x%x, mbox_producer = 0x%x, " "frag_len = 0x%x\n", f, mbox_consumer, mbox_producer, f_frag_len); if (mbox_producer == mbox_consumer) { bytes_at_end = bytes_at_start = 0; } else if (mbox_producer < mbox_consumer) { bytes_at_end = mbox_len - mbox_consumer; bytes_at_start = mbox_producer; } else { bytes_at_end = mbox_producer - mbox_consumer; bytes_at_start = 0; } SGSBBC_DBG_MBOX("%s: bytes_at_end = 0x%x, " "bytes_at_start = 0x%x\n", f, bytes_at_end, bytes_at_start); if ((bytes_at_end + bytes_at_start) < frag_total_size) { /* * mailbox is corrupt * but what to do ? */ cmn_err(CE_PANIC, "Corrupt INBOX!\n" "producer = %x, consumer = %x, bytes_at_start = %x, " "bytes_at_end = %x\n", mbox_producer, mbox_consumer, bytes_at_start, bytes_at_end); } /* * If bytes_at_end is greater than header size, read the * part at the end of the mailbox, and then update the * pointers and bytes_to_read. */ if (bytes_at_end > frag_header_size) { /* * We are only interested in the data segment. */ bytes_at_end -= frag_header_size; bytes_to_read = (bytes_at_end >= f_frag_len)? f_frag_len : bytes_at_end; SGSBBC_DBG_MBOX("%s: reading data: sram_src = 0x%x, " "bytes_to_read = 0x%x\n", f, sram_src, bytes_to_read); rc = iosram_read(SBBC_MAILBOX_KEY, sram_src, msg_buf, bytes_to_read); if (rc) { goto done; } /* * Update pointers in SRAM and message buffer. */ sram_src = (uint32_t)mbox_offset; msg_buf = (caddr_t)(msg_buf + bytes_to_read); bytes_to_read = f_frag_len - bytes_to_read; } else { bytes_to_read = f_frag_len; } /* * wraparound to start of mailbox */ if (bytes_to_read > 0) { SGSBBC_DBG_MBOX("%s: reading the rest: sram_src = 0x%x, " "bytes_to_read = 0x%x\n", f, sram_src, bytes_to_read); rc = iosram_read(SBBC_MAILBOX_KEY, sram_src, msg_buf, bytes_to_read); } done: msg->msg_bytes += f_frag_len; return (rc); } /* * move past the next message in the inbox */ static void mbox_skip_next_msg(struct sbbc_mbox_header *header) { struct sbbc_fragment frag; uint32_t next_msg; ASSERT(MUTEX_HELD(&master_mbox->mbox_in->mb_lock)); if (mbox_read_frag(header, &frag)) { cmn_err(CE_PANIC, "INBOX is Corrupt !\n"); } /* * Move on to the next message */ next_msg = header->mailboxes[SBBC_INBOX].mbox_consumer; next_msg += sizeof (struct sbbc_fragment); next_msg += frag.f_frag_len; if (next_msg >= header->mailboxes[SBBC_INBOX].mbox_len) { next_msg = (next_msg + header->mailboxes[SBBC_INBOX].mbox_len) % header->mailboxes[SBBC_INBOX].mbox_len; } header->mailboxes[SBBC_INBOX].mbox_consumer = next_msg; mbox_update_header(SBBC_INBOX, header); return; } static struct sbbc_msg_waiter * mbox_find_waiter(uint16_t msg_type, uint32_t msg_id) { struct sbbc_msg_waiter *waiter, *prev; prev = NULL; for (waiter = master_mbox->mbox_wait_list[msg_type]; waiter != NULL; waiter = waiter->w_next) { if (waiter->w_id == msg_id) { if (prev != NULL) { prev->w_next = waiter->w_next; } else { master_mbox->mbox_wait_list[msg_type] = waiter->w_next; } break; } prev = waiter; } return (waiter); } static int mbox_read_header(uint32_t mailbox, struct sbbc_mbox_header *header) { struct sbbc_mbox_header *hd; uint32_t offset; int rc; /* * Initialize a sbbc_mbox_header pointer to 0 so that we * can use it to calculate the offsets of fields inside * the structure. */ hd = (struct sbbc_mbox_header *)0; if (rc = iosram_read(SBBC_MAILBOX_KEY, 0, (caddr_t)header, sizeof (struct sbbc_mbox_header))) return (rc); /* * Since the header is read in a byte-by-byte fashion * using ddi_rep_get8, we need to re-read the producer * or consumer pointer as integer in case it has changed * after part of the previous value has been read. */ switch (mailbox) { case SBBC_INBOX: offset = (uint32_t)(uintptr_t) (&hd->mailboxes[SBBC_INBOX].mbox_producer); rc = iosram_read(SBBC_MAILBOX_KEY, offset, (caddr_t)&header->mailboxes[SBBC_INBOX].mbox_producer, sizeof (uint32_t)); break; case SBBC_OUTBOX: offset = (uint32_t)(uintptr_t) (&hd->mailboxes[SBBC_OUTBOX].mbox_consumer); rc = iosram_read(SBBC_MAILBOX_KEY, offset, (caddr_t)&header->mailboxes[SBBC_OUTBOX].mbox_consumer, sizeof (uint32_t)); break; default: cmn_err(CE_PANIC, "Invalid Mbox header type\n"); break; } return (rc); } /* * There are only two fields updated by the domain, * the inbox consumer field and the outbox producer * field. These fields are protected by the respective * mbox_{in|out}->mb_lock so that accesses will * be serialised. The only coherency issue is writing * back the header, so we do it here after grabbing * the global mailbox lock. */ static void mbox_update_header(uint32_t mailbox, struct sbbc_mbox_header *header) { struct sbbc_mbox_header *hd; uint32_t value, offset, mbox_len; /* * Initialize a sbbc_mbox_header pointer to 0 so that we * can use it to calculate the offsets of fields inside * the structure. */ hd = (struct sbbc_mbox_header *)0; switch (mailbox) { case SBBC_INBOX: value = header->mailboxes[SBBC_INBOX].mbox_consumer; offset = (uint32_t)(uintptr_t) (&hd->mailboxes[SBBC_INBOX].mbox_consumer); mbox_len = header->mailboxes[SBBC_INBOX].mbox_len; break; case SBBC_OUTBOX: value = header->mailboxes[SBBC_OUTBOX].mbox_producer; offset = (uint32_t)(uintptr_t) (&hd->mailboxes[SBBC_OUTBOX].mbox_producer); mbox_len = header->mailboxes[SBBC_OUTBOX].mbox_len; break; default: cmn_err(CE_PANIC, "Invalid Mbox header type\n"); break; } /* * If the last read/write would cause the next read/write * to be unaligned, we skip on modulo MBOX_ALIGN_BYTES. * This is OK because all the mailbox handlers will * conform to this. */ if (value % MBOX_ALIGN_BYTES) { value += (MBOX_ALIGN_BYTES - (value % MBOX_ALIGN_BYTES)); value %= mbox_len; } if (iosram_write(SBBC_MAILBOX_KEY, offset, (caddr_t)&value, sizeof (uint32_t))) { cmn_err(CE_PANIC, "Mailbox Corrupt ! \n"); } /* * Update internal pointers so they won't be out of sync with * the values in IOSRAM. */ switch (mailbox) { case SBBC_INBOX: header->mailboxes[SBBC_INBOX].mbox_consumer = value; break; case SBBC_OUTBOX: header->mailboxes[SBBC_OUTBOX].mbox_producer = value; break; } } static int mbox_read_frag(struct sbbc_mbox_header *header, struct sbbc_fragment *frag) { int rc = 0; uint32_t sram_src, bytes; caddr_t dst; ASSERT(MUTEX_HELD(&master_mbox->mbox_in->mb_lock)); /* * read the fragment header for this message */ sram_src = (uint32_t)(header->mailboxes[SBBC_INBOX].mbox_offset + header->mailboxes[SBBC_INBOX].mbox_consumer); /* * wraparound ? */ if ((header->mailboxes[SBBC_INBOX].mbox_consumer + sizeof (struct sbbc_fragment)) >= header->mailboxes[SBBC_INBOX].mbox_len) { dst = (caddr_t)frag; bytes = header->mailboxes[SBBC_INBOX].mbox_len - header->mailboxes[SBBC_INBOX].mbox_consumer; if (rc = iosram_read(SBBC_MAILBOX_KEY, sram_src, dst, bytes)) { return (rc); } dst += bytes; sram_src = header->mailboxes[SBBC_INBOX].mbox_offset; bytes = (header->mailboxes[SBBC_INBOX].mbox_consumer + sizeof (struct sbbc_fragment)) % header->mailboxes[SBBC_INBOX].mbox_len; if (rc = iosram_read(SBBC_MAILBOX_KEY, sram_src, dst, bytes)) { return (rc); } } else { if (rc = iosram_read(SBBC_MAILBOX_KEY, sram_src, (caddr_t)frag, sizeof (struct sbbc_fragment))) { return (rc); } } return (0); } /* * This function is triggered by a soft interrupt and it's purpose is to call * to kadmin() to shutdown the Domain. */ /*ARGSUSED0*/ static uint_t sbbc_do_fast_shutdown(char *arg) { (void) kadmin(A_SHUTDOWN, AD_POWEROFF, NULL, kcred); /* * If kadmin fails for some reason then we bring the system down * via power_down(), or failing that using halt(). */ power_down("kadmin() failed, trying power_down()"); halt("power_down() failed, trying halt()"); /* * We should never make it this far, so something must have gone * horribly, horribly wrong. */ /*NOTREACHED*/ return (DDI_INTR_UNCLAIMED); } /* * This function handles unsolicited PANIC_SHUTDOWN events */ static uint_t sbbc_panic_shutdown_handler(char *arg) { static fn_t f = "sbbc_panic_shutdown_handler()"; sg_panic_shutdown_t *payload = NULL; sbbc_msg_t *msg = NULL; if (arg == NULL) { SGSBBC_DBG_EVENT(CE_NOTE, "%s: arg == NULL", f); return (DDI_INTR_UNCLAIMED); } msg = (sbbc_msg_t *)arg; if (msg->msg_buf == NULL) { SGSBBC_DBG_EVENT(CE_NOTE, "%s: msg_buf == NULL", f); return (DDI_INTR_UNCLAIMED); } payload = (sg_panic_shutdown_t *)msg->msg_buf; switch (*payload) { case SC_EVENT_PANIC_ENV: /* * Let the user know why the domain is going down. */ cmn_err(CE_WARN, "%s", PANIC_ENV_EVENT_MSG); /* * trigger sbbc_do_fast_shutdown(). */ ddi_trigger_softintr(panic_softintr_id); /*NOTREACHED*/ break; case SC_EVENT_PANIC_KEYSWITCH: /* * The SC warns a user if they try a destructive keyswitch * command on a Domain which is currently running Solaris. * If the user chooses to continue despite our best advise * then we bring down the Domain immediately without trying * to shut the system down gracefully. */ break; default: SGSBBC_DBG_EVENT(CE_NOTE, "%s: Unknown payload:%d", f, *payload); return (DDI_INTR_UNCLAIMED); } return (DDI_INTR_CLAIMED); } /* * dp_get_cores() * * Checks cpu implementation for the input cpuid and returns * the number of cores. * If implementation cannot be determined, returns 1 */ static int dp_get_cores(uint16_t cpuid) { int bd, ii, impl, nc; bd = cpuid / 4; nc = SG_MAX_CPUS_PER_BD; /* find first with valid implementation */ for (ii = 0; ii < nc; ii++) if (cpu[MAKE_CPUID(bd, ii)]) { impl = cpunodes[MAKE_CPUID(bd, ii)].implementation; break; } if (IS_JAGUAR(impl) || IS_PANTHER(impl)) return (2); else return (1); } /* * dp_payload_add_cpus() * * From datapath mailbox message, determines the number of and safari IDs * for affected cpus, then adds this info to the datapath ereport. * */ static int dp_payload_add_cpus(plat_datapath_info_t *dpmsg, nvlist_t *erp) { int jj = 0, numcpus = 0; int bd, procpos, ii, num, ncores, ret; uint16_t *dparray, cpuid; uint64_t *snarray; /* check for multiple core architectures */ ncores = dp_get_cores(dpmsg->cpuid); switch (dpmsg->type) { case DP_CDS_TYPE: numcpus = ncores; break; case DP_DX_TYPE: numcpus = 2 * ncores; break; case DP_RP_TYPE: numcpus = SG_MAX_CPUS_PER_BD; break; default: ASSERT(0); return (-1); } num = numcpus; /* * populate dparray with impacted cores (only those present) */ dparray = kmem_zalloc(num * sizeof (uint16_t *), KM_SLEEP); bd = SG_PORTID_TO_BOARD_NUM(SG_CPUID_TO_PORTID(dpmsg->cpuid)); procpos = SG_CPUID_TO_PORTID(dpmsg->cpuid) & 0x3; mutex_enter(&cpu_lock); switch (dpmsg->type) { case DP_CDS_TYPE: /* * For a CDS error, it's the reporting cpuid * and it's other core (if present) */ cpuid = dpmsg->cpuid & 0x1FF; /* core 0 */ if (cpu[cpuid]) dparray[jj++] = cpuid; cpuid = dpmsg->cpuid | SG_CORE_ID_MASK; /* core 1 */ if (cpu[cpuid]) dparray[jj++] = cpuid; break; case DP_DX_TYPE: /* * For a DX error, it's the reporting cpuid (all * cores) and the other CPU sharing the same * DX<-->DCDS interface (all cores) */ /* reporting cpuid */ cpuid = dpmsg->cpuid & 0x1FF; /* core 0 */ if (cpu[cpuid]) dparray[jj++] = cpuid; cpuid = dpmsg->cpuid | SG_CORE_ID_MASK; /* core 1 */ if (cpu[cpuid]) dparray[jj++] = cpuid; /* find partner cpuid */ if (procpos == 0 || procpos == 2) cpuid = dpmsg->cpuid + 1; else cpuid = dpmsg->cpuid - 1; /* add partner cpuid */ cpuid &= 0x1FF; /* core 0 */ if (cpu[cpuid]) dparray[jj++] = cpuid; cpuid |= SG_CORE_ID_MASK; /* core 1 */ if (cpu[cpuid]) dparray[jj++] = cpuid; break; case DP_RP_TYPE: /* * For a RP error, it's all cpuids (all cores) on * the reporting board */ for (ii = 0; ii < SG_MAX_CMPS_PER_BD; ii++) { cpuid = MAKE_CPUID(bd, ii); if (cpu[cpuid]) /* core 0 */ dparray[jj++] = cpuid; cpuid |= SG_CORE_ID_MASK; if (cpu[cpuid]) /* core 1 */ dparray[jj++] = cpuid; } break; } mutex_exit(&cpu_lock); /* * The datapath message could not be associated with any * configured CPU. */ if (!jj) { kmem_free(dparray, num * sizeof (uint16_t *)); ret = nvlist_add_uint32(erp, DP_LIST_SIZE, jj); ASSERT(ret == 0); return (-1); } snarray = kmem_zalloc(jj * sizeof (uint64_t), KM_SLEEP); for (ii = 0; ii < jj; ii++) snarray[ii] = cpunodes[dparray[ii]].device_id; ret = nvlist_add_uint32(erp, DP_LIST_SIZE, jj); ret |= nvlist_add_uint16_array(erp, DP_LIST, dparray, jj); ret |= nvlist_add_uint64_array(erp, SN_LIST, snarray, jj); ASSERT(ret == 0); kmem_free(dparray, num * sizeof (uint16_t *)); kmem_free(snarray, jj * sizeof (uint64_t *)); return (0); } /* * sbbc_dp_trans_event() - datapath message handler. * * Process datapath error and fault messages received from the SC. Checks * for, and disregards, messages associated with I/O boards. Otherwise, * extracts message info to produce a datapath ereport. */ /*ARGSUSED*/ static uint_t sbbc_dp_trans_event(char *arg) { const char *f = "sbbc_dp_trans_event()"; nvlist_t *erp, *detector, *hcelem; char buf[FM_MAX_CLASS]; int board; plat_datapath_info_t *dpmsg; sbbc_msg_t *msg; int msgtype; /* set i/f message and payload pointers */ msg = &dp_payload_msg; dpmsg = &dp_payload; msgtype = msg->msg_type.type; cmn_err(CE_NOTE, "%s: msgtype=0x%x\n", f, msgtype); cmn_err(CE_NOTE, "type=0x%x cpuid=0x%x t_value=0x%x\n", dpmsg->type, dpmsg->cpuid, dpmsg->t_value); /* check for valid type */ if (dpmsg->type > DP_RP_TYPE) { cmn_err(CE_WARN, "%s: dpmsg type 0x%x invalid\n", f, dpmsg->type); return (DDI_INTR_CLAIMED); } /* check for I/O board message - Schizo AIDs are 25 - 30 */ if (dpmsg->cpuid > 23) { cmn_err(CE_NOTE, "%s: ignore I/O board msg\n", f); return (DDI_INTR_CLAIMED); } /* allocate space for ereport */ erp = fm_nvlist_create(NULL); /* * Member Name Data Type Comments * ----------- --------- ----------- * version uint8 0 * class string "asic" * ENA uint64 ENA Format 1 * detector fmri aggregated ID data for SC-DE * * Datapath ereport subclasses and data payloads: * There will be two types of ereports (error and fault) which will be * identified by the "type" member. * * ereport.asic.serengeti.cds.cds-dp * ereport.asic.serengeti.dx.dx-dp (board) * ereport.asic.serengeti.rp.rp-dp (centerplane) * * Member Name Data Type Comments * ----------- --------- ----------- * erptype uint16 derived from message type: error or * fault * t-value uint32 SC's datapath SERD timeout threshold * dp-list-sz uint8 number of dp-list array elements * dp-list array of uint16 Safari IDs of affected cpus * sn-list array of uint64 Serial numbers of affected cpus */ /* compose common ereport elements */ detector = fm_nvlist_create(NULL); /* * Create legacy FMRI for the detector */ board = SG_PORTID_TO_BOARD_NUM(SG_CPUID_TO_PORTID(dpmsg->cpuid)); switch (dpmsg->type) { case DP_CDS_TYPE: case DP_DX_TYPE: (void) snprintf(buf, FM_MAX_CLASS, "SB%d", board); break; case DP_RP_TYPE: (void) snprintf(buf, FM_MAX_CLASS, "RP"); break; default: (void) snprintf(buf, FM_MAX_CLASS, "UNKNOWN"); break; } hcelem = fm_nvlist_create(NULL); (void) nvlist_add_string(hcelem, FM_FMRI_HC_NAME, FM_FMRI_LEGACY_HC); (void) nvlist_add_string(hcelem, FM_FMRI_HC_ID, buf); (void) nvlist_add_uint8(detector, FM_VERSION, FM_HC_SCHEME_VERSION); (void) nvlist_add_string(detector, FM_FMRI_SCHEME, FM_FMRI_SCHEME_HC); (void) nvlist_add_string(detector, FM_FMRI_HC_ROOT, ""); (void) nvlist_add_uint32(detector, FM_FMRI_HC_LIST_SZ, 1); (void) nvlist_add_nvlist_array(detector, FM_FMRI_HC_LIST, &hcelem, 1); /* build ereport class name */ (void) snprintf(buf, FM_MAX_CLASS, "asic.serengeti.%s.%s-%s", dperrtype[dpmsg->type], dperrtype[dpmsg->type], FM_ERROR_DATAPATH); fm_ereport_set(erp, FM_EREPORT_VERSION, buf, fm_ena_generate(0, FM_ENA_FMT1), detector, NULL); /* add payload elements */ if (msgtype == MBOX_EVENT_DP_ERROR) fm_payload_set(erp, DP_EREPORT_TYPE, DATA_TYPE_UINT16, DP_ERROR, NULL); else fm_payload_set(erp, DP_EREPORT_TYPE, DATA_TYPE_UINT16, DP_FAULT, NULL); fm_payload_set(erp, DP_TVALUE, DATA_TYPE_UINT32, dpmsg->t_value, NULL); (void) dp_payload_add_cpus(dpmsg, erp); /* post ereport */ fm_ereport_post(erp, EVCH_SLEEP); /* free ereport memory */ fm_nvlist_destroy(erp, FM_NVA_FREE); fm_nvlist_destroy(detector, FM_NVA_FREE); return (DDI_INTR_CLAIMED); } static uint_t sbbc_datapath_error_msg_handler(char *arg) { static fn_t f = "sbbc_datapath_error_msg_handler()"; sbbc_msg_t *msg = NULL; if (arg == NULL) { SGSBBC_DBG_EVENT(CE_NOTE, "%s: arg == NULL", f); return (DDI_INTR_UNCLAIMED); } msg = (sbbc_msg_t *)arg; if (msg->msg_buf == NULL) { SGSBBC_DBG_EVENT(CE_NOTE, "%s: msg_buf == NULL", f); return (DDI_INTR_UNCLAIMED); } msg->msg_type.type = MBOX_EVENT_DP_ERROR; /* trigger sbbc_dp_trans_event() */ ddi_trigger_softintr(dp_softintr_id); return (DDI_INTR_CLAIMED); } static uint_t sbbc_datapath_fault_msg_handler(char *arg) { static fn_t f = "sbbc_datapath_fault_msg_handler()"; sbbc_msg_t *msg = NULL; if (arg == NULL) { SGSBBC_DBG_EVENT(CE_NOTE, "%s: arg == NULL", f); return (DDI_INTR_UNCLAIMED); } msg = (sbbc_msg_t *)arg; if (msg->msg_buf == NULL) { SGSBBC_DBG_EVENT(CE_NOTE, "%s: msg_buf == NULL", f); return (DDI_INTR_UNCLAIMED); } msg->msg_type.type = MBOX_EVENT_DP_FAULT; /* trigger sbbc_dp_trans_event() */ ddi_trigger_softintr(dp_softintr_id); return (DDI_INTR_CLAIMED); } /* * Log an ECC event message to the SC. This is called from the * sbbc_ecc_mbox_taskq or directly from plat_send_ecc_mailbox_msg * for indictment messages. */ int sbbc_mbox_ecc_output(sbbc_ecc_mbox_t *msgp) { int rv; plat_capability_data_t *cap; plat_dimm_sid_board_data_t *ddata; plat_ecc_msg_hdr_t *hdr; rv = sbbc_mbox_request_response(&msgp->ecc_req, &msgp->ecc_resp, sbbc_mbox_default_timeout); if (rv != 0) { /* * Indictment messages use the return value to indicate a * problem in the mailbox. For Error mailbox messages, we'll * have to use a syslog message. */ if (msgp->ecc_log_error) { if (sbbc_ecc_mbox_send_errs == 0) { cmn_err(CE_NOTE, "!Solaris failed to send a " "message (0x%x/0x%x) to the System " "Controller. Error: %d, Message Status: %d", msgp->ecc_resp.msg_type.type, msgp->ecc_resp.msg_type.sub_type, rv, msgp->ecc_resp.msg_status); } if (++sbbc_ecc_mbox_send_errs >= sbbc_ecc_mbox_err_throttle) { sbbc_ecc_mbox_send_errs = 0; } } } else if (msgp->ecc_resp.msg_status != 0) { if (msgp->ecc_resp.msg_type.type == INFO_MBOX) { switch (msgp->ecc_resp.msg_type.sub_type) { case INFO_MBOX_ECC: hdr = (plat_ecc_msg_hdr_t *) msgp->ecc_req.msg_buf; if (hdr->emh_msg_type == PLAT_ECC_DIMM_SID_MESSAGE) { rv = msgp->ecc_resp.msg_status; break; } /*FALLTHROUGH*/ case INFO_MBOX_ECC_CAP: /* * The positive response comes only * from the AVL FS1 updated SC. * If the firmware is either downgraded * or failover to an older version, then * lets reset the SC capability to * default. */ plat_ecc_capability_sc_set (PLAT_ECC_CAPABILITY_SC_DEFAULT); break; default: break; } } if (msgp->ecc_log_error) { if (sbbc_ecc_mbox_inval_errs == 0) { cmn_err(CE_NOTE, "!An internal error (%d) " "occurred in the System Controller while " "processing this message (0x%x/0x%x)", msgp->ecc_resp.msg_status, msgp->ecc_resp.msg_type.type, msgp->ecc_resp.msg_type.sub_type); } if (msgp->ecc_resp.msg_status == EINVAL) { if (++sbbc_ecc_mbox_inval_errs >= sbbc_ecc_mbox_err_throttle) { sbbc_ecc_mbox_inval_errs = 0; } rv = ENOMSG; } else { if (++sbbc_ecc_mbox_other_errs >= sbbc_ecc_mbox_err_throttle) { sbbc_ecc_mbox_other_errs = 0; } rv = msgp->ecc_resp.msg_status; } } } else { if (msgp->ecc_resp.msg_type.type == INFO_MBOX) { switch (msgp->ecc_resp.msg_type.sub_type) { case INFO_MBOX_ECC_CAP: /* * Successfully received the response * for the capability message, so updating * the SC ECC messaging capability. */ cap = (plat_capability_data_t *) msgp->ecc_resp.msg_buf; plat_ecc_capability_sc_set (cap->capd_capability); break; case INFO_MBOX_ECC: hdr = (plat_ecc_msg_hdr_t *) msgp->ecc_resp.msg_buf; if (hdr && (hdr->emh_msg_type == PLAT_ECC_DIMM_SID_MESSAGE)) { /* * Successfully received a response * to a request for DIMM serial ids. */ ddata = (plat_dimm_sid_board_data_t *) msgp->ecc_resp.msg_buf; (void) plat_store_mem_sids(ddata); } break; default: break; } } } if (msgp->ecc_resp.msg_buf) kmem_free((void *)msgp->ecc_resp.msg_buf, (size_t)msgp->ecc_resp.msg_len); kmem_free((void *)msgp->ecc_req.msg_buf, (size_t)msgp->ecc_req.msg_len); kmem_free(msgp, sizeof (sbbc_ecc_mbox_t)); return (rv); } /* * Enqueue ECC event message on taskq to SC. This is invoked from * plat_send_ecc_mailbox_msg() for each ECC event generating a message. */ void sbbc_mbox_queue_ecc_event(sbbc_ecc_mbox_t *sbbc_ecc_msgp) { /* * Create the ECC event mailbox taskq, if it does not yet exist. * This must be done here rather than in sbbc_mbox_init(). The * sgsbbc driver is loaded very early in the boot flow. Calling * taskq_create() from sbbc_mbox_init could lead to a boot deadlock. * * There might be a tiny probability that two ECC handlers on * different processors could arrive here simultaneously. If * the taskq has not been created previously, then these two * simultaneous events could cause the creation of an extra taskq. * Given the extremely small likelihood (if not outright impossibility) * of this occurrence, sbbc_ecc_mbox_taskq is not protected by a lock. */ if (sbbc_ecc_mbox_taskq == NULL) { sbbc_ecc_mbox_taskq = taskq_create("ECC_event_mailbox", 1, minclsyspri, ECC_MBOX_TASKQ_MIN, ECC_MBOX_TASKQ_MAX, TASKQ_PREPOPULATE); if (sbbc_ecc_mbox_taskq == NULL) { if (sbbc_ecc_mbox_taskq_errs == 0) { cmn_err(CE_NOTE, "Unable to create mailbox " "task queue for ECC event logging to " "System Controller"); } if (++sbbc_ecc_mbox_taskq_errs >= sbbc_ecc_mbox_err_throttle) { sbbc_ecc_mbox_taskq_errs = 0; } kmem_free((void *)sbbc_ecc_msgp->ecc_req.msg_buf, (size_t)sbbc_ecc_msgp->ecc_req.msg_len); kmem_free((void *)sbbc_ecc_msgp, sizeof (sbbc_ecc_mbox_t)); return; } /* * Reset error counter so that first taskq_dispatch * error will be output */ sbbc_ecc_mbox_taskq_errs = 0; } /* * Enqueue the message */ if (taskq_dispatch(sbbc_ecc_mbox_taskq, (task_func_t *)sbbc_mbox_ecc_output, sbbc_ecc_msgp, TQ_NOSLEEP) == TASKQID_INVALID) { if (sbbc_ecc_mbox_taskq_errs == 0) { cmn_err(CE_NOTE, "Unable to send ECC event " "message to System Controller"); } if (++sbbc_ecc_mbox_taskq_errs >= sbbc_ecc_mbox_err_throttle) { sbbc_ecc_mbox_taskq_errs = 0; } kmem_free((void *)sbbc_ecc_msgp->ecc_req.msg_buf, (size_t)sbbc_ecc_msgp->ecc_req.msg_len); kmem_free((void *)sbbc_ecc_msgp, sizeof (sbbc_ecc_mbox_t)); } } static uint_t cap_ecc_msg_handler(char *addr) { sbbc_msg_t *msg = NULL; plat_capability_data_t *cap = NULL; static fn_t f = "cap_ecc_msg_handler"; msg = (sbbc_msg_t *)addr; if (msg == NULL) { SGSBBC_DBG_EVENT(CE_WARN, "cap_ecc_msg_handler() called with " "null addr"); return (DDI_INTR_CLAIMED); } if (msg->msg_buf == NULL) { SGSBBC_DBG_EVENT(CE_WARN, "cap_ecc_msg_handler() called with " "null data buffer"); return (DDI_INTR_CLAIMED); } cap = (plat_capability_data_t *)msg->msg_buf; switch (cap->capd_msg_type) { case PLAT_ECC_CAPABILITY_MESSAGE: SGSBBC_DBG_MBOX("%s: capability 0x%x\n", f, cap->capd_capability); plat_ecc_capability_sc_set(cap->capd_capability); break; default: SGSBBC_DBG_MBOX("%s: Unknown message type = 0x%x\n", f, cap->capd_msg_type); break; } return (DDI_INTR_CLAIMED); }