dr_cpu.c revision f500b19684bd0346ac05bec02a50af07f369da1a
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* sun4v CPU DR Module
*/
#include <sys/processor.h>
#include <sys/hypervisor_api.h>
#include <sys/mach_descrip.h>
#include <sys/machsystm.h>
"sun4v CPU DR"
};
static struct modlinkage modlinkage = {
(void *)&modlmisc,
};
/*
* Global DS Handle
*/
static ds_svc_hdl_t ds_handle;
/*
* Supported DS Capability Versions
*/
/*
* DS Capability Description
*/
static ds_capability_t dr_cpu_cap = {
DR_CPU_DS_ID, /* svc_id */
dr_cpu_vers, /* vers */
DR_CPU_NVERS /* nvers */
};
/*
* DS Callbacks
*/
/*
* DS Client Ops Vector
*/
static ds_clnt_ops_t dr_cpu_ops = {
dr_cpu_reg_handler, /* ds_reg_cb */
dr_cpu_unreg_handler, /* ds_unreg_cb */
dr_cpu_data_handler, /* ds_data_cb */
NULL /* cb_arg */
};
/*
* Operation Results
*
* Used internally to gather results while an operation on a
* list of CPUs is in progress. In particular, it is used to
* keep track of which CPUs have already failed so that they are
* not processed further, and the manner in which they failed.
*/
typedef struct {
char *string;
} dr_cpu_res_t;
/*
* Internal Functions
*/
static int dr_cpu_init(void);
static int dr_cpu_fini(void);
dr_cpu_hdr_t **respp);
int
_init(void)
{
int status;
/* check that CPU DR is enabled */
if (dr_is_disabled(DR_TYPE_CPU)) {
return (-1);
}
if ((status = dr_cpu_init()) != 0) {
return (status);
}
(void) dr_cpu_fini();
}
return (status);
}
int
{
}
int dr_cpu_allow_unload;
int
_fini(void)
{
int status;
if (dr_cpu_allow_unload == 0)
return (EBUSY);
(void) dr_cpu_fini();
}
return (status);
}
static int
dr_cpu_init(void)
{
int rv;
return (-1);
}
return (0);
}
static int
dr_cpu_fini(void)
{
int rv;
return (-1);
}
return (0);
}
static void
{
}
static void
{
}
static void
{
int resp_len = 0;
int rv;
/*
* Sanity check the message
*/
if (buflen < sizeof (dr_cpu_hdr_t)) {
DR_DBG_CPU("incoming message short: expected at least %ld "
goto done;
}
DR_DBG_CPU("empty message: expected at least %ld bytes\n",
sizeof (dr_cpu_hdr_t));
goto done;
}
DR_DBG_CPU("incoming request:\n");
DR_DBG_CPU("CPU list too long: %d when %d is the maximum\n",
goto done;
}
if (req->num_records == 0) {
DR_DBG_CPU("No CPU specified for operation\n");
goto done;
}
/*
* Process the command
*/
case DR_CPU_CONFIGURE:
case DR_CPU_UNCONFIGURE:
case DR_CPU_FORCE_UNCONFIG:
DR_DBG_CPU("%s%s failed (%d)\n",
"CPU configure" : "CPU unconfigure",
}
break;
case DR_CPU_STATUS:
break;
default:
break;
}
done:
/* check if an error occurred */
resp->num_records = 0;
resp_len = sizeof (dr_cpu_hdr_t);
}
DR_DBG_CPU("outgoing response:\n");
/* send back the response */
DR_DBG_CPU("ds_send failed\n");
}
/* free any allocated memory */
}
}
/*
* Common routine to config or unconfig multiple cpus. The unconfig
* case checks with the OS to see if the removal of cpus will be
* permitted, but can be overridden by the "force" version of the
* command. Otherwise, the logic for both cases is identical.
*
* Note: Do not modify result buffer or length on error.
*/
static int
{
int rv;
int idx;
int count;
int se_hint;
int drctl_cmd;
int drctl_flags = 0;
size_t drctl_res_len = 0;
static const char me[] = "dr_cpu_list_wrk";
/*
* Extract all information that is specific
* to the various types of operations.
*/
case DR_CPU_CONFIGURE:
break;
case DR_CPU_FORCE_UNCONFIG:
case DR_CPU_UNCONFIGURE:
break;
default:
/* Programming error if we reach this. */
ASSERT(0);
return (-1);
}
/* the incoming array of cpuids to operate on */
/* allocate drctl request msg based on incoming resource count */
/* copy the cpuids for the drctl call from the incoming request msg */
return (-1);
}
/* create the result scratch array */
/*
* For unconfigure, check if there are any conditions
* that will cause the operation to fail. These are
* performed before the actual unconfigure attempt so
* that a meaningful error message can be generated.
*/
/* perform the specified operation on each of the CPUs */
int result;
int status;
/*
* If no action will be taken against the current
* CPU, update the drctl resource information to
* ensure that it gets recovered properly during
* the drctl fini() call.
*/
continue;
}
/* call the function to perform the actual operation */
/* save off results of the operation */
/* save result for drctl fini() reusing init() msg memory */
DR_DBG_CPU("%s: cpuid %d status %d result %d off '%s'\n",
}
/*
* Operation completed without any fatal errors.
* Pack the response for transmission.
*/
/* notify interested parties about the operation */
/*
* Deallocate any scratch memory.
*/
return (0);
}
/*
* Allocate and initialize a result array based on the initial
* drctl operation. A valid result array is always returned.
*/
static dr_cpu_res_t *
{
int idx;
char *err_str;
/* allocate zero filled buffer to initialize fields */
/*
* Fill in the result information for each resource.
*/
continue;
/*
* Update the state information for this CPU.
*/
/*
* If an error string exists, copy it out of the
* message buffer. This eliminates any dependency
* on the memory allocated for the message buffer
* itself.
*/
}
}
return (res);
}
static void
{
int idx;
/* deallocate the error string if present */
}
}
/* deallocate the result array itself */
}
/*
* Allocate and pack a response message for transmission based
* on the specified result array. A valid response message and
* valid size information is always returned.
*/
static size_t
{
int idx;
/*
* Calculate the size of the response message
* and allocate an appropriately sized buffer.
*/
resp_len = 0;
/* add the header size */
resp_len += sizeof (dr_cpu_hdr_t);
/* add the stat array size */
/* add the size of any error strings */
}
}
/* allocate the message buffer */
/*
* Fill in the header information.
*/
/*
* Fill in the stat information.
*/
/* string offsets start immediately after stat array */
/* copy over the error string */
}
}
/* buffer should be exactly filled */
return (resp_len);
}
/*
* Check for conditions that will prevent a CPU from being offlined.
* This provides the opportunity to generate useful information to
* help diagnose the failure rather than letting the offline attempt
* fail in a more generic way.
*/
static void
{
int idx;
DR_DBG_CPU("dr_cpu_check_cpus...\n");
/* array of cpuids start just after the header */
/*
* Always check processor set membership first. The
* last CPU in a processor set will fail to offline
* even if the operation if forced, so any failures
* should always be reported.
*/
/* process each cpu that is part of the request */
/* nothing to check if the CPU has already failed */
continue;
continue;
/*
* Only check if there are bound threads if the
* operation is not a forced unconfigure. In a
* forced request, threads are automatically
* unbound before they are offlined.
*/
/*
* The return value is only interesting if other
* checks are added to this loop and a decision
* is needed on whether to continue checking.
*/
}
}
}
/*
* Examine the processor set configuration for the specified
* CPUs and see if the unconfigure operation would result in
* trying to remove the last CPU in any processor set.
*/
static void
{
int cpu_idx;
int set_idx;
char err_str[DR_CPU_MAX_ERR_LEN];
struct {
int ncpus;
} *psrset;
/*
* Allocate a scratch array to count the CPUs in
* the various processor sets. A CPU always belongs
* to exactly one processor set, so by definition,
* the scratch array never needs to be larger than
* the number of CPUs.
*/
/* skip any CPUs that have already failed */
continue;
continue;
/* lookup the set this CPU belongs to */
/* matching set found */
break;
/* set not found, start a new entry */
break;
}
}
/*
* Remove the current CPU from the set total but only
* generate an error for the last CPU. The correct CPU
* will get the error because the unconfigure attempts
* will occur in the same order in which the CPUs are
* examined in this loop.
*/
/*
* Fill in the various pieces of information
* to report that the operation will fail.
*/
}
}
}
/*
* Check if any threads are bound to the specified CPU. If the
* condition is true, DR_CPU_RES_BLOCKED is returned and an error
* string is generated and placed in the specified result structure.
* Otherwise, DR_CPU_RES_OK is returned.
*/
static int
{
int nbound;
char err_str[DR_CPU_MAX_ERR_LEN];
/*
* Error string allocation makes an assumption
* that no blocking condition has been identified.
*/
nbound = 0;
/*
* Walk the active processes, checking if each
* thread belonging to the process is bound.
*/
continue;
}
do {
continue;
/*
* Update the running total of bound
* threads. Continue the search until
* it can be determined if more than
* one thread is bound to the CPU.
*/
if (++nbound > 1)
break;
}
if (nbound) {
/*
* Threads are bound to the CPU. Fill in
* various pieces of information to report
* that the operation will fail.
*/
}
}
/*
* Do not modify result buffer or length on error.
*/
static int
{
int idx;
int result;
int status;
int rlen;
int num_nodes;
int listsz;
/* the incoming array of cpuids to configure */
/* allocate a response message */
rlen = sizeof (dr_cpu_hdr_t);
/* fill in the known data */
/* stat array for the response */
/* get the status for each of the CPUs */
if (result == DR_CPU_RES_FAILURE)
/* save off results of the status */
}
goto done;
/*
* At least one of the cpus did not have a CPU
* structure. So, consult the MD to determine if
* they are present.
*/
DR_DBG_CPU("unable to initialize MD\n");
goto done;
}
continue;
/* check the MD for the current cpuid */
if (cpunode == MDE_INVAL_ELEM_COOKIE) {
} else {
}
}
(void) md_fini_handle(mdp);
done:
return (0);
}
static int
{
int rv = 0;
DR_DBG_CPU("dr_cpu_configure...\n");
/*
* Build device tree node for the CPU
*/
return (DR_CPU_RES_NOT_IN_MD);
}
return (DR_CPU_RES_FAILURE);
}
/*
* Configure the CPU
*/
DR_DBG_CPU("failed to configure CPU %d (%d)\n",
goto done;
}
/* CPU struct should exist now */
}
/*
* Power on the CPU. In sun4v, this brings the stopped
* CPU into the guest from the Hypervisor.
*/
if (cpu_is_poweredoff(cp)) {
DR_DBG_CPU("failed to power on CPU %d (%d)\n",
goto done;
}
}
/*
* Online the CPU
*/
if (cpu_is_offline(cp)) {
DR_DBG_CPU("failed to online CPU %d (%d)\n",
/* offline is still configured */
goto done;
}
}
rv = DR_CPU_RES_OK;
done:
return (rv);
}
static int
{
int rv = 0;
int cpu_flags;
/*
* The OS CPU structures are already torn down,
* Attempt to deprobe the CPU to make sure the
* device tree is up to date.
*/
if (dr_cpu_deprobe(cpuid) != 0) {
goto done;
}
goto done;
}
/*
* Offline the CPU
*/
if (cpu_is_active(cp)) {
/* set the force flag correctly */
DR_DBG_CPU("failed to offline CPU %d (%d)\n",
goto done;
}
}
/*
* Power off the CPU. In sun4v, this puts the running
* CPU into the stopped state in the Hypervisor.
*/
if (!cpu_is_poweredoff(cp)) {
DR_DBG_CPU("failed to power off CPU %d (%d)\n",
goto done;
}
}
/*
* Unconfigure the CPU
*/
goto done;
}
/*
* Tear down device tree.
*/
goto done;
}
rv = DR_CPU_RES_OK;
done:
return (rv);
}
/*
* Determine the state of a CPU. If the CPU structure is not present,
* it does not attempt to determine whether or not the CPU is in the
* MD. It is more efficient to do this at the higher level for all
* CPUs since it may not even be necessary to search the MD if all
* the CPUs are accounted for. Returns DR_CPU_RES_OK if the CPU
* structure is present, and DR_CPU_RES_FAILURE otherwise as a signal
* that an MD walk is necessary.
*/
static int
{
int rv;
DR_DBG_CPU("dr_cpu_status...\n");
/* need to check if cpu is in the MD */
goto done;
}
if (cpu_is_poweredoff(cp)) {
/*
* The CPU is powered off, so it is considered
* unconfigured from the service entity point of
* view. The CPU is not available to the system
* and intervention by the service entity would
* be required to change that.
*/
} else {
/*
* The CPU is powered on, so it is considered
* configured from the service entity point of
* view. It is available for use by the system
* and service entities are not concerned about
* the operational status (offline, online, etc.)
* of the CPU in terms of DR.
*/
}
rv = DR_CPU_RES_OK;
done:
return (rv);
}
typedef struct {
} cb_arg_t;
#define STR_ARR_LEN 5
static int
{
char *compat;
int regbuf[4];
int len = 0;
char *str_arr[STR_ARR_LEN];
char *curr;
int idx = 0;
DR_DBG_CPU("new_cpu_node...\n");
/*
* Add 'name' property
*/
DR_DBG_CPU("new_cpu_node: failed to create 'name' property\n");
return (DDI_WALK_ERROR);
}
/*
* Add 'compatible' property
*/
DR_DBG_CPU("new_cpu_node: failed to read 'compatible' property "
"from MD\n");
return (DDI_WALK_ERROR);
}
/* parse the MD string array */
if (idx == STR_ARR_LEN) {
break;
}
}
DR_DBG_CPU("new_cpu_node: failed to create 'compatible' "
"property\n");
return (DDI_WALK_ERROR);
}
/*
* Add 'device_type' property
*/
DR_DBG_CPU("new_cpu_node: failed to create 'device_type' "
"property\n");
return (DDI_WALK_ERROR);
}
/*
* Add 'clock-frequency' property
*/
DR_DBG_CPU("new_cpu_node: failed to read 'clock-frequency' "
"property from MD\n");
return (DDI_WALK_ERROR);
}
DR_DBG_CPU("new_cpu_node: failed to create 'clock-frequency' "
"property\n");
return (DDI_WALK_ERROR);
}
/*
* Add 'reg' (cpuid) property
*/
DR_DBG_CPU("new_cpu_node: failed to read 'id' property "
"from MD\n");
return (DDI_WALK_ERROR);
}
DR_DBG_CPU("new_cpu_node: failed to create 'reg' property\n");
return (DDI_WALK_ERROR);
}
return (DDI_WALK_TERMINATE);
}
static int
{
int num_nodes;
int rv = 0;
int listsz;
/* nothing to do */
return (0);
}
DR_DBG_CPU("unable to initialize machine description\n");
return (-1);
}
if (cpunode == MDE_INVAL_ELEM_COOKIE) {
goto done;
}
/* pass in MD cookie for CPU */
pdip = ddi_root_node();
rv = -1;
goto done;
}
rv = 0;
done:
if (listp)
if (mdp)
(void) md_fini_handle(mdp);
return (rv);
}
static int
{
return (0);
}
/*
* If non-NULL, fdip is held and must be released.
*/
} else {
}
return (-1);
}
return (0);
}
typedef struct {
static int
{
char *name;
if (dip == ddi_root_node()) {
return (DDI_WALK_CONTINUE);
}
return (DDI_WALK_PRUNECHILD);
}
"reg", -1);
DR_DBG_CPU("matching node\n");
/* matching node must be returned held */
if (!e_ddi_branch_held(dip))
return (DDI_WALK_TERMINATE);
}
return (DDI_WALK_CONTINUE);
}
/*
* Walk the device tree to find the dip corresponding to the cpuid
* passed in. If present, the dip is returned held. The caller must
* release the hold on the dip once it is no longer required. If no
* matching node if found, NULL is returned.
*/
static dev_info_t *
{
DR_DBG_CPU("dr_cpu_find_node...\n");
}
/*
* Look up a particular cpuid in the MD. Returns the mde_cookie_t
* representing that CPU if present, and MDE_INVAL_ELEM_COOKIE
* otherwise. It is assumed the scratch array has already been
* allocated so that it can accommodate the worst case scenario,
* every node in the MD.
*/
static mde_cookie_t
{
int idx;
int nnodes;
/*
* Scan the DAG for all the CPU nodes
*/
if (nnodes < 0) {
DR_DBG_CPU("Scan for CPUs failed\n");
return (result);
}
/*
* Find the CPU of interest
*/
DR_DBG_CPU("Missing 'id' property for CPU node %d\n",
idx);
break;
}
if (cpuid_prop == cpuid) {
/* found a match */
DR_DBG_CPU("dr_cpu_find_node_md: found CPU %d "
"in MD\n", cpuid);
break;
}
}
if (result == MDE_INVAL_ELEM_COOKIE) {
}
return (result);
}