/*
* 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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <config_admin.h>
#include <strings.h>
#include <syslog.h>
#include <libsysevent.h>
#include <libdevinfo.h>
#include <libnvpair.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <stropts.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysevent/dr.h>
#include <sys/scfd/opcioif.h>
/* Macros */
#define SCF_DEV_DIR "/devices" /* device base dir */
/*
* Connection for SCF driver
*/
/* Check the availability of SCF driver */
static int scfdrv_enable = 0;
/* Device for SCF Driver */
#define SCFIOCDEV "/devices/pseudo/scfd@200:rasctl"
#define SCFRETRY 10
#define SCFIOCWAIT 3
#define SCFDATA_DEV_INFO 32
#define SCFDATA_APID 1054
/*
* Data for XSCF
* Note the size of the ap_id must be SCFDATA_APID for proper data alignment
* for the ioctl. The SCF has a corresponding data structure which is matched
* here.
*/
typedef struct {
char ap_id[SCFDATA_APID];
uint8_t ioua;
uint8_t vflag;
uint32_t r_state;
uint32_t o_state;
uint64_t tstamp;
char dev_name[SCFDATA_DEV_INFO];
char dev_model[SCFDATA_DEV_INFO];
} scf_slotinfo_t;
/*
* Data for scf notification of state changes.
* pci_name is an ap_id phys path for the hot pluggable pci device.
* r_state is the recepticle state.
* o_state is the occupant state.
* cache_fmri_str is a string representation of an fmri in the rsrc cache.
* fmri_asru_str is the asru for an fmri which is found in the topology.
* found is a boolean indicating whether the device was found in the topology.
*/
typedef struct {
char pci_name[MAXPATHLEN];
uint32_t r_state;
uint32_t o_state;
} pci_notify_t;
/*
* Function Prototypes
*/
void scf_get_slotinfo(char *ap_id, cfga_stat_t *o_state,
cfga_stat_t *r_state);
static int scf_get_pci_name(const char *ap_phys_id, char *pci_name);
static int scf_get_devinfo(char *dev_name, char *dev_model,
const char *pci_name);
void notify_scf_of_hotplug(sysevent_t *ev);
/*
* Error report utility for libcfgadm functions
*/
void
config_error(cfga_err_t err, const char *func_name, const char *errstr,
const char *ap_id)
{
const char *ep;
ep = config_strerror(err);
if (ep == NULL) {
ep = "configuration administration unknown error";
}
if (errstr != NULL && *errstr != '\0') {
syslog(LOG_DEBUG, "%s: %s (%s), ap_id = %s\n",
func_name, ep, errstr, ap_id);
} else {
syslog(LOG_DEBUG, "%s: %s , ap_id = %s\n",
func_name, ep, ap_id);
}
}
/*
* Get the slot status.
*/
void
scf_get_slotinfo(char *ap_pid, cfga_stat_t *r_state, cfga_stat_t *o_state)
{
cfga_err_t rv; /* return value */
cfga_list_data_t *stat = NULL; /* slot info. */
int nlist; /* number of slot */
char *errstr = NULL; /* error code */
/*
* Get the attachment point information.
*/
rv = config_list_ext(1, (char *const *)&ap_pid, &stat, &nlist, NULL,
NULL, &errstr, 0);
if (rv != CFGA_OK) {
config_error(rv, "config_list_ext", errstr, ap_pid);
goto out;
}
assert(nlist == 1);
syslog(LOG_DEBUG, "\n"
"ap_log_id = %.*s\n"
"ap_phys_id = %.*s\n"
"ap_r_state = %d\n"
"ap_o_state = %d\n"
"ap_cond = %d\n"
"ap_busy = %6d\n"
"ap_status_time = %s"
"ap_info = %.*s\n"
"ap_type = %.*s\n",
sizeof (stat->ap_log_id), stat->ap_log_id,
sizeof (stat->ap_phys_id), stat->ap_phys_id,
stat->ap_r_state,
stat->ap_o_state,
stat->ap_cond,
stat->ap_busy,
asctime(localtime(&stat->ap_status_time)),
sizeof (stat->ap_info), stat->ap_info,
sizeof (stat->ap_type), stat->ap_type);
/* Copy the slot status. */
*r_state = stat->ap_r_state;
*o_state = stat->ap_o_state;
out:
if (stat) {
free(stat);
}
if (errstr) {
free(errstr);
}
}
/*
* Get the pci_name
*/
static int
scf_get_pci_name(const char *ap_phys_id, char *pci_name)
{
char *pci_name_ptr; /* pci node name pointer */
char *ap_lid_ptr; /* logical ap_id pointer */
int devices_len; /* "/device" length */
int pci_name_len; /* pci node name length */
int ap_lid_len; /* logical ap_id pointer */
/*
* Pick pci node name up from physical ap_id string.
* "/devices/pci@XX,YYYYYY:PCI#ZZ"
*/
/* Check the length of physical ap_id string */
if (strlen(ap_phys_id) >= MAXPATHLEN) {
return (-1); /* changed */
}
/* Check the pci node name start, which is after "/devices". */
if (strncmp(SCF_DEV_DIR, ap_phys_id, strlen(SCF_DEV_DIR)) == 0) {
devices_len = strlen(SCF_DEV_DIR);
} else {
devices_len = 0;
}
/* Check the pci node name end, which is before ":". */
if ((ap_lid_ptr = strchr(ap_phys_id, ':')) == NULL) {
ap_lid_len = 0;
} else {
ap_lid_len = strlen(ap_lid_ptr);
}
/*
* Get the head of pci node name string.
* Get the length of pci node name string.
*/
pci_name_ptr = (char *)ap_phys_id + devices_len;
pci_name_len = strlen(ap_phys_id) - devices_len - ap_lid_len;
/* Copy the pci node name. */
(void) strncpy(pci_name, pci_name_ptr, pci_name_len);
pci_name[pci_name_len] = '\0';
syslog(LOG_DEBUG, "pci device path = %s\n", pci_name);
return (0);
}
/*
* Get the property of name and model.
*/
static int
scf_get_devinfo(char *dev_name, char *dev_model, const char *pci_name)
{
char *tmp; /* tmp */
unsigned int devid, funcid; /* bus addr */
unsigned int sdevid, sfuncid; /* sibling bus addr */
di_node_t pci_node; /* pci device node */
di_node_t child_node; /* child level node */
di_node_t ap_node; /* hotplugged node */
pci_node = ap_node = DI_NODE_NIL;
/*
* Take the snap shot of device node configuration,
* to get the names of node and model.
*/
if ((pci_node = di_init(pci_name, DINFOCPYALL)) == DI_NODE_NIL) {
syslog(LOG_NOTICE,
"Could not get dev info snapshot. errno=%d\n",
errno);
return (-1); /* changed */
}
/*
* The new child under pci node should be added. Then the
* device and model names should be passed, which is in the
* node with the minimum bus address.
*
* - Move to the child node level.
* - Search the node with the minimum bus addrress in the
* sibling list.
*/
if ((child_node = di_child_node(pci_node)) == DI_NODE_NIL) {
syslog(LOG_NOTICE, "No slot device in snapshot\n");
goto out;
}
ap_node = child_node;
if ((tmp = di_bus_addr(child_node)) != NULL) {
if (sscanf(tmp, "%x,%x", &devid, &funcid) != 2) {
funcid = 0;
if (sscanf(tmp, "%x", &devid) != 1) {
devid = 0;
syslog(LOG_DEBUG,
"no bus addrress on device\n");
goto one_child;
}
}
}
while ((child_node = di_sibling_node(child_node)) != NULL) {
if ((tmp = di_bus_addr(child_node)) == NULL) {
ap_node = child_node;
break;
}
if (sscanf(tmp, "%x,%x", &sdevid, &sfuncid) == 2) {
/*
* We do need to update the child node
* Case 1. devid > sdevid
* Case 2. devid == sdevid && funcid > sfuncid
*/
if ((devid > sdevid) || ((devid == sdevid) &&
(funcid > sfuncid))) {
ap_node = child_node;
devid = sdevid;
funcid = sfuncid;
}
} else if (sscanf(tmp, "%x", &sdevid) == 1) {
/*
* We do need to update the child node
* Case 1. devid >= sdevid
*/
if (devid >= sdevid) {
ap_node = child_node;
devid = sdevid;
funcid = 0;
}
} else {
ap_node = child_node;
break;
}
}
one_child:
/*
* Get the name and model properties.
*/
tmp = di_node_name(ap_node);
if (tmp != NULL) {
(void) strlcpy((char *)dev_name, tmp, SCFDATA_DEV_INFO);
}
tmp = NULL;
if (di_prop_lookup_strings(DDI_DEV_T_ANY, ap_node, "model", &tmp) > 0) {
if (tmp != NULL) {
(void) strlcpy((char *)dev_model, tmp,
SCFDATA_DEV_INFO);
}
}
syslog(LOG_DEBUG, "device: %s@%x,%x [model: %s]\n",
dev_name, devid, funcid, dev_model);
out:
di_fini(pci_node);
return (0); /* added */
}
void
notify_scf_of_hotplug(sysevent_t *ev)
{
int rc; /* return code */
/* For libsysevent */
char *vendor = NULL; /* event vendor */
char *publisher = NULL; /* event publisher */
nvlist_t *ev_attr_list = NULL; /* attribute */
/* For libcfgadm */
char *ap_id = NULL; /* attachment point */
cfga_stat_t r_state, o_state; /* slot status */
/* For libdevinfo */
char dev_name[SCFDATA_DEV_INFO]; /* name property */
char dev_model[SCFDATA_DEV_INFO]; /* model property */
/* Data for SCF */
pci_notify_t pci_notify_dev_info;
scfsetphpinfo_t scfdata;
scf_slotinfo_t sdata;
time_t sec; /* hotplug event current time */
int fd, retry = 0;
/*
* Initialization
*/
r_state = o_state = 0;
dev_name[0] = dev_model[0] = '\0';
(void) memset((void *)&pci_notify_dev_info, 0, sizeof (pci_notify_t));
/* Get the current time when event picked up. */
sec = time(NULL);
/*
* Check the vendor and publisher name of event.
*/
vendor = sysevent_get_vendor_name(ev);
publisher = sysevent_get_pub_name(ev);
/* Check the vendor is "SUNW" */
if (strncmp("SUNW", vendor, strlen("SUNW")) != 0) {
/* Just return when not from SUNW */
syslog(LOG_DEBUG, "Event is not a SUNW vendor event\n");
goto out;
}
/* Enough to check "px" and "pcieb" at the beginning of string */
if (strncmp("px", publisher, strlen("px")) != 0 &&
strncmp("pcieb", publisher, strlen("pcieb")) != 0) {
/* Just return when not px event */
syslog(LOG_DEBUG, "Event is not a px publisher event\n");
goto out;
}
/*
* Get attribute values of attachment point.
*/
if (sysevent_get_attr_list(ev, &ev_attr_list) != 0) {
/* could not get attribute list */
syslog(LOG_DEBUG, "Could not get attribute list\n");
goto out;
}
if (nvlist_lookup_string(ev_attr_list, DR_AP_ID, &ap_id) != 0) {
/* could not find the attribute from the list */
syslog(LOG_DEBUG, "Could not get ap_id in attribute list\n");
goto out;
}
if (ap_id == NULL || strlen(ap_id) == 0) {
syslog(LOG_DEBUG, "ap_id is NULL\n");
goto out;
} else {
/*
* Get the slot status.
*/
syslog(LOG_DEBUG, "ap_id = %s\n", ap_id);
scf_get_slotinfo(ap_id, &r_state, &o_state);
}
syslog(LOG_DEBUG, "r_state = %d\n", r_state);
syslog(LOG_DEBUG, "o_state = %d\n", o_state);
/*
* Get the pci name which is needed for both the configure and
* unconfigure.
*/
rc = scf_get_pci_name(ap_id, (char *)pci_notify_dev_info.pci_name);
if (rc != 0) {
goto out;
}
/*
* Event for configure case only,
* Get the name and model property
*/
if (o_state == CFGA_STAT_CONFIGURED) {
rc = scf_get_devinfo(dev_name, dev_model,
(char *)pci_notify_dev_info.pci_name);
if (rc != 0) {
goto out;
}
}
/*
* Copy the data for SCF.
* Initialize Data passed to SCF Driver.
*/
(void) memset(scfdata.buf, 0, sizeof (scfdata.buf));
/*
* Set Data passed to SCF Driver.
*/
scfdata.size = sizeof (scf_slotinfo_t);
(void) strlcpy(sdata.ap_id, ap_id, sizeof (sdata.ap_id));
sdata.vflag = (uint8_t)0x80;
sdata.r_state = (uint32_t)r_state;
sdata.o_state = (uint32_t)o_state;
sdata.tstamp = (uint64_t)sec;
(void) strlcpy(sdata.dev_name, dev_name, sizeof (dev_name));
(void) strlcpy(sdata.dev_model, dev_model, sizeof (sdata.dev_model));
(void) memcpy((void *)&(scfdata.buf), (void *)&sdata,
sizeof (scf_slotinfo_t));
pci_notify_dev_info.r_state = (uint32_t)r_state;
pci_notify_dev_info.o_state = (uint32_t)o_state;
if (!scfdrv_enable) {
scfdrv_enable = 1;
/*
* Pass data to SCF driver by ioctl.
*/
if ((fd = open(SCFIOCDEV, O_WRONLY)) < 0) {
syslog(LOG_ERR, "open %s fail", SCFIOCDEV);
scfdrv_enable = 0;
goto out;
}
while (ioctl(fd, SCFIOCSETPHPINFO, scfdata) < 0) {
/* retry a few times for EBUSY and EIO */
if ((++retry <= SCFRETRY) &&
((errno == EBUSY) || (errno == EIO))) {
(void) sleep(SCFIOCWAIT);
continue;
}
syslog(LOG_ERR, "SCFIOCSETPHPINFO failed: %s.",
strerror(errno));
break;
}
(void) close(fd);
scfdrv_enable = 0;
}
out:
if (vendor != NULL) {
free(vendor);
}
if (publisher != NULL) {
free(publisher);
}
if (ev_attr_list != NULL) {
nvlist_free(ev_attr_list);
}
}