sghsc.c revision 2bc987325e3ded1865bff043128661815c4690b9
/*
* 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
* 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.
*/
/*
*
* Serengeti CompactPCI Hot Swap Controller Driver.
*
*/
#include <sys/serengeti.h>
/*
* Debug flags
*/
int sghsc_configure_ack = 0;
int cpci_enable = 1;
#ifdef DEBUG
#define SGHSC_DEBUG
#endif
#ifdef SGHSC_DEBUG
int sghsc_debug = 0;
#define DEBUGOFF sghsc_debug = 0
#else
#define DEBUGON
#define DEBUGOFF
#endif
/*
* Global data
*/
static void *sghsc_state; /* soft state */
/*
* Definitions for events thread (outside interrupt context), mutex and
* condition variable.
*/
static kthread_t *sghsc_event_thread;
static kmutex_t sghsc_event_thread_mutex;
static kcondvar_t sghsc_event_thread_cv;
static struct cb_ops sghsc_cb_ops = {
nodev, /* open */
nodev, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
nodev, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop_op */
0, /* streamtab */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
/*
* Function prototype for dev_ops
*/
static struct dev_ops sghsc_dev_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
nulldev, /* get_dev_info */
nulldev, /* identify */
nulldev, /* probe */
sghsc_attach, /* attach */
sghsc_detach, /* detach */
nodev, /* reset */
&sghsc_cb_ops, /* driver operations */
(struct bus_ops *)0, /* no bus operations */
NULL, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
"Serengeti CompactPCI HSC",
};
static struct modlinkage modlinkage = {
&modldrv,
};
/*
* Function prototype for HP support
*/
/*
* Function prototypes for internal functions
*/
static int sghsc_register_slots(sghsc_t *, int);
static int sghsc_scctl(int, int, int, int, int *);
static void sghsc_freemem(sghsc_t *);
static hpc_slot_t sghsc_find_sloth(int, int, int);
static sghsc_t *sghsc_find_softstate(int, int, int);
static void sghsc_rb_setup(sghsc_rb_head_t *);
static void sghsc_rb_teardown(sghsc_rb_head_t *);
/*
* Patchable timeout value
*/
/*
* Data for self-identification. This will help enumerate all soft states.
*/
static int sghsc_maxinst;
/*
* Six slot boat and four slot boats are different in topology (slot to
* bus assignment) and here we should have 2 separate maps (the first 3
* slots have the same topology). The map is in the "delta" form. Logical
* slots correspond to indexes in the map.
*/
static sdesc_t four_slot_wib_bd[] = {
};
static sdesc_t four_slot_bd[] = {
};
static sdesc_t six_slot_wib_bd[] = {
};
static sdesc_t six_slot_bd[] = {
};
/*
* DR event handlers
* We want to register the event handlers once for all instances. In the
* other hand we have register them after the sghsc has been attached.
* event_initialize gives us the logic of only registering the events only
* once. The event thread will do all the work when called from interrupts.
*/
int sghsc_event_init = 0;
static uint_t sghsc_event_handler(char *);
static void sghsc_event_thread_code(void);
/*
* DR event msg and payload
*/
static sbbc_msg_t event_msg;
static sghsc_event_t payload;
/*
* Event lock and state
*/
static kmutex_t sghsc_event_lock;
int sghsc_event_state;
int
_init(void)
{
int error;
sghsc_maxinst = 0;
sizeof (sghsc_t), 1)) != 0)
return (error);
return (error);
}
return (error);
}
int
_fini(void)
{
int error;
return (error);
/*
* Unregister the event handler
*/
/*
* Kill the event thread if it is running.
*/
if (sghsc_event_thread != NULL) {
/*
* Goes to the thread at once.
*/
/*
* Waiting for the response from the thread.
*/
}
/*
* tear down shared, global ring buffer now that it is safe to
* do so because sghsc_event_handler has been unregistered and
* sghsc_event_thread_code has exited
*/
sghsc_maxinst = 0;
return (0);
}
int
{
}
/*
* sghsc_attach()
*/
/* ARGSUSED */
static int
{
int rc;
int board_type = 0;
switch (cmd) {
case DDI_RESUME:
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
default:
return (DDI_FAILURE);
}
/* Fetch Safari Extended Agent ID of this device. */
if (!SG_PORTID_IS_IO_TYPE(portid)) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
if (rc) {
goto cleanup_stage2;
}
sghsc->sghsc_num_slots));
switch (sghsc->sghsc_num_slots) {
case 4:
case 6:
rc = 0;
break;
default:
rc = -1;
break;
}
if (rc) {
goto cleanup_stage2;
}
" failed for node %d / board %d",
goto cleanup;
}
!= HPC_SUCCESS) {
sghsc->sghsc_board));
goto cleanup;
}
if (sghsc_event_init == 0) {
/*
* allocate shared, global ring buffer before registering
* sghsc_event_handler and before starting
* sghsc_event_thread_code
*/
/*
* Regiter cpci DR event handler
*
*/
if (rc != 0)
sghsc_event_init = 1;
/*
* Create the event thread if it is not already created.
*/
if (sghsc_event_thread == NULL) {
}
}
/*
* Grossly bump up the instance counter. We may have holes inside.
*/
return (DDI_SUCCESS);
/*
* Free up allocated resources and return error
* sghsc_register_slots => unregister all slots
*/
return (DDI_FAILURE);
}
/*
* detach(9E)
*/
/* ARGSUSED */
static int
{
int instance;
int i;
return (DDI_FAILURE);
switch (cmd) {
case DDI_DETACH:
/*
* We don't allow to detach in case the pci nexus
* didn't run pcihp_uninit(). The buses should be
* unregistered by now, otherwise slot info will be
* corrupted on the next 'cfgadm'.
*/
for (i = 0; i < sghsc->sghsc_num_slots; i++) {
"sghsc: must detach buses first");
return (DDI_FAILURE);
}
}
if (mutex_tryenter(&sghsc_event_thread_mutex) == 0)
return (EBUSY);
sghsc->sghsc_valid = 0;
/*
* Grossly decrement the counter. We may have holes inside.
*/
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*
* Set up and register slot 0 to num_slots with hotplug
* framework
* Assume SGHSC_MUTEX is held
*
* Return val: DDI_SUCCESS
* DDI_FAILURE
*/
static int
{
int i;
if ((cpci_enable == 0) || (sg_prom_cpci_dr_check() != 0))
return (DDI_SUCCESS);
return (DDI_FAILURE);
switch (board_type) {
/*
* If the GET_CPCI_BOARD_TYPE request failed, board type
* will be NO_BOARD_TYPE. In that case, assume it is an
* io boat and make board type determination based on the
* number of slots.
*/
case NO_BOARD_TYPE:
case CPCI_BOARD:
case SP_CPCI_BOARD:
switch (sghsc->sghsc_num_slots) {
case 4:
break;
case 6:
break;
default:
" node %d / board %d",
break;
}
break;
case WCI_CPCI_BOARD:
break;
case WCI_SP_CPCI_BOARD:
break;
default:
sghsc->sghsc_board);
return (DDI_FAILURE);
}
/*
* constructing the slot table array and register the
* slot with the HPS
* we don't depend on the .conf file
*/
for (i = 0; i < sghsc->sghsc_num_slots; i++) {
char *nexuspath;
/*
* Some kind of black list may be needed
*/
/*
* Need to talk to SC and get slot info and set slot state:
* 1. slot status
* 2. slot capabilities
* 3. LED status
* 4. get bus num
*/
/*
* fill up nexuspath, extended id is used instead of the
* local one, the node id is encoded in the path twice.
*/
/*
* fill up slot_info
*/
/* capabilities need to be discovered via SC */
sghsc->sghsc_board));
/*
* allocate and fill up slot_ops
*/
/* assign slot ops for HPS */
/*
* HA (Full Hot Swap) is the default mode of operation
* but the type of the board is set conservstively as
* sghsc has no way of knowing it. The HP Framwork will
* overwrite the value set at boot time.
*/
/* Only register CPCI slots */
"slot %d is non-cpci", i));
continue;
}
/*
* register slots
*/
/*
* return failure and let attach()
* do the cleanup
*/
" registration process for node %d / board %d",
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* Connecting a slot or all slots
* State Diagram:
* states
* hw bits EMPTY DISCONNECT CONNECT
* slot_enable NO NO YES
* card_present NO YES YES
*
* Return val: HPC_SUCCESS if the slot(s) are enabled
* HPC_ERR_FAILED if the slot can't be enabled
*/
/* ARGSUSED */
static int
{
int i = 0;
int rc;
int result;
switch (flag) {
case SGHSC_ALL_SLOTS_ENABLE:
for (i = 0; i < sghsc->sghsc_num_slots; i++) {
/*
* All slots will be marked 'empty' as HP Framework
* will try to connect those which have no kernel node.
*/
}
return (HPC_SUCCESS);
}
if (slot_num == -1)
return (HPC_ERR_INVALID);
/*
* Powering an empty slot is highly illegal so far
* (before SC implemented a constant poll). Otherwise
* it breaks ddi framework and HP. The workaround
* is to check for a card first.
*/
return (HPC_ERR_FAILED);
}
if (rc) {
return (HPC_ERR_FAILED);
}
return (HPC_ERR_FAILED);
}
if (rc) {
return (HPC_ERR_FAILED);
} else {
}
return (HPC_SUCCESS);
}
/*
* Disconnecting a slot or slots
*
* return: HPC_SUCCESS if slot(s) are successfully disconnected
* HPC_ERR_FAILED if slot(s) can't be disconnected
*
*/
/* ARGSUSED */
static int
{
int rc;
int result;
switch (flag) {
case SGHSC_ALL_SLOTS_DISABLE:
return (HPC_SUCCESS);
}
if (slot_num == -1)
return (HPC_ERR_INVALID);
/*
* Disconnecting an empty or disconnected slot
* does't make sense.
*/
return (HPC_SUCCESS);
}
if (rc) {
return (HPC_ERR_FAILED);
} else {
}
return (HPC_SUCCESS);
}
/*
* Entry point from the hotplug framework to do
* the main hotplug operations
* Return val: HPC_SUCCESS success on ops
* HPC_NOT_SUPPORTED not supported feature
* HPC_ERR_FAILED ops failed
*/
/*ARGSUSED*/
static int
{
int error = HPC_SUCCESS;
int rc;
int result;
" max = %d, sloth = 0x%p for node %d / board %d",
return (HPC_ERR_INVALID);
}
switch (request) {
case HPC_CTRL_GET_LED_STATE: {
/* arg == hpc_led_info_t */
" HPC_CTRL_GET_LED_STATE for node %d / board %d slot %d",
case HPC_POWER_LED:
case HPC_ATTN_LED:
case HPC_FAULT_LED:
case HPC_ACTIVE_LED:
break;
default:
" HPC_CTRL_GET_LED_STATE "
" unknown led state %d for node %d / board %d"
break;
}
break;
}
case HPC_CTRL_SET_LED_STATE: {
/* arg == hpc_led_info_t */
" HPC_CTRL_SET_LED_STATE for node %d / board %d slot %d",
case HPC_POWER_LED:
case HPC_ATTN_LED:
case HPC_FAULT_LED:
case HPC_ACTIVE_LED:
" LED writing not supported "));
break;
default:
" LED not supported "));
}
break;
}
case HPC_CTRL_GET_SLOT_STATE: {
" HPC_CTRL_GET_SLOT_STATE for node %d / board %d slot %d",
/*
* Send mailbox cmd to SC to query the latest state
*/
break;
}
if (rc) {
break;
}
/*
* Update the cached state if needed. Initally all
* slots are marked as empty for the Hot Plug Framwork.
*/
}
/*
* No change
*/
*(hpc_slot_state_t *)arg =
break;
}
case HPC_CTRL_DEV_CONFIGURED:
" HPC_CTRL_DEV_CONFIGURED for node %d / board %d slot %d",
if (sghsc_configure_ack)
" node %d / board %d slot %d configured",
/*
* This is important to tell SC:
* "start looking for ENUMs"
*/
(void) sghsc_scctl(SGHSC_SET_ENUM_CLEARED,
break;
/*
* due to unclean drivers, unconfigure may leave
* some state on card, configure may actually
* use these invalid values. therefore, may force
* disconnect.
*/
"HPC_CTRL_DEV_UNCONFIGURED for node %d / board %d slot %d",
0) != HPC_SUCCESS) {
"disconnect failed"));
}
return (error);
case HPC_CTRL_GET_BOARD_TYPE: {
/* arg = hpc_board_type_t */
" HPC_CTRL_GET_BOARD_TYPE for node %d / board %d slot %d",
*(hpc_board_type_t *)arg =
break;
}
case HPC_CTRL_ENABLE_AUTOCFG:
" HPC_CTRL_ENABLE_AUTOCFG for node %d / board %d slot %d",
/*
* Tell SC to start looking for ENUMs on this slot.
*/
if (rc)
" node %d / board %d, slot %d",
break;
case HPC_CTRL_DISABLE_AUTOCFG:
" HPC_CTRL_DISABLE_AUTOCFG for node %d / board %d slot %d",
break;
case HPC_CTRL_DISABLE_SLOT:
case HPC_CTRL_ENABLE_SLOT:
break;
/* need to add support for enable/disable_ENUM */
case HPC_CTRL_DISABLE_ENUM:
case HPC_CTRL_ENABLE_ENUM:
default:
request));
/* invalid request */
}
return (error);
}
/*
* Assume MUTEX_HELD
*
* return: HPC_SUCCESS if the led's status is avaiable,
* SC return status otherwise.
*/
static int
{
int rval;
int slot_num;
int result;
if (rval != HPC_SUCCESS)
return (rval);
switch (op) {
case HPC_CTRL_GET_LED_STATE:
case HPC_POWER_LED:
else
break;
case HPC_ATTN_LED:
case HPC_FAULT_LED:
else
break;
case HPC_ACTIVE_LED:
else
break;
}
break;
case HPC_CTRL_SET_LED_STATE:
return (HPC_ERR_NOTSUPPORTED);
}
return (HPC_SUCCESS);
}
/*
* sghsc_get_slotnum()
* get slot number from the slot handle
* returns non-negative value to indicate slot number
* -1 for failure
*/
static int
{
int i;
return (-1);
for (i = 0; i < sghsc->sghsc_num_slots; i++) {
return (i);
}
return (-1);
}
/*
* sghsc_scctl()
* mailbox interface
*
* return result code from mailbox operation
*/
static int
{
int ret = 0xbee;
switch (cmd) {
case SGHSC_GET_SLOT_STATUS:
break;
case SGHSC_GET_NUM_SLOTS:
break;
break;
break;
break;
break;
break;
break;
case SGHSC_SET_SLOT_POWER_OFF:
break;
case SGHSC_SET_SLOT_POWER_ON:
break;
break;
case SGHSC_SET_ENUM_CLEARED:
break;
default:
cmd);
}
"sghsc: sending mbox command type=%d subtype=0x%x size=%d buf=%p",
"sghsc: sending buf cmd_id=0x%x node_id=0x%x board=0x%x "
/*
* The resp->msg_status field may contain an SC error or a common
* error such as ETIMEDOUT.
*/
return (-1);
}
reqp->msg_status));
resp->msg_status));
#ifdef DEBUG_EXTENDED
if (cmd == SGHSC_GET_NUM_SLOTS) {
cmd_info_r_p->result));
return (0);
}
if (tmp)
"sghsc: slot condition(hot swap status) is 0x%x", tmp));
#endif
return (0);
}
/*
* sghsc_freemem()
* deallocates memory resources
*
*/
static void
{
int i;
/*
* Free up allocated resources
* sghsc_register_slots => unregister all slots
*/
for (i = 0; i < sghsc->sghsc_num_slots; i++) {
(void) hpc_slot_unregister(
}
/* finally free up slot_table */
}
/*
* sghsc_find_sloth()
* Find slot handle by node id, board number and slot numbert
* Returns slot handle or 0 if slot not found.
*/
static hpc_slot_t
{
int instance;
continue;
return (NULL);
}
if (sghsc->sghsc_valid == 0)
return (NULL);
/*
* Found matching slot, return handle.
*/
}
return (NULL);
}
/*
* sghsc_event_handler()
* Event Handler. This is what for other platforms was an interrupt
* Handler servicing events. It accepts an event and signals it to
* non-interrupt thread.
*/
sghsc_event_handler(char *arg)
{
("sghsc: sghsc_event_handler argument is null\n"));
return (DDI_INTR_CLAIMED);
}
/*
* On a board disconnect sghsc soft state may not exist
* when the interrupt occurs. We should treat these
* interrupts as noise and but them.
*/
" node %d / board %d slot %d. CPCI event rejected",
return (DDI_INTR_CLAIMED);
}
if (enum_state == NULL) {
return (DDI_INTR_UNCLAIMED);
}
case SGHSC_EVENT_CARD_INSERT:
break;
case SGHSC_EVENT_CARD_REMOVE:
return (DDI_INTR_CLAIMED);
case SGHSC_EVENT_POWER_ON:
return (DDI_INTR_CLAIMED);
case SGHSC_EVENT_POWER_OFF:
return (DDI_INTR_CLAIMED);
case SGHSC_EVENT_HEALTHY_LOST:
return (DDI_INTR_CLAIMED);
case SGHSC_EVENT_LEVER_ACTION:
break;
default:
return (DDI_INTR_CLAIMED);
}
/*
* Signal the ENUM event to the non-interrupt thread as the Hot
* Plug Framework will eventually call sghsc_control() but all
* the mailbox messages are not allowed from interrupt context.
*/
return (DDI_INTR_UNCLAIMED);
}
return (DDI_INTR_CLAIMED);
}
/*
* sghsc_event_thread_code()
* Event Thread. This is non-interrupt thread servicing #ENUM, Insert,
*/
static void
sghsc_event_thread_code(void)
{
int rc;
int result;
for (;;) {
/*
* Wait for Event handler to signal event or self destruction.
* Assuming the mutex will be automatically reaccuired.
*/
break;
/*
* Pick up all the relevant events from the ring buffer.
*/
DDI_SUCCESS) {
continue;
continue;
continue;
/*
* Insert event leads only to the electrical
* connection.
*/
NULL, 0);
if (rc != HPC_SUCCESS)
" could not connect inserted card,"
" node %d / board %d slot %d",
continue;
}
/*
* ENUM event received.
* Reset ENUM and notify SC to poll for the next one.
*/
if (rc == HPC_EVENT_UNCLAIMED) {
"sghsc: unable to clear ENUM"));
continue;
}
if (rc) {
"sghsc: unable to ACK cleared ENUM"));
continue;
}
/*
* process the ENUM.
*/
if (rc == HPC_EVENT_UNCLAIMED) {
"sghsc: could not process ENUM"));
}
}
}
thread_exit();
}
/*
* sghsc_find_softstate()
* Find softstate by node id and board number. Slot number is used for
* verification.
* Returns board's softstate or 0 if not found.
*/
static sghsc_t *
{
int instance;
continue;
"slot data corruption", instance);
return (NULL);
}
if (sghsc->sghsc_valid == 0)
return (NULL);
/*
* Found matching data, return soft state.
*/
return (sghsc);
}
return (NULL);
}
/*
* sghsc_rb_setup()
* Initialize the event ring buffer with a fixed size. It may require
* a more elaborate scheme with buffer extension
*/
static void
{
/*
* Allocate space for event ring buffer
*/
}
}
/*
* sghsc_rb_teardown()
* Free event ring buffer resources.
*/
static void
{
/*
* Deallocate space for event ring buffer
*/
}
}
/*
* sghsc_rb_put()
* Insert an event info into the event ring buffer.
* Returns DDI_FAILURE if the buffer is full, DDI_SUCCESS otherwise
*/
static int
{
return (DDI_FAILURE);
else
return (DDI_SUCCESS);
}
/*
* sghsc_rb_get()
* Remove an event info from the event ring buffer.
* Returns DDI_FAILURE if the buffer is empty, DDI_SUCCESS otherwise.
*/
static int
{
return (DDI_FAILURE);
else
return (DDI_SUCCESS);
}