diff options
Diffstat (limited to 'usr/src/cmd/hotplugd/hotplugd_rcm.c')
| -rw-r--r-- | usr/src/cmd/hotplugd/hotplugd_rcm.c | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/usr/src/cmd/hotplugd/hotplugd_rcm.c b/usr/src/cmd/hotplugd/hotplugd_rcm.c new file mode 100644 index 0000000000..5be134b32b --- /dev/null +++ b/usr/src/cmd/hotplugd/hotplugd_rcm.c @@ -0,0 +1,703 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <librcm.h> +#include <libhotplug.h> +#include <libhotplug_impl.h> +#include <sys/sunddi.h> +#include <sys/ddi_hp.h> +#include "hotplugd_impl.h" + +/* + * Define structures for a path-to-usage lookup table. + */ +typedef struct info_entry { + char *rsrc; + char *usage; + struct info_entry *next; +} info_entry_t; + +typedef struct { + char *path; + info_entry_t *entries; +} info_table_t; + +/* + * Define callback argument used when getting resources. + */ +typedef struct { + int error; + int n_rsrcs; + char **rsrcs; + char path[MAXPATHLEN]; + char connection[MAXPATHLEN]; + char dev_path[MAXPATHLEN]; +} resource_cb_arg_t; + +/* + * Define callback argument used when merging info. + */ +typedef struct { + int error; + info_table_t *table; + size_t table_len; + char path[MAXPATHLEN]; + char connection[MAXPATHLEN]; +} merge_cb_arg_t; + +/* + * Local functions. + */ +static int merge_rcm_info(hp_node_t root, rcm_info_t *info); +static int get_rcm_usage(char **rsrcs, rcm_info_t **info_p); +static int build_table(rcm_info_t *info, info_table_t **tablep, + size_t *table_lenp); +static void free_table(info_table_t *table, size_t table_len); +static int resource_callback(hp_node_t node, void *argp); +static int merge_callback(hp_node_t node, void *argp); +static int rsrc2path(const char *rsrc, char *path); +static int compare_info(const void *a, const void *b); + +/* + * copy_usage() + * + * Given an information snapshot, get the corresponding + * RCM usage information and merge it into the snapshot. + */ +int +copy_usage(hp_node_t root) +{ + rcm_info_t *info = NULL; + char **rsrcs = NULL; + int rv; + + /* Get resource names */ + if ((rv = rcm_resources(root, &rsrcs)) != 0) { + log_err("Cannot get RCM resources (%s)\n", strerror(rv)); + return (rv); + } + + /* Do nothing if no resources */ + if (rsrcs == NULL) + return (0); + + /* Get RCM usage information */ + if ((rv = get_rcm_usage(rsrcs, &info)) != 0) { + log_err("Cannot get RCM information (%s)\n", strerror(rv)); + free_rcm_resources(rsrcs); + return (rv); + } + + /* Done with resource names */ + free_rcm_resources(rsrcs); + + /* If there is RCM usage information, merge it in */ + if (info != NULL) { + rv = merge_rcm_info(root, info); + rcm_free_info(info); + return (rv); + } + + return (0); +} + +/* + * rcm_resources() + * + * Given the root of a hotplug information snapshot, + * construct a list of RCM compatible resource names. + */ +int +rcm_resources(hp_node_t root, char ***rsrcsp) +{ + resource_cb_arg_t arg; + + /* Initialize results */ + *rsrcsp = NULL; + + /* Traverse snapshot to get resources */ + (void) memset(&arg, 0, sizeof (resource_cb_arg_t)); + (void) hp_traverse(root, &arg, resource_callback); + + /* Check for errors */ + if (arg.error != 0) { + free_rcm_resources(arg.rsrcs); + return (arg.error); + } + + /* Success */ + *rsrcsp = arg.rsrcs; + return (0); +} + +/* + * free_rcm_resources() + * + * Free a table of RCM resource names. + */ +void +free_rcm_resources(char **rsrcs) +{ + int i; + + if (rsrcs != NULL) { + for (i = 0; rsrcs[i] != NULL; i++) + free(rsrcs[i]); + free(rsrcs); + } +} + +/* + * rcm_offline() + * + * Implement an RCM offline request. + * + * NOTE: errors from RCM will be merged into the snapshot. + */ +int +rcm_offline(char **rsrcs, uint_t flags, hp_node_t root) +{ + rcm_handle_t *handle; + rcm_info_t *info = NULL; + uint_t rcm_flags = 0; + int rv = 0; + + dprintf("rcm_offline()\n"); + + /* Set flags */ + if (flags & HPFORCE) + rcm_flags |= RCM_FORCE; + if (flags & HPQUERY) + rcm_flags |= RCM_QUERY; + + /* Allocate RCM handle */ + if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) { + log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); + return (EFAULT); + } + + /* Request RCM offline */ + if (rcm_request_offline_list(handle, rsrcs, rcm_flags, + &info) != RCM_SUCCESS) + rv = EBUSY; + + /* RCM handle is no longer needed */ + (void) rcm_free_handle(handle); + + /* + * Check if RCM returned any information tuples. If so, + * then also check if the RCM operation failed, and possibly + * merge the RCM info into the caller's hotplug snapshot. + */ + if (info != NULL) { + if (rv != 0) + (void) merge_rcm_info(root, info); + rcm_free_info(info); + } + + return (rv); +} + +/* + * rcm_online() + * + * Implement an RCM online notification. + */ +void +rcm_online(char **rsrcs) +{ + rcm_handle_t *handle; + rcm_info_t *info = NULL; + + dprintf("rcm_online()\n"); + + if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) { + log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); + return; + } + + (void) rcm_notify_online_list(handle, rsrcs, 0, &info); + + (void) rcm_free_handle(handle); + + if (info != NULL) + rcm_free_info(info); +} + +/* + * rcm_remove() + * + * Implement an RCM remove notification. + */ +void +rcm_remove(char **rsrcs) +{ + rcm_handle_t *handle; + rcm_info_t *info = NULL; + + dprintf("rcm_remove()\n"); + + if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) { + log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); + return; + } + + (void) rcm_notify_remove_list(handle, rsrcs, 0, &info); + + (void) rcm_free_handle(handle); + + if (info != NULL) + rcm_free_info(info); +} + +/* + * get_rcm_usage() + * + * Lookup usage information for a set of resources from RCM. + */ +static int +get_rcm_usage(char **rsrcs, rcm_info_t **info_p) +{ + rcm_handle_t *handle; + rcm_info_t *info = NULL; + int rv = 0; + + /* No-op if no RCM resources */ + if (rsrcs == NULL) + return (0); + + /* Allocate RCM handle */ + if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &handle) != RCM_SUCCESS) { + log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); + return (EFAULT); + } + + /* Get usage information from RCM */ + if (rcm_get_info_list(handle, rsrcs, + RCM_INCLUDE_DEPENDENT | RCM_INCLUDE_SUBTREE, + &info) != RCM_SUCCESS) { + log_err("Failed to get RCM information (%s)\n", + strerror(errno)); + rv = EFAULT; + } + + /* RCM handle is no longer needed */ + (void) rcm_free_handle(handle); + + *info_p = info; + return (rv); +} + +/* + * merge_rcm_info() + * + * Merge RCM information into a hotplug information snapshot. + * First a lookup table is built to map lists of RCM usage to + * pathnames. Then during a full traversal of the snapshot, + * the lookup table is used for each node to find matching + * RCM info tuples for each path in the snapshot. + */ +static int +merge_rcm_info(hp_node_t root, rcm_info_t *info) +{ + merge_cb_arg_t arg; + info_table_t *table; + size_t table_len; + int rv; + + /* Build a lookup table, mapping paths to usage information */ + if ((rv = build_table(info, &table, &table_len)) != 0) { + log_err("Cannot build RCM lookup table (%s)\n", strerror(rv)); + return (rv); + } + + /* Stop if no valid entries were inserted in table */ + if ((table == NULL) || (table_len == 0)) { + log_err("Unable to gather RCM usage.\n"); + return (0); + } + + /* Initialize callback argument */ + (void) memset(&arg, 0, sizeof (merge_cb_arg_t)); + arg.table = table; + arg.table_len = table_len; + + /* Perform a merge traversal */ + (void) hp_traverse(root, (void *)&arg, merge_callback); + + /* Done with the table */ + free_table(table, table_len); + + /* Check for errors */ + if (arg.error != 0) { + log_err("Cannot merge RCM information (%s)\n", strerror(rv)); + return (rv); + } + + return (0); +} + +/* + * resource_callback() + * + * A callback function for hp_traverse() that builds an RCM + * compatible list of resource path names. The array has + * been pre-allocated based on results from the related + * callback resource_count_callback(). + */ +static int +resource_callback(hp_node_t node, void *argp) +{ + resource_cb_arg_t *arg = (resource_cb_arg_t *)argp; + char **new_rsrcs; + size_t new_size; + int type; + + /* Get node type */ + type = hp_type(node); + + /* Prune OFFLINE ports */ + if ((type == HP_NODE_PORT) && HP_IS_OFFLINE(hp_state(node))) + return (HP_WALK_PRUNECHILD); + + /* Skip past non-devices */ + if (type != HP_NODE_DEVICE) + return (HP_WALK_CONTINUE); + + /* Lookup resource path */ + if (hp_path(node, arg->path, arg->connection) != 0) { + log_err("Cannot get RCM resource path.\n"); + arg->error = EFAULT; + return (HP_WALK_TERMINATE); + } + + /* Insert "/devices" to path name */ + (void) snprintf(arg->dev_path, MAXPATHLEN, "/devices%s", arg->path); + + /* + * Grow resource array to accomodate new /devices path. + * NOTE: include an extra NULL pointer at end of array. + */ + new_size = (arg->n_rsrcs + 2) * sizeof (char *); + if (arg->rsrcs == NULL) + new_rsrcs = (char **)malloc(new_size); + else + new_rsrcs = (char **)realloc(arg->rsrcs, new_size); + if (new_rsrcs != NULL) { + arg->rsrcs = new_rsrcs; + } else { + log_err("Cannot allocate RCM resource array.\n"); + arg->error = ENOMEM; + return (HP_WALK_TERMINATE); + } + + /* Initialize new entries */ + arg->rsrcs[arg->n_rsrcs] = strdup(arg->dev_path); + arg->rsrcs[arg->n_rsrcs + 1] = NULL; + + /* Check for errors */ + if (arg->rsrcs[arg->n_rsrcs] == NULL) { + log_err("Cannot allocate RCM resource path.\n"); + arg->error = ENOMEM; + return (HP_WALK_TERMINATE); + } + + /* Increment resource count */ + arg->n_rsrcs += 1; + + /* Do not visit children */ + return (HP_WALK_PRUNECHILD); +} + +/* + * merge_callback() + * + * A callback function for hp_traverse() that merges RCM information + * tuples into an existing hotplug information snapshot. The RCM + * information will be turned into HP_NODE_USAGE nodes. + */ +static int +merge_callback(hp_node_t node, void *argp) +{ + merge_cb_arg_t *arg = (merge_cb_arg_t *)argp; + hp_node_t usage; + info_table_t lookup; + info_table_t *slot; + info_entry_t *entry; + int rv; + + /* Only process device nodes (other nodes cannot have usage) */ + if (hp_type(node) != HP_NODE_DEVICE) + return (HP_WALK_CONTINUE); + + /* Get path of current node, using buffer provided in 'arg' */ + if ((rv = hp_path(node, arg->path, arg->connection)) != 0) { + log_err("Cannot lookup hotplug path (%s)\n", strerror(rv)); + arg->error = rv; + return (HP_WALK_TERMINATE); + } + + /* Check the lookup table for associated usage */ + lookup.path = arg->path; + if ((slot = bsearch(&lookup, arg->table, arg->table_len, + sizeof (info_table_t), compare_info)) == NULL) + return (HP_WALK_CONTINUE); + + /* Usage information was found. Append HP_NODE_USAGE nodes. */ + for (entry = slot->entries; entry != NULL; entry = entry->next) { + + /* Allocate a new usage node */ + usage = (hp_node_t)calloc(1, sizeof (struct hp_node)); + if (usage == NULL) { + log_err("Cannot allocate hotplug usage node.\n"); + arg->error = ENOMEM; + return (HP_WALK_TERMINATE); + } + + /* Initialize the usage node's contents */ + usage->hp_type = HP_NODE_USAGE; + if ((usage->hp_name = strdup(entry->rsrc)) == NULL) { + log_err("Cannot allocate hotplug usage node name.\n"); + free(usage); + arg->error = ENOMEM; + return (HP_WALK_TERMINATE); + } + if ((usage->hp_usage = strdup(entry->usage)) == NULL) { + log_err("Cannot allocate hotplug usage node info.\n"); + free(usage->hp_name); + free(usage); + arg->error = ENOMEM; + return (HP_WALK_TERMINATE); + } + + /* Link the usage node as a child of the device node */ + usage->hp_parent = node; + usage->hp_sibling = node->hp_child; + node->hp_child = usage; + } + + return (HP_WALK_CONTINUE); +} + +/* + * build_table() + * + * Build a lookup table that will be used to map paths to their + * corresponding RCM information tuples. + */ +static int +build_table(rcm_info_t *info, info_table_t **tablep, size_t *table_lenp) +{ + rcm_info_tuple_t *tuple; + info_entry_t *entry; + info_table_t *slot; + info_table_t *table; + size_t table_len; + const char *rsrc; + const char *usage; + char path[MAXPATHLEN]; + + /* Initialize results */ + *tablep = NULL; + *table_lenp = 0; + + /* Count the RCM info tuples to determine the table's size */ + table_len = 0; + for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; ) + table_len++; + + /* If the table would be empty, then do nothing */ + if (table_len == 0) + return (ENOENT); + + /* Allocate the lookup table */ + table = (info_table_t *)calloc(table_len, sizeof (info_table_t)); + if (table == NULL) + return (ENOMEM); + + /* + * Fill in the lookup table. Fill one slot in the table + * for each device path that has a set of associated RCM + * information tuples. In some cases multiple tuples will + * be joined together within the same slot. + */ + slot = NULL; + table_len = 0; + for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; ) { + + /* + * Extract RCM resource name and usage description. + * + * NOTE: skip invalid tuples to return as much as possible. + */ + if (((rsrc = rcm_info_rsrc(tuple)) == NULL) || + ((usage = rcm_info_info(tuple)) == NULL)) { + log_err("RCM returned invalid resource or usage.\n"); + continue; + } + + /* + * Try to convert the RCM resource name to a hotplug path. + * If conversion succeeds and this path differs from the + * current slot in the table, then initialize the next + * slot in the table. + */ + if ((rsrc2path(rsrc, path) == 0) && + ((slot == NULL) || (strcmp(slot->path, path) != 0))) { + slot = &table[table_len]; + if ((slot->path = strdup(path)) == NULL) { + log_err("Cannot build info table slot.\n"); + free_table(table, table_len); + return (ENOMEM); + } + table_len++; + } + + /* Append current usage to entry list in the current slot */ + if (slot != NULL) { + + /* Allocate new entry */ + entry = (info_entry_t *)malloc(sizeof (info_entry_t)); + if (entry == NULL) { + log_err("Cannot allocate info table entry.\n"); + free_table(table, table_len); + return (ENOMEM); + } + + /* Link entry into current slot list */ + entry->next = slot->entries; + slot->entries = entry; + + /* Initialize entry values */ + if (((entry->rsrc = strdup(rsrc)) == NULL) || + ((entry->usage = strdup(usage)) == NULL)) { + log_err("Cannot build info table entry.\n"); + free_table(table, table_len); + return (ENOMEM); + } + } + } + + /* Check if valid entries were inserted in table */ + if (table_len == 0) { + free(table); + return (0); + } + + /* Sort the lookup table by hotplug path */ + qsort(table, table_len, sizeof (info_table_t), compare_info); + + /* Done */ + *tablep = table; + *table_lenp = table_len; + return (0); +} + +/* + * free_table() + * + * Destroy a lookup table. + */ +static void +free_table(info_table_t *table, size_t table_len) +{ + info_entry_t *entry; + int index; + + if (table != NULL) { + for (index = 0; index < table_len; index++) { + if (table[index].path != NULL) + free(table[index].path); + while (table[index].entries != NULL) { + entry = table[index].entries; + table[index].entries = entry->next; + if (entry->rsrc != NULL) + free(entry->rsrc); + if (entry->usage != NULL) + free(entry->usage); + free(entry); + } + } + free(table); + } +} + +/* + * rsrc2path() + * + * Convert from an RCM resource name to a hotplug device path. + */ +static int +rsrc2path(const char *rsrc, char *path) +{ + char *s; + char tmp[MAXPATHLEN]; + + /* Only convert /dev and /devices paths */ + if (strncmp(rsrc, "/dev", 4) == 0) { + + /* Follow symbolic links for /dev paths */ + if (realpath(rsrc, tmp) == NULL) { + log_err("Cannot resolve RCM resource (%s)\n", + strerror(errno)); + return (-1); + } + + /* Remove the leading "/devices" part */ + (void) strlcpy(path, &tmp[strlen(S_DEVICES)], MAXPATHLEN); + + /* Remove any trailing minor node part */ + if ((s = strrchr(path, ':')) != NULL) + *s = '\0'; + + /* Successfully converted */ + return (0); + } + + /* Not converted */ + return (-1); +} + +/* + * compare_info() + * + * Compare two slots in the lookup table that maps paths to usage. + * + * NOTE: for use with qsort() and bsearch(). + */ +static int +compare_info(const void *a, const void *b) +{ + info_table_t *slot_a = (info_table_t *)a; + info_table_t *slot_b = (info_table_t *)b; + + return (strcmp(slot_a->path, slot_b->path)); +} |
