blob: 5dd01dfa31a8ee0cbe56943665997cbde1524de6 [file] [log] [blame]
/*
* Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdio.h>
#include <errno.h>
#include <ipxe/malloc.h>
#include <ipxe/device.h>
#include <ipxe/timer.h>
#include <ipxe/nap.h>
#include <ipxe/xen.h>
#include <ipxe/xenstore.h>
#include <ipxe/xenbus.h>
/** @file
*
* Xen device bus
*
*/
/* Disambiguate the various error causes */
#define ETIMEDOUT_UNKNOWN \
__einfo_error ( EINFO_ETIMEDOUT_UNKNOWN )
#define EINFO_ETIMEDOUT_UNKNOWN \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateUnknown, \
"Unknown" )
#define ETIMEDOUT_INITIALISING \
__einfo_error ( EINFO_ETIMEDOUT_INITIALISING )
#define EINFO_ETIMEDOUT_INITIALISING \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitialising, \
"Initialising" )
#define ETIMEDOUT_INITWAIT \
__einfo_error ( EINFO_ETIMEDOUT_INITWAIT )
#define EINFO_ETIMEDOUT_INITWAIT \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitWait, \
"InitWait" )
#define ETIMEDOUT_INITIALISED \
__einfo_error ( EINFO_ETIMEDOUT_INITIALISED )
#define EINFO_ETIMEDOUT_INITIALISED \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitialised, \
"Initialised" )
#define ETIMEDOUT_CONNECTED \
__einfo_error ( EINFO_ETIMEDOUT_CONNECTED )
#define EINFO_ETIMEDOUT_CONNECTED \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateConnected, \
"Connected" )
#define ETIMEDOUT_CLOSING \
__einfo_error ( EINFO_ETIMEDOUT_CLOSING )
#define EINFO_ETIMEDOUT_CLOSING \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateClosing, \
"Closing" )
#define ETIMEDOUT_CLOSED \
__einfo_error ( EINFO_ETIMEDOUT_CLOSED )
#define EINFO_ETIMEDOUT_CLOSED \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateClosed, \
"Closed" )
#define ETIMEDOUT_RECONFIGURING \
__einfo_error ( EINFO_ETIMEDOUT_RECONFIGURING )
#define EINFO_ETIMEDOUT_RECONFIGURING \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateReconfiguring, \
"Reconfiguring" )
#define ETIMEDOUT_RECONFIGURED \
__einfo_error ( EINFO_ETIMEDOUT_RECONFIGURED )
#define EINFO_ETIMEDOUT_RECONFIGURED \
__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateReconfigured, \
"Reconfigured" )
#define ETIMEDOUT_STATE( state ) \
EUNIQ ( EINFO_ETIMEDOUT, (state), ETIMEDOUT_UNKNOWN, \
ETIMEDOUT_INITIALISING, ETIMEDOUT_INITWAIT, \
ETIMEDOUT_INITIALISED, ETIMEDOUT_CONNECTED, \
ETIMEDOUT_CLOSING, ETIMEDOUT_CLOSED, \
ETIMEDOUT_RECONFIGURING, ETIMEDOUT_RECONFIGURED )
/** Maximum time to wait for backend to reach a given state, in ticks */
#define XENBUS_BACKEND_TIMEOUT ( 5 * TICKS_PER_SEC )
/**
* Set device state
*
* @v xendev Xen device
* @v state New state
* @ret rc Return status code
*/
int xenbus_set_state ( struct xen_device *xendev, int state ) {
int rc;
/* Attempt to set state */
if ( ( rc = xenstore_write_num ( xendev->xen, state, xendev->key,
"state", NULL ) ) != 0 ) {
DBGC ( xendev, "XENBUS %s could not set state=\"%d\": %s\n",
xendev->key, state, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Get backend state
*
* @v xendev Xen device
* @ret state Backend state, or negative error
*/
int xenbus_backend_state ( struct xen_device *xendev ) {
unsigned long state;
int rc;
/* Attempt to get backend state */
if ( ( rc = xenstore_read_num ( xendev->xen, &state, xendev->backend,
"state", NULL ) ) != 0 ) {
DBGC ( xendev, "XENBUS %s could not read %s/state: %s\n",
xendev->key, xendev->backend, strerror ( rc ) );
return rc;
}
return state;
}
/**
* Wait for backend to reach a given state
*
* @v xendev Xen device
* @v state Desired backend state
* @ret rc Return status code
*/
int xenbus_backend_wait ( struct xen_device *xendev, int state ) {
unsigned long started = currticks();
unsigned long elapsed;
unsigned int attempts = 0;
int current_state;
int rc;
/* Wait for backend to reach this state */
do {
/* Get current backend state */
current_state = xenbus_backend_state ( xendev );
if ( current_state < 0 ) {
rc = current_state;
return rc;
}
if ( current_state == state )
return 0;
/* Allow time for backend to react */
cpu_nap();
/* XenStore is a very slow interface; any fixed delay
* time would be dwarfed by the XenStore access time.
* We therefore use wall clock to time out this
* operation.
*/
elapsed = ( currticks() - started );
attempts++;
} while ( elapsed < XENBUS_BACKEND_TIMEOUT );
/* Construct status code from current backend state */
rc = -ETIMEDOUT_STATE ( current_state );
DBGC ( xendev, "XENBUS %s timed out after %d attempts waiting for "
"%s/state=\"%d\": %s\n", xendev->key, attempts, xendev->backend,
state, strerror ( rc ) );
return rc;
}
/**
* Find driver for Xen device
*
* @v type Device type
* @ret driver Driver, or NULL
*/
static struct xen_driver * xenbus_find_driver ( const char *type ) {
struct xen_driver *xendrv;
for_each_table_entry ( xendrv, XEN_DRIVERS ) {
if ( strcmp ( xendrv->type, type ) == 0 )
return xendrv;
}
return NULL;
}
/**
* Probe Xen device
*
* @v xen Xen hypervisor
* @v parent Parent device
* @v instance Device instance
* @v driver Device driver
* @ret rc Return status code
*/
static int xenbus_probe_device ( struct xen_hypervisor *xen,
struct device *parent, const char *instance,
struct xen_driver *driver ) {
const char *type = driver->type;
struct xen_device *xendev;
size_t key_len;
int rc;
/* Allocate and initialise structure */
key_len = ( 7 /* "device/" */ + strlen ( type ) + 1 /* "/" */ +
strlen ( instance ) + 1 /* NUL */ );
xendev = zalloc ( sizeof ( *xendev ) + key_len );
if ( ! xendev ) {
rc = -ENOMEM;
goto err_alloc;
}
snprintf ( xendev->dev.name, sizeof ( xendev->dev.name ), "%s/%s",
type, instance );
xendev->dev.desc.bus_type = BUS_TYPE_XEN;
INIT_LIST_HEAD ( &xendev->dev.children );
list_add_tail ( &xendev->dev.siblings, &parent->children );
xendev->dev.parent = parent;
xendev->xen = xen;
xendev->key = ( ( void * ) ( xendev + 1 ) );
snprintf ( xendev->key, key_len, "device/%s/%s", type, instance );
xendev->driver = driver;
xendev->dev.driver_name = driver->name;
DBGC ( xendev, "XENBUS %s has driver \"%s\"\n", xendev->key,
xendev->driver->name );
/* Read backend key */
if ( ( rc = xenstore_read ( xen, &xendev->backend, xendev->key,
"backend", NULL ) ) != 0 ) {
DBGC ( xendev, "XENBUS %s could not read backend: %s\n",
xendev->key, strerror ( rc ) );
goto err_read_backend;
}
/* Read backend domain ID */
if ( ( rc = xenstore_read_num ( xen, &xendev->backend_id, xendev->key,
"backend-id", NULL ) ) != 0 ) {
DBGC ( xendev, "XENBUS %s could not read backend-id: %s\n",
xendev->key, strerror ( rc ) );
goto err_read_backend_id;
}
DBGC ( xendev, "XENBUS %s backend=\"%s\" in domain %ld\n",
xendev->key, xendev->backend, xendev->backend_id );
/* Probe driver */
if ( ( rc = xendev->driver->probe ( xendev ) ) != 0 ) {
DBGC ( xendev, "XENBUS could not probe %s: %s\n",
xendev->key, strerror ( rc ) );
goto err_probe;
}
return 0;
xendev->driver->remove ( xendev );
err_probe:
err_read_backend_id:
free ( xendev->backend );
err_read_backend:
list_del ( &xendev->dev.siblings );
free ( xendev );
err_alloc:
return rc;
}
/**
* Remove Xen device
*
* @v xendev Xen device
*/
static void xenbus_remove_device ( struct xen_device *xendev ) {
/* Remove device */
xendev->driver->remove ( xendev );
free ( xendev->backend );
list_del ( &xendev->dev.siblings );
free ( xendev );
}
/**
* Probe Xen devices of a given type
*
* @v xen Xen hypervisor
* @v parent Parent device
* @v type Device type
* @ret rc Return status code
*/
static int xenbus_probe_type ( struct xen_hypervisor *xen,
struct device *parent, const char *type ) {
struct xen_driver *driver;
char *children;
char *child;
size_t len;
int rc;
/* Look for a driver */
driver = xenbus_find_driver ( type );
if ( ! driver ) {
DBGC ( xen, "XENBUS has no driver for \"%s\" devices\n", type );
/* Not a fatal error */
rc = 0;
goto err_no_driver;
}
/* Get children of this key */
if ( ( rc = xenstore_directory ( xen, &children, &len, "device",
type, NULL ) ) != 0 ) {
DBGC ( xen, "XENBUS could not list \"%s\" devices: %s\n",
type, strerror ( rc ) );
goto err_directory;
}
/* Probe each child */
for ( child = children ; child < ( children + len ) ;
child += ( strlen ( child ) + 1 /* NUL */ ) ) {
if ( ( rc = xenbus_probe_device ( xen, parent, child,
driver ) ) != 0 )
goto err_probe_device;
}
free ( children );
return 0;
err_probe_device:
free ( children );
err_directory:
err_no_driver:
return rc;
}
/**
* Probe Xen bus
*
* @v xen Xen hypervisor
* @v parent Parent device
* @ret rc Return status code
*/
int xenbus_probe ( struct xen_hypervisor *xen, struct device *parent ) {
char *types;
char *type;
size_t len;
int rc;
/* Get children of "device" key */
if ( ( rc = xenstore_directory ( xen, &types, &len, "device",
NULL ) ) != 0 ) {
DBGC ( xen, "XENBUS could not list device types: %s\n",
strerror ( rc ) );
goto err_directory;
}
/* Probe each child type */
for ( type = types ; type < ( types + len ) ;
type += ( strlen ( type ) + 1 /* NUL */ ) ) {
if ( ( rc = xenbus_probe_type ( xen, parent, type ) ) != 0 )
goto err_probe_type;
}
free ( types );
return 0;
xenbus_remove ( xen, parent );
err_probe_type:
free ( types );
err_directory:
return rc;
}
/**
* Remove Xen bus
*
* @v xen Xen hypervisor
* @v parent Parent device
*/
void xenbus_remove ( struct xen_hypervisor *xen __unused,
struct device *parent ) {
struct xen_device *xendev;
struct xen_device *tmp;
/* Remove devices */
list_for_each_entry_safe ( xendev, tmp, &parent->children,
dev.siblings ) {
xenbus_remove_device ( xendev );
}
}