/* $NetBSD: xenbus_probe.c,v 1.62 2025/02/06 15:51:58 bouyer Exp $ */
/******************************************************************************
* Talks to Xen Store to figure out what devices we have.
*
* Copyright (C) 2005 Rusty Russell, IBM Corporation
* Copyright (C) 2005 Mike Wray, Hewlett-Packard
* Copyright (C) 2005 XenSource Ltd
*
* This file may be distributed separately from the Linux kernel, or
* incorporated into other software packages, subject to the following license:
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this source file (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
static int
read_otherend_details(struct xenbus_device *xendev,
const char *id_node, const char *path_node)
{
int err;
unsigned long id;
err = xenbus_read_ul(NULL, xendev->xbusd_path, id_node, &id, 10);
if (err) {
printf("reading other end details %s from %s\n",
id_node, xendev->xbusd_path);
xenbus_dev_fatal(xendev, err,
"reading other end details %s from %s",
id_node, xendev->xbusd_path);
return err;
}
xendev->xbusd_otherend_id = (int)id;
err = xenbus_read(NULL, xendev->xbusd_path, path_node,
xendev->xbusd_otherend, sizeof(xendev->xbusd_otherend));
if (err) {
printf("reading other end details %s from %s (%d)\n",
path_node, xendev->xbusd_path, err);
xenbus_dev_fatal(xendev, err,
"reading other end details %s from %s",
path_node, xendev->xbusd_path);
return err;
}
DPRINTK("read_otherend_details: read %s/%s returned %s\n",
xendev->xbusd_path, path_node, xendev->xbusd_otherend);
if (strlen(xendev->xbusd_otherend) == 0 ||
!xenbus_exists(NULL, xendev->xbusd_otherend, "")) {
printf("missing other end from %s\n", xendev->xbusd_path);
xenbus_dev_fatal(xendev, -ENOENT, "missing other end from %s",
xendev->xbusd_path);
free_otherend_details(xendev);
return ENOENT;
}
/* Protect us against watches firing on old details when the otherend
details change, say immediately after a resume. */
if (strncmp(xdev->xbusd_otherend, vec[XS_WATCH_PATH],
strlen(xdev->xbusd_otherend))) {
DPRINTK("Ignoring watch at %s", vec[XS_WATCH_PATH]);
return;
}
state = xenbus_read_driver_state(xdev->xbusd_otherend);
DPRINTK("state is %d, %s, %s",
state, xdev->xbusd_otherend_watch.node, vec[XS_WATCH_PATH]);
if (state == XenbusStateClosed) {
int error;
if (xdev->xbusd_type == XENBUS_BACKEND_DEVICE) {
error = xdev->xbusd_u.b.b_detach(
xdev->xbusd_u.b.b_cookie);
if (error) {
printf("could not detach %s: %d\n",
xdev->xbusd_path, error);
return;
}
} else {
error = config_detach(xdev->xbusd_u.f.f_dev,
DETACH_FORCE);
if (error) {
printf("could not detach %s: %d\n",
device_xname(xdev->xbusd_u.f.f_dev), error);
return;
}
}
xenbus_free_device(xdev);
return;
}
if (xdev->xbusd_otherend_changed)
xdev->xbusd_otherend_changed(
(xdev->xbusd_type == XENBUS_BACKEND_DEVICE) ?
xdev->xbusd_u.b.b_cookie : xdev->xbusd_u.f.f_dev, state);
}
static int
watch_otherend(struct xenbus_device *dev)
{
free_otherend_watch(dev);
id_sz = sizeof(unsigned long) * dir_n;
id = kmem_zalloc(id_sz, KM_SLEEP);
/* Convert string values to numeric; skip invalid */
for (i = 0; i < dir_n; i++) {
/*
* Add one to differentiate numerical zero from invalid
* string. Has no effect on sort order.
*/
id[i] = strtoul(dir[i], &ep, 10) + 1;
if (dir[i][0] == '\0' || *ep != '\0')
id[i] = 0;
}
/* Build lookup table in ascending order */
for (pos = 0; pos < dir_n; ) {
minv = UINT32_MAX;
minp = -1;
for (i = 0; i < dir_n; i++) {
if (id[i] < minv && id[i] > 0) {
minv = id[i];
minp = i;
}
}
if (minp >= 0) {
lookup[pos++] = minp;
id[minp] = 0;
}
else
break;
}
kmem_free(id, id_sz);
/* Adjust in case we had to skip non-numeric entries */
dir_n = pos;
}
for (pos = 0; pos < dir_n; pos++) {
err = 0;
if (lookup)
i = lookup[pos];
else
i = pos;
/*
* add size of path to size of xenbus_device. xenbus_device
* already has room for one char in xbusd_path.
*/
msize = sizeof(*xbusd) + strlen(path) + strlen(dir[i]) + 2;
xbusd = kmem_zalloc(msize, KM_SLEEP);
xbusd->xbusd_sz = msize;
xbusd->xbusd_dmat = xenbus_dmat;
snprintf(__UNCONST(xbusd->xbusd_path),
msize - sizeof(*xbusd) + 1, "%s/%s", path, dir[i]);
if (xenbus_lookup_device_path(xbusd->xbusd_path) != NULL) {
/* device already registered */
kmem_free(xbusd, xbusd->xbusd_sz);
continue;
}
err = xenbus_read_ul(NULL, xbusd->xbusd_path, "state",
&state, 10);
if (err) {
aprint_error_dev(xenbus_dev, "can't get state "
"for %s (%d)\n", xbusd->xbusd_path, err);
kmem_free(xbusd, xbusd->xbusd_sz);
err = 0;
continue;
}
if (state != XenbusStateInitialising) {
/* device is not new */
kmem_free(xbusd, xbusd->xbusd_sz);
continue;
}
xbusd->xbusd_otherend_watch.xbw_dev = xbusd;
DPRINTK("xenbus_probe_device_type probe %s\n",
xbusd->xbusd_path);
if (create != NULL) {
xbusd->xbusd_type = XENBUS_BACKEND_DEVICE;
err = read_frontend_details(xbusd);
if (err != 0) {
aprint_error_dev(xenbus_dev,
"can't get frontend details for %s (%d)\n",
xbusd->xbusd_path, err);
break;
}
if (create(xbusd)) {
kmem_free(xbusd, xbusd->xbusd_sz);
continue;
}
} else {
xbusd->xbusd_type = XENBUS_FRONTEND_DEVICE;
xa.xa_xbusd = xbusd;
xa.xa_type = type;
xa.xa_id = strtoul(dir[i], &ep, 0);
if (dir[i][0] == '\0' || *ep != '\0') {
aprint_error_dev(xenbus_dev,
"device type %s: id %s is not a number\n",
type, dir[i]);
err = EFTYPE;
kmem_free(xbusd, xbusd->xbusd_sz);
break;
}
if (strcmp(xa.xa_type, "vbd") == 0) {
char dtype[10];
if (xenbus_read(NULL, xbusd->xbusd_path,
"device-type", dtype, sizeof(dtype)) !=0) {
aprint_error_dev(xenbus_dev,
"%s: can't read device-type\n",
xbusd->xbusd_path);
kmem_free(xbusd, xbusd->xbusd_sz);
break;
}
if (vm_guest == VM_GUEST_XENPVHVM &&
strcmp(dtype, "cdrom") == 0) {
aprint_verbose_dev(xenbus_dev,
"ignoring %s type cdrom\n",
xbusd->xbusd_path);
kmem_free(xbusd, xbusd->xbusd_sz);
continue;
}
}
err = read_backend_details(xbusd);
if (err != 0) {
aprint_error_dev(xenbus_dev,
"can't get backend details for %s (%d)\n",
xbusd->xbusd_path, err);
kmem_free(xbusd, xbusd->xbusd_sz);
break;
}
for (i = 0; i < dir_n; i++) {
/*
* console is configured through xen_start_info when
* xencons is attaching to hypervisor, so avoid console
* probing when configuring xenbus devices
*/
if (strcmp(dir[i], "console") == 0)
continue;
/* We watch for devices appearing and vanishing. */
static struct xenbus_watch fe_watch;
static struct xenbus_watch be_watch;
/* A flag to determine if xenstored is 'ready' (i.e. has started) */
int xenstored_ready = 0;
static kmutex_t xenstored_lock;
static kcondvar_t xenstored_cv;
/* Next allocate a local port which xenstored can bind to */
op.cmd = EVTCHNOP_alloc_unbound;
op.u.alloc_unbound.dom = DOMID_SELF;
op.u.alloc_unbound.remote_dom = 0;