| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <stdio.h> | |
| #include <string.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 ); | |
| } | |
| } | |
| /* Drag in objects via xenbus_set_state() */ | |
| REQUIRING_SYMBOL ( xenbus_set_state ); | |
| /* Drag in backing hardware device */ | |
| REQUIRE_OBJECT ( hvm ); |