blob: 0dcb0621a47003561074536b3fa76f5be93a89b6 [file] [log] [blame]
/*
* Node information (ConfigROM) collection and management.
*
* Copyright (C) 2000 Andreas E. Bombe
* 2001 Ben Collins <bcollins@debian.net>
*
* This code is licensed under the GPL. See the file COPYING in the root
* directory of the kernel sources for details.
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <asm/byteorder.h>
#include <asm/atomic.h>
#include <linux/smp_lock.h>
#include <linux/interrupt.h>
#include <linux/kmod.h>
#include <linux/completion.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif
#include "ieee1394_types.h"
#include "ieee1394.h"
#include "hosts.h"
#include "ieee1394_transactions.h"
#include "ieee1394_hotplug.h"
#include "highlevel.h"
#include "csr.h"
#include "nodemgr.h"
/*
* Basically what we do here is start off retrieving the bus_info block.
* From there will fill in some info about the node, verify it is of IEEE
* 1394 type, and that the crc checks out ok. After that we start off with
* the root directory, and subdirectories. To do this, we retrieve the
* quadlet header for a directory, find out the length, and retrieve the
* complete directory entry (be it a leaf or a directory). We then process
* it and add the info to our structure for that particular node.
*
* We verify CRC's along the way for each directory/block/leaf. The
* entire node structure is generic, and simply stores the information in
* a way that's easy to parse by the protocol interface.
*/
static LIST_HEAD(node_list);
static rwlock_t node_lock = RW_LOCK_UNLOCKED;
static LIST_HEAD(driver_list);
static rwlock_t driver_lock = RW_LOCK_UNLOCKED;
/* The rwlock unit_directory_lock is always held when manipulating the
* global unit_directory_list, but this also protects access to the
* lists of unit directories stored in the protocol drivers.
*/
static LIST_HEAD(unit_directory_list);
static rwlock_t unit_directory_lock = RW_LOCK_UNLOCKED;
static LIST_HEAD(host_info_list);
static spinlock_t host_info_lock = SPIN_LOCK_UNLOCKED;
struct host_info {
struct hpsb_host *host;
struct list_head list;
struct completion started;
struct completion exited;
wait_queue_head_t wait;
int pid;
};
#ifdef CONFIG_PROC_FS
static int raw1394_read_proc(char *buffer, char **start, off_t offset,
int size, int *eof, void *data )
{
struct list_head *lh;
struct node_entry *ne;
int disp_size = 0;
char display_str[1024];
#define PUTF(fmt, args...) disp_size += sprintf(display_str, fmt, ## args); strcat(buffer,display_str)
buffer[0] = '\0';
list_for_each(lh, &node_list) {
ne = list_entry(lh, struct node_entry, list);
if (!ne)
continue;
PUTF("Node[" NODE_BUS_FMT "] GUID[%016Lx]:\n",
NODE_BUS_ARGS(ne->nodeid), (unsigned long long)ne->guid);
if (ne->host != NULL && ne->host->node_id == ne->nodeid) {
PUTF("\tNodes connected : %d\n", ne->host->node_count);
PUTF("\tSelfIDs received: %d\n", ne->host->selfid_count);
PUTF("\tOwn ID : 0x%08x\n",ne->host->node_id);
PUTF("\tIrm ID : 0x%08x\n",ne->host->irm_id);
PUTF("\tBusMgr ID : 0x%08x\n",ne->host->busmgr_id);
PUTF("\tBR IR IC II IB\n");
PUTF("\t%02d %02d %02d %02d %02d\n",
ne->host->in_bus_reset, ne->host->is_root,
ne->host->is_cycmst, ne->host->is_irm,
ne->host->is_busmgr);
}
PUTF("\tVendor ID: %s [0x%06x]\n",
ne->vendor_name ?: "Unknown", ne->vendor_id);
PUTF("\tCapabilities: 0x%06x\n", ne->capabilities);
PUTF("\tirmc=%d cmc=%d isc=%d bmc=%d pmc=%d cyc_clk_acc=%d max_rec=%d gen=%d lspd=%d\n",
ne->busopt.irmc, ne->busopt.cmc, ne->busopt.isc, ne->busopt.bmc,
ne->busopt.pmc, ne->busopt.cyc_clk_acc, ne->busopt.max_rec,
ne->busopt.generation, ne->busopt.lnkspd);
}
#undef PUTF
return disp_size;
}
#endif /* CONFIG_PROC_FS */
static void nodemgr_process_config_rom(struct node_entry *ne,
quadlet_t busoptions);
static int nodemgr_read_quadlet(struct hpsb_host *host,
nodeid_t nodeid, octlet_t address,
quadlet_t *quad)
{
int i;
int ret = 0;
for (i = 0; i < 3; i++) {
ret = hpsb_read(host, nodeid, address, quad, 4);
if (ret != -EAGAIN)
break;
}
*quad = be32_to_cpu(*quad);
return ret;
}
static int nodemgr_size_text_leaf(struct hpsb_host *host,
nodeid_t nodeid,
octlet_t address)
{
quadlet_t quad;
int size = 0;
if (nodemgr_read_quadlet(host, nodeid, address, &quad))
return -1;
if (CONFIG_ROM_KEY(quad) == CONFIG_ROM_DESCRIPTOR_LEAF) {
/* This is the offset. */
address += 4 * CONFIG_ROM_VALUE(quad);
if (nodemgr_read_quadlet(host, nodeid, address, &quad))
return -1;
/* Now we got the size of the text descriptor leaf. */
size = CONFIG_ROM_LEAF_LENGTH(quad);
}
return size;
}
static int nodemgr_read_text_leaf(struct hpsb_host *host,
nodeid_t nodeid,
octlet_t address,
quadlet_t *quadp)
{
quadlet_t quad;
int i, size, ret;
if (nodemgr_read_quadlet(host, nodeid, address, &quad)
&& CONFIG_ROM_KEY(quad) != CONFIG_ROM_DESCRIPTOR_LEAF)
return -1;
/* This is the offset. */
address += 4 * CONFIG_ROM_VALUE(quad);
if (nodemgr_read_quadlet(host, nodeid, address, &quad))
return -1;
/* Now we got the size of the text descriptor leaf. */
size = CONFIG_ROM_LEAF_LENGTH(quad) - 2;
if (size <= 0)
return -1;
address += 4;
for (i = 0; i < 2; i++, address += 4, quadp++) {
if (nodemgr_read_quadlet(host, nodeid, address, quadp))
return -1;
}
/* Now read the text string. */
ret = -ENXIO;
for (; size > 0; size--, address += 4, quadp++) {
for (i = 0; i < 3; i++) {
ret = hpsb_read(host, nodeid, address, quadp, 4);
if (ret != -EAGAIN)
break;
}
if (ret)
break;
}
return ret;
}
static struct node_entry *nodemgr_scan_root_directory
(struct hpsb_host *host, nodeid_t nodeid)
{
octlet_t address;
quadlet_t quad;
int length;
int code, size, total_size;
struct node_entry *ne;
address = CSR_REGISTER_BASE + CSR_CONFIG_ROM;
if (nodemgr_read_quadlet(host, nodeid, address, &quad))
return NULL;
address += 4 + CONFIG_ROM_BUS_INFO_LENGTH(quad) * 4;
if (nodemgr_read_quadlet(host, nodeid, address, &quad))
return NULL;
length = CONFIG_ROM_ROOT_LENGTH(quad);
address += 4;
size = 0;
total_size = sizeof(struct node_entry);
for (; length > 0; length--, address += 4) {
if (nodemgr_read_quadlet(host, nodeid, address, &quad))
return NULL;
code = CONFIG_ROM_KEY(quad);
if (code == CONFIG_ROM_VENDOR_ID) {
/* Check if there is a text descriptor leaf
immediately after this. */
length--;
if (length <= 0)
break;
address += 4;
size = nodemgr_size_text_leaf(host, nodeid,
address);
switch (size) {
case -1:
return NULL;
break;
case 0:
break;
default:
total_size += (size + 1) * sizeof (quadlet_t);
break;
}
break;
}
}
ne = kmalloc(total_size, SLAB_ATOMIC);
if (ne != NULL) {
if (size != 0) {
ne->vendor_name
= (const char *) &(ne->quadlets[2]);
ne->quadlets[size] = 0;
}
else {
ne->vendor_name = NULL;
}
}
return ne;
}
static struct node_entry *nodemgr_create_node(octlet_t guid, quadlet_t busoptions,
struct hpsb_host *host, nodeid_t nodeid)
{
struct node_entry *ne;
unsigned long flags;
ne = nodemgr_scan_root_directory (host, nodeid);
if (!ne) return NULL;
INIT_LIST_HEAD(&ne->list);
INIT_LIST_HEAD(&ne->unit_directories);
ne->host = host;
ne->nodeid = nodeid;
ne->guid = guid;
atomic_set(&ne->generation, get_hpsb_generation(ne->host));
write_lock_irqsave(&node_lock, flags);
list_add_tail(&ne->list, &node_list);
write_unlock_irqrestore(&node_lock, flags);
nodemgr_process_config_rom (ne, busoptions);
HPSB_DEBUG("%s added: Node[" NODE_BUS_FMT "] GUID[%016Lx] [%s]",
(host->node_id == nodeid) ? "Local host" : "Device",
NODE_BUS_ARGS(nodeid), (unsigned long long)guid,
ne->vendor_name ?: "Unknown");
return ne;
}
static struct node_entry *find_entry_by_guid(u64 guid)
{
struct list_head *lh;
struct node_entry *ne;
list_for_each(lh, &node_list) {
ne = list_entry(lh, struct node_entry, list);
if (ne->guid == guid) return ne;
}
return NULL;
}
static struct node_entry *find_entry_by_nodeid(nodeid_t nodeid)
{
struct list_head *lh;
struct node_entry *ne;
list_for_each(lh, &node_list) {
ne = list_entry(lh, struct node_entry, list);
if (ne->nodeid == nodeid) return ne;
}
return NULL;
}
static struct unit_directory *nodemgr_scan_unit_directory
(struct node_entry *ne, octlet_t address)
{
struct unit_directory *ud;
quadlet_t quad;
u8 flags, todo;
int length, size, total_size, count;
int vendor_name_size, model_name_size;
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
return NULL;
length = CONFIG_ROM_DIRECTORY_LENGTH(quad) ;
address += 4;
size = 0;
total_size = sizeof (struct unit_directory);
flags = 0;
count = 0;
vendor_name_size = 0;
model_name_size = 0;
for (; length > 0; length--, address += 4) {
int code;
quadlet_t value;
retry:
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
return NULL;
code = CONFIG_ROM_KEY(quad);
value = CONFIG_ROM_VALUE(quad);
todo = 0;
switch (code) {
case CONFIG_ROM_VENDOR_ID:
todo = UNIT_DIRECTORY_VENDOR_TEXT;
break;
case CONFIG_ROM_MODEL_ID:
todo = UNIT_DIRECTORY_MODEL_TEXT;
break;
case CONFIG_ROM_SPECIFIER_ID:
case CONFIG_ROM_UNIT_SW_VERSION:
break;
case CONFIG_ROM_DESCRIPTOR_LEAF:
case CONFIG_ROM_DESCRIPTOR_DIRECTORY:
/* TODO: read strings... icons? */
break;
default:
/* Which types of quadlets do we want to
store? Only count immediate values and
CSR offsets for now. */
code &= CONFIG_ROM_KEY_TYPE_MASK;
if ((code & 0x80) == 0)
count++;
break;
}
if (todo) {
/* Check if there is a text descriptor leaf
immediately after this. */
length--;
if (length <= 0)
break;
address += 4;
size = nodemgr_size_text_leaf(ne->host,
ne->nodeid,
address);
if (todo | UNIT_DIRECTORY_VENDOR_TEXT)
vendor_name_size = size;
else
model_name_size = size;
switch (size) {
case -1:
return NULL;
break;
case 0:
goto retry;
break;
default:
flags |= todo;
total_size += (size + 1) * sizeof (quadlet_t);
break;
}
}
}
total_size += count * sizeof (quadlet_t);
ud = kmalloc (total_size, GFP_KERNEL);
if (ud != NULL) {
memset (ud, 0, sizeof *ud);
ud->flags = flags;
ud->count = count;
ud->vendor_name_size = vendor_name_size;
ud->model_name_size = model_name_size;
/* If there is no vendor name in the unit directory,
use the one in the root directory. */
ud->vendor_name = ne->vendor_name;
}
return ud;
}
/* This implementation currently only scans the config rom and its
* immediate unit directories looking for software_id and
* software_version entries, in order to get driver autoloading working.
*/
static void nodemgr_process_unit_directory(struct node_entry *ne,
octlet_t address)
{
struct unit_directory *ud;
quadlet_t quad;
quadlet_t *infop;
int length;
if (!(ud = nodemgr_scan_unit_directory(ne, address)))
goto unit_directory_error;
ud->ne = ne;
ud->address = address;
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
goto unit_directory_error;
length = CONFIG_ROM_DIRECTORY_LENGTH(quad) ;
address += 4;
infop = (quadlet_t *) ud->quadlets;
for (; length > 0; length--, address += 4, infop++) {
int code;
quadlet_t value;
quadlet_t *quadp;
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
goto unit_directory_error;
code = CONFIG_ROM_KEY(quad) ;
value = CONFIG_ROM_VALUE(quad);
switch (code) {
case CONFIG_ROM_VENDOR_ID:
ud->vendor_id = value;
ud->flags |= UNIT_DIRECTORY_VENDOR_ID;
if ((ud->flags & UNIT_DIRECTORY_VENDOR_TEXT) != 0) {
length--;
address += 4;
quadp = &(ud->quadlets[ud->count]);
if (nodemgr_read_text_leaf(ne->host,
ne->nodeid,
address,
quadp) == 0
&& quadp[0] == 0
&& quadp[1] == 0) {
/* We only support minimal
ASCII and English. */
quadp[ud->vendor_name_size] = 0;
ud->vendor_name
= (const char *) &(quadp[2]);
}
}
break;
case CONFIG_ROM_MODEL_ID:
ud->model_id = value;
ud->flags |= UNIT_DIRECTORY_MODEL_ID;
if ((ud->flags & UNIT_DIRECTORY_MODEL_TEXT) != 0) {
length--;
address += 4;
quadp = &(ud->quadlets[ud->count + ud->vendor_name_size + 1]);
if (nodemgr_read_text_leaf(ne->host,
ne->nodeid,
address,
quadp) == 0
&& quadp[0] == 0
&& quadp[1] == 0) {
/* We only support minimal
ASCII and English. */
quadp[ud->model_name_size] = 0;
ud->model_name
= (const char *) &(quadp[2]);
}
}
break;
case CONFIG_ROM_SPECIFIER_ID:
ud->specifier_id = value;
ud->flags |= UNIT_DIRECTORY_SPECIFIER_ID;
break;
case CONFIG_ROM_UNIT_SW_VERSION:
ud->version = value;
ud->flags |= UNIT_DIRECTORY_VERSION;
break;
case CONFIG_ROM_DESCRIPTOR_LEAF:
case CONFIG_ROM_DESCRIPTOR_DIRECTORY:
/* TODO: read strings... icons? */
break;
default:
/* Which types of quadlets do we want to
store? Only count immediate values and
CSR offsets for now. */
code &= CONFIG_ROM_KEY_TYPE_MASK;
if ((code & 0x80) == 0)
*infop = quad;
break;
}
}
list_add_tail(&ud->node_list, &ne->unit_directories);
list_add_tail(&ud->driver_list, &unit_directory_list);
return;
unit_directory_error:
if (ud != NULL)
kfree(ud);
}
static void dump_directories (struct node_entry *ne)
{
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
struct list_head *l;
HPSB_DEBUG("vendor_id=0x%06x [%s], capabilities=0x%06x",
ne->vendor_id, ne->vendor_name ?: "Unknown",
ne->capabilities);
list_for_each (l, &ne->unit_directories) {
struct unit_directory *ud = list_entry (l, struct unit_directory, node_list);
HPSB_DEBUG("unit directory:");
if (ud->flags & UNIT_DIRECTORY_VENDOR_ID)
HPSB_DEBUG(" vendor_id=0x%06x [%s]",
ud->vendor_id,
ud->vendor_name ?: "Unknown");
if (ud->flags & UNIT_DIRECTORY_MODEL_ID)
HPSB_DEBUG(" model_id=0x%06x [%s]",
ud->model_id,
ud->model_name ?: "Unknown");
if (ud->flags & UNIT_DIRECTORY_SPECIFIER_ID)
HPSB_DEBUG(" sw_specifier_id=0x%06x ", ud->specifier_id);
if (ud->flags & UNIT_DIRECTORY_VERSION)
HPSB_DEBUG(" sw_version=0x%06x ", ud->version);
}
#endif
return;
}
static void nodemgr_process_root_directory(struct node_entry *ne)
{
octlet_t address;
quadlet_t quad;
int length;
address = CSR_REGISTER_BASE + CSR_CONFIG_ROM;
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
return;
address += 4 + CONFIG_ROM_BUS_INFO_LENGTH(quad) * 4;
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
return;
length = CONFIG_ROM_ROOT_LENGTH(quad);
address += 4;
for (; length > 0; length--, address += 4) {
int code, value;
if (nodemgr_read_quadlet(ne->host, ne->nodeid, address, &quad))
return;
code = CONFIG_ROM_KEY(quad);
value = CONFIG_ROM_VALUE(quad);
switch (code) {
case CONFIG_ROM_VENDOR_ID:
ne->vendor_id = value;
/* Now check if there is a vendor name text
string. */
if (ne->vendor_name != NULL) {
length--;
address += 4;
if (nodemgr_read_text_leaf(ne->host,
ne->nodeid,
address,
ne->quadlets)
!= 0
|| ne->quadlets [0] != 0
|| ne->quadlets [1] != 0)
/* We only support minimal
ASCII and English. */
ne->vendor_name = NULL;
}
break;
case CONFIG_ROM_NODE_CAPABILITES:
ne->capabilities = value;
break;
case CONFIG_ROM_UNIT_DIRECTORY:
nodemgr_process_unit_directory(ne, address + value * 4);
break;
case CONFIG_ROM_DESCRIPTOR_LEAF:
case CONFIG_ROM_DESCRIPTOR_DIRECTORY:
/* TODO: read strings... icons? */
break;
}
}
dump_directories(ne);
}
#ifdef CONFIG_HOTPLUG
static void nodemgr_call_policy(char *verb, struct unit_directory *ud)
{
char *argv [3], **envp, *buf, *scratch;
int i = 0, value;
if (!hotplug_path [0])
return;
if (!current->fs->root)
return;
if (!(envp = (char **) kmalloc(20 * sizeof (char *), GFP_KERNEL))) {
HPSB_DEBUG ("ENOMEM");
return;
}
if (!(buf = kmalloc(256, GFP_KERNEL))) {
kfree(envp);
HPSB_DEBUG("ENOMEM2");
return;
}
/* only one standardized param to hotplug command: type */
argv[0] = hotplug_path;
argv[1] = "ieee1394";
argv[2] = 0;
/* minimal command environment */
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
/* hint that policy agent should enter no-stdout debug mode */
envp[i++] = "DEBUG=kernel";
#endif
/* extensible set of named bus-specific parameters,
* supporting multiple driver selection algorithms.
*/
scratch = buf;
envp[i++] = scratch;
scratch += sprintf(scratch, "ACTION=%s", verb) + 1;
envp[i++] = scratch;
scratch += sprintf(scratch, "VENDOR_ID=%06x", ud->ne->vendor_id) + 1;
envp[i++] = scratch;
scratch += sprintf(scratch, "GUID=%016Lx", (long long unsigned)ud->ne->guid) + 1;
envp[i++] = scratch;
scratch += sprintf(scratch, "SPECIFIER_ID=%06x", ud->specifier_id) + 1;
envp[i++] = scratch;
scratch += sprintf(scratch, "VERSION=%06x", ud->version) + 1;
envp[i++] = 0;
/* NOTE: user mode daemons can call the agents too */
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
HPSB_DEBUG("NodeMgr: %s %s %016Lx", argv[0], verb, (long long unsigned)ud->ne->guid);
#endif
value = call_usermodehelper(argv[0], argv, envp);
kfree(buf);
kfree(envp);
if (value != 0)
HPSB_DEBUG("NodeMgr: hotplug policy returned %d", value);
}
#else
static inline void
nodemgr_call_policy(char *verb, struct unit_directory *ud)
{
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
HPSB_DEBUG("NodeMgr: nodemgr_call_policy(): hotplug not enabled");
#endif
return;
}
#endif /* CONFIG_HOTPLUG */
static void nodemgr_claim_unit_directory(struct unit_directory *ud,
struct hpsb_protocol_driver *driver)
{
ud->driver = driver;
list_del(&ud->driver_list);
list_add_tail(&ud->driver_list, &driver->unit_directories);
}
static void nodemgr_release_unit_directory(struct unit_directory *ud)
{
ud->driver = NULL;
list_del(&ud->driver_list);
list_add_tail(&ud->driver_list, &unit_directory_list);
}
void hpsb_release_unit_directory(struct unit_directory *ud)
{
unsigned long flags;
write_lock_irqsave(&unit_directory_lock, flags);
nodemgr_release_unit_directory(ud);
write_unlock_irqrestore(&unit_directory_lock, flags);
}
static void nodemgr_free_unit_directories(struct node_entry *ne)
{
struct list_head *lh;
struct unit_directory *ud;
lh = ne->unit_directories.next;
while (lh != &ne->unit_directories) {
ud = list_entry(lh, struct unit_directory, node_list);
lh = lh->next;
if (ud->driver && ud->driver->disconnect)
ud->driver->disconnect(ud);
nodemgr_release_unit_directory(ud);
nodemgr_call_policy("remove", ud);
list_del(&ud->driver_list);
kfree(ud);
}
}
static struct ieee1394_device_id *
nodemgr_match_driver(struct hpsb_protocol_driver *driver,
struct unit_directory *ud)
{
struct ieee1394_device_id *id;
for (id = driver->id_table; id->match_flags != 0; id++) {
if ((id->match_flags & IEEE1394_MATCH_VENDOR_ID) &&
id->vendor_id != ud->vendor_id)
continue;
if ((id->match_flags & IEEE1394_MATCH_MODEL_ID) &&
id->model_id != ud->model_id)
continue;
if ((id->match_flags & IEEE1394_MATCH_SPECIFIER_ID) &&
id->specifier_id != ud->specifier_id)
continue;
if ((id->match_flags & IEEE1394_MATCH_VERSION) &&
id->version != ud->version)
continue;
return id;
}
return NULL;
}
static struct hpsb_protocol_driver *
nodemgr_find_driver(struct unit_directory *ud)
{
struct list_head *l;
struct hpsb_protocol_driver *match, *driver;
struct ieee1394_device_id *device_id;
match = NULL;
list_for_each(l, &driver_list) {
driver = list_entry(l, struct hpsb_protocol_driver, list);
device_id = nodemgr_match_driver(driver, ud);
if (device_id != NULL) {
match = driver;
break;
}
}
return match;
}
static void nodemgr_bind_drivers (struct node_entry *ne)
{
struct list_head *lh;
struct hpsb_protocol_driver *driver;
struct unit_directory *ud;
list_for_each(lh, &ne->unit_directories) {
ud = list_entry(lh, struct unit_directory, node_list);
driver = nodemgr_find_driver(ud);
if (driver != NULL && driver->probe(ud) == 0)
nodemgr_claim_unit_directory(ud, driver);
nodemgr_call_policy("add", ud);
}
}
int hpsb_register_protocol(struct hpsb_protocol_driver *driver)
{
struct unit_directory *ud;
struct list_head *lh;
unsigned long flags;
write_lock_irqsave(&driver_lock, flags);
list_add_tail(&driver->list, &driver_list);
write_unlock_irqrestore(&driver_lock, flags);
write_lock_irqsave(&unit_directory_lock, flags);
INIT_LIST_HEAD(&driver->unit_directories);
lh = unit_directory_list.next;
while (lh != &unit_directory_list) {
ud = list_entry(lh, struct unit_directory, driver_list);
lh = lh->next;
if (nodemgr_match_driver(driver, ud) && driver->probe(ud) == 0)
nodemgr_claim_unit_directory(ud, driver);
}
write_unlock_irqrestore(&unit_directory_lock, flags);
/*
* Right now registration always succeeds, but maybe we should
* detect clashes in protocols handled by other drivers.
*/
return 0;
}
void hpsb_unregister_protocol(struct hpsb_protocol_driver *driver)
{
struct list_head *lh;
struct unit_directory *ud;
unsigned long flags;
write_lock_irqsave(&driver_lock, flags);
list_del(&driver->list);
write_unlock_irqrestore(&driver_lock, flags);
write_lock_irqsave(&unit_directory_lock, flags);
lh = driver->unit_directories.next;
while (lh != &driver->unit_directories) {
ud = list_entry(lh, struct unit_directory, driver_list);
lh = lh->next;
if (ud->driver && ud->driver->disconnect)
ud->driver->disconnect(ud);
nodemgr_release_unit_directory(ud);
}
write_unlock_irqrestore(&unit_directory_lock, flags);
}
static void nodemgr_process_config_rom(struct node_entry *ne,
quadlet_t busoptions)
{
unsigned long flags;
ne->busopt.irmc = (busoptions >> 31) & 1;
ne->busopt.cmc = (busoptions >> 30) & 1;
ne->busopt.isc = (busoptions >> 29) & 1;
ne->busopt.bmc = (busoptions >> 28) & 1;
ne->busopt.pmc = (busoptions >> 27) & 1;
ne->busopt.cyc_clk_acc = (busoptions >> 16) & 0xff;
ne->busopt.max_rec = 1 << (((busoptions >> 12) & 0xf) + 1);
ne->busopt.generation = (busoptions >> 4) & 0xf;
ne->busopt.lnkspd = busoptions & 0x7;
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
HPSB_DEBUG("NodeMgr: raw=0x%08x irmc=%d cmc=%d isc=%d bmc=%d pmc=%d "
"cyc_clk_acc=%d max_rec=%d gen=%d lspd=%d",
busoptions, ne->busopt.irmc, ne->busopt.cmc,
ne->busopt.isc, ne->busopt.bmc, ne->busopt.pmc,
ne->busopt.cyc_clk_acc, ne->busopt.max_rec,
ne->busopt.generation, ne->busopt.lnkspd);
#endif
/*
* When the config rom changes we disconnect all drivers and
* free the cached unit directories and reread the whole
* thing. If this was a new device, the call to
* nodemgr_disconnect_drivers is a no-op and all is well.
*/
write_lock_irqsave(&unit_directory_lock, flags);
nodemgr_free_unit_directories(ne);
nodemgr_process_root_directory(ne);
nodemgr_bind_drivers(ne);
write_unlock_irqrestore(&unit_directory_lock, flags);
}
/*
* This function updates nodes that were present on the bus before the
* reset and still are after the reset. The nodeid and the config rom
* may have changed, and the drivers managing this device must be
* informed that this device just went through a bus reset, to allow
* the to take whatever actions required.
*/
static void nodemgr_update_node(struct node_entry *ne, quadlet_t busoptions,
struct hpsb_host *host, nodeid_t nodeid)
{
struct list_head *lh;
struct unit_directory *ud;
if (ne->nodeid != nodeid) {
HPSB_DEBUG("Node " NODE_BUS_FMT " changed to " NODE_BUS_FMT,
NODE_BUS_ARGS(ne->nodeid), NODE_BUS_ARGS(nodeid));
ne->nodeid = nodeid;
}
if (ne->busopt.generation != ((busoptions >> 4) & 0xf))
nodemgr_process_config_rom (ne, busoptions);
/* Since that's done, we can declare this record current */
atomic_set(&ne->generation, get_hpsb_generation(ne->host));
list_for_each (lh, &ne->unit_directories) {
ud = list_entry (lh, struct unit_directory, node_list);
if (ud->driver != NULL && ud->driver->update != NULL)
ud->driver->update(ud);
}
}
static int read_businfo_block(struct hpsb_host *host, nodeid_t nodeid,
quadlet_t *buffer, int buffer_length)
{
octlet_t base = CSR_REGISTER_BASE + CSR_CONFIG_ROM;
int retries = 3;
int header_count;
unsigned header_size;
quadlet_t quad;
int ret;
retry_configrom:
if (!retries--) {
HPSB_ERR("Giving up on node " NODE_BUS_FMT
" for ConfigROM probe, too many errors",
NODE_BUS_ARGS(nodeid));
return -1;
}
header_count = 0;
header_size = 0;
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
HPSB_INFO("Initiating ConfigROM request for node " NODE_BUS_FMT,
NODE_BUS_ARGS(nodeid));
#endif
/* Now, P1212 says that devices should support 64byte block
* reads, aligned on 64byte boundaries. That doesn't seem
* to work though, and we are forced to doing quadlet
* sized reads. */
ret = hpsb_read(host, nodeid, base, &quad, 4);
if (ret) {
HPSB_ERR("ConfigROM quadlet transaction error (%d) for node " NODE_BUS_FMT,
ret, NODE_BUS_ARGS(nodeid));
goto retry_configrom;
}
buffer[header_count++] = be32_to_cpu(quad);
header_size = buffer[0] >> 24;
if (header_size < 4) {
HPSB_INFO("Node " NODE_BUS_FMT " has non-standard ROM format (%d quads), "
"cannot parse", NODE_BUS_ARGS(nodeid), header_size);
return -1;
}
while (header_count <= header_size && header_count < buffer_length) {
ret = hpsb_read(host, nodeid, base + (header_count<<2), &quad, 4);
if (ret) {
HPSB_ERR("ConfigROM quadlet transaction error (%d) for " NODE_BUS_FMT,
ret, NODE_BUS_ARGS(nodeid));
goto retry_configrom;
}
buffer[header_count++] = be32_to_cpu(quad);
}
return 0;
}
static void nodemgr_remove_node(struct node_entry *ne)
{
unsigned long flags;
HPSB_DEBUG("%s removed: Node[" NODE_BUS_FMT "] GUID[%016Lx] [%s]",
(ne->host->node_id == ne->nodeid) ? "Local host" : "Device",
NODE_BUS_ARGS(ne->nodeid), (unsigned long long)ne->guid,
ne->vendor_name ?: "Unknown");
write_lock_irqsave(&unit_directory_lock, flags);
nodemgr_free_unit_directories(ne);
write_unlock_irqrestore(&unit_directory_lock, flags);
list_del(&ne->list);
kfree(ne);
return;
}
/* This is where we probe the nodes for their information and provided
* features. */
static void nodemgr_node_probe_one(struct hpsb_host *host, nodeid_t nodeid)
{
struct node_entry *ne;
quadlet_t buffer[5];
octlet_t guid;
/* We need to detect when the ConfigROM's generation has changed,
* so we only update the node's info when it needs to be. */
if (read_businfo_block (host, nodeid, buffer, sizeof(buffer) >> 2))
return;
if (buffer[1] != IEEE1394_BUSID_MAGIC) {
/* This isn't a 1394 device */
HPSB_ERR("Node " NODE_BUS_FMT " isn't an IEEE 1394 device",
NODE_BUS_ARGS(nodeid));
return;
}
guid = ((u64)buffer[3] << 32) | buffer[4];
ne = hpsb_guid_get_entry(guid);
if (!ne)
nodemgr_create_node(guid, buffer[2], host, nodeid);
else
nodemgr_update_node(ne, buffer[2], host, nodeid);
return;
}
static void nodemgr_node_probe_cleanup(struct hpsb_host *host)
{
unsigned long flags;
struct list_head *lh, *next;
struct node_entry *ne;
/* Now check to see if we have any nodes that aren't referenced
* any longer. */
write_lock_irqsave(&node_lock, flags);
list_for_each_safe(lh, next, &node_list) {
ne = list_entry(lh, struct node_entry, list);
/* Only checking this host */
if (ne->host != host)
continue;
/* If the generation didn't get updated, then either the
* node was removed, or it failed the above probe. Either
* way, we remove references to it, since they are
* invalid. */
if (!hpsb_node_entry_valid(ne))
nodemgr_remove_node(ne);
}
write_unlock_irqrestore(&node_lock, flags);
return;
}
static void nodemgr_node_probe(struct hpsb_host *host)
{
int nodecount = host->node_count;
struct selfid *sid = (struct selfid *)host->topology_map;
nodeid_t nodeid = LOCAL_BUS;
/* Scan each node on the bus */
for (; nodecount; nodecount--, nodeid++, sid++) {
while (sid->extended)
sid++;
if (!sid->link_active)
continue;
nodemgr_node_probe_one(host, nodeid);
}
/* Cleanup if needed */
nodemgr_node_probe_cleanup(host);
return;
}
static int nodemgr_host_thread(void *__hi)
{
struct host_info *hi = (struct host_info *)__hi;
lock_kernel();
/* No userlevel access needed */
daemonize();
strcpy(current->comm, "NodeMgr");
complete(&hi->started);
/* Sit and wait for a signal to probe the nodes on the bus. This
* happens when we get a bus reset. */
do {
interruptible_sleep_on(&hi->wait);
if (!signal_pending(current))
nodemgr_node_probe(hi->host);
} while (!signal_pending(current));
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
HPSB_DEBUG ("NodeMgr: Exiting thread for %s", hi->host->driver->name);
#endif
unlock_kernel();
complete_and_exit(&hi->exited, 0);
}
struct node_entry *hpsb_guid_get_entry(u64 guid)
{
unsigned long flags;
struct node_entry *ne;
read_lock_irqsave(&node_lock, flags);
ne = find_entry_by_guid(guid);
read_unlock_irqrestore(&node_lock, flags);
return ne;
}
struct node_entry *hpsb_nodeid_get_entry(nodeid_t nodeid)
{
unsigned long flags;
struct node_entry *ne;
read_lock_irqsave(&node_lock, flags);
ne = find_entry_by_nodeid(nodeid);
read_unlock_irqrestore(&node_lock, flags);
return ne;
}
struct hpsb_host *hpsb_get_host_by_ne(struct node_entry *ne)
{
if (atomic_read(&ne->generation) != get_hpsb_generation(ne->host))
return NULL;
if (ne->nodeid == ne->host->node_id) return ne->host;
return NULL;
}
int hpsb_guid_fill_packet(struct node_entry *ne, struct hpsb_packet *pkt)
{
if (atomic_read(&ne->generation) != get_hpsb_generation(ne->host))
return 0;
pkt->host = ne->host;
pkt->node_id = ne->nodeid;
pkt->generation = atomic_read(&ne->generation);
return 1;
}
static void nodemgr_add_host(struct hpsb_host *host)
{
struct host_info *hi = kmalloc (sizeof (struct host_info), GFP_KERNEL);
unsigned long flags;
if (!hi) {
HPSB_ERR ("NodeMgr: out of memory in add host");
return;
}
/* We simply initialize the struct here. We don't start the thread
* until the first bus reset. */
hi->host = host;
INIT_LIST_HEAD(&hi->list);
init_completion(&hi->started);
init_completion(&hi->exited);
init_waitqueue_head(&hi->wait);
hi->pid = kernel_thread(nodemgr_host_thread, hi, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
if (hi->pid < 0) {
HPSB_ERR ("NodeMgr: failed to start NodeMgr thread for %s", host->driver->name);
return;
}
wait_for_completion(&hi->started);
spin_lock_irqsave (&host_info_lock, flags);
list_add_tail (&hi->list, &host_info_list);
spin_unlock_irqrestore (&host_info_lock, flags);
return;
}
static void nodemgr_host_reset(struct hpsb_host *host)
{
struct list_head *lh;
struct host_info *hi = NULL;
unsigned long flags;
spin_lock_irqsave (&host_info_lock, flags);
list_for_each(lh, &host_info_list) {
struct host_info *myhi = list_entry(lh, struct host_info, list);
if (myhi->host == host) {
hi = myhi;
break;
}
}
if (hi == NULL) {
HPSB_ERR ("NodeMgr: could not process reset of non-existent host");
goto done_reset_host;
}
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
HPSB_DEBUG ("NodeMgr: Processing host reset for %s", host->driver->name);
#endif
wake_up(&hi->wait);
done_reset_host:
spin_unlock_irqrestore (&host_info_lock, flags);
return;
}
static void nodemgr_remove_host(struct hpsb_host *host)
{
struct list_head *lh, *next;
struct node_entry *ne;
unsigned long flags;
struct host_info *hi = NULL;
spin_lock_irqsave (&host_info_lock, flags);
list_for_each_safe(lh, next, &host_info_list) {
struct host_info *myhi = list_entry(lh, struct host_info, list);
if (myhi->host == host) {
list_del(&myhi->list);
hi = myhi;
break;
}
}
if (!hi)
HPSB_ERR ("NodeMgr: host %s does not exist, cannot remove",
host->driver->name);
spin_unlock_irqrestore (&host_info_lock, flags);
/* Even if we fail the host_info part, remove all the node
* entries. */
write_lock_irqsave(&node_lock, flags);
list_for_each_safe(lh, next, &node_list) {
ne = list_entry(lh, struct node_entry, list);
if (ne->host == host)
nodemgr_remove_node(ne);
}
write_unlock_irqrestore(&node_lock, flags);
if (hi) {
if (hi->pid >= 0) {
kill_proc(hi->pid, SIGTERM, 1);
wait_for_completion(&hi->exited);
}
kfree(hi);
}
return;
}
static struct hpsb_highlevel_ops nodemgr_ops = {
add_host: nodemgr_add_host,
host_reset: nodemgr_host_reset,
remove_host: nodemgr_remove_host,
};
static struct hpsb_highlevel *hl;
#define PROC_ENTRY "devices"
void init_ieee1394_nodemgr(void)
{
#ifdef CONFIG_PROC_FS
if (!create_proc_read_entry(PROC_ENTRY, 0444, ieee1394_procfs_entry, raw1394_read_proc, NULL))
HPSB_ERR("Can't create devices procfs entry");
#endif
hl = hpsb_register_highlevel("Node manager", &nodemgr_ops);
if (!hl) {
HPSB_ERR("NodeMgr: out of memory during ieee1394 initialization");
}
}
void cleanup_ieee1394_nodemgr(void)
{
hpsb_unregister_highlevel(hl);
#ifdef CONFIG_PROC_FS
remove_proc_entry(PROC_ENTRY, ieee1394_procfs_entry);
#endif
}