| /* |
| * 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 ); |
| } |
| } |