484 lines
14 KiB
C
484 lines
14 KiB
C
/*
|
|
* VAS_EBOOT -- GRand Unified Bootloader
|
|
* Copyright (C) 2013 Free Software Foundation, Inc.
|
|
*
|
|
* VAS_EBOOT 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* VAS_EBOOT 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 VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <VasEBoot/disk.h>
|
|
#include <VasEBoot/dl.h>
|
|
#include <VasEBoot/mm.h>
|
|
#include <VasEBoot/types.h>
|
|
#include <VasEBoot/misc.h>
|
|
#include <VasEBoot/err.h>
|
|
#include <VasEBoot/term.h>
|
|
#include <VasEBoot/i18n.h>
|
|
#include <VasEBoot/xen.h>
|
|
#include <VasEBoot/time.h>
|
|
#include <xen/io/blkif.h>
|
|
|
|
struct virtdisk
|
|
{
|
|
int handle;
|
|
char *fullname;
|
|
char *backend_dir;
|
|
char *frontend_dir;
|
|
struct blkif_sring *shared_page;
|
|
struct blkif_front_ring ring;
|
|
VasEBoot_xen_grant_t grant;
|
|
VasEBoot_xen_evtchn_t evtchn;
|
|
void *dma_page;
|
|
VasEBoot_xen_grant_t dma_grant;
|
|
struct virtdisk *compat_next;
|
|
};
|
|
|
|
#define xen_wmb() mb()
|
|
#define xen_mb() mb()
|
|
|
|
static struct virtdisk *virtdisks;
|
|
static VasEBoot_size_t vdiskcnt;
|
|
struct virtdisk *compat_head;
|
|
|
|
static int
|
|
VasEBoot_virtdisk_iterate (VasEBoot_disk_dev_iterate_hook_t hook, void *hook_data,
|
|
VasEBoot_disk_pull_t pull)
|
|
{
|
|
VasEBoot_size_t i;
|
|
|
|
if (pull != VAS_EBOOT_DISK_PULL_NONE)
|
|
return 0;
|
|
|
|
for (i = 0; i < vdiskcnt; i++)
|
|
if (hook (virtdisks[i].fullname, hook_data))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static VasEBoot_err_t
|
|
VasEBoot_virtdisk_open (const char *name, VasEBoot_disk_t disk)
|
|
{
|
|
int i;
|
|
VasEBoot_uint32_t secsize;
|
|
char fdir[200];
|
|
char *buf;
|
|
int num = -1;
|
|
struct virtdisk *vd;
|
|
|
|
/* For compatibility with pv-VasEBoot legacy menu.lst accept hdX as disk name */
|
|
if (name[0] == 'h' && name[1] == 'd' && name[2])
|
|
{
|
|
num = VasEBoot_strtoul (name + 2, 0, 10);
|
|
if (VasEBoot_errno)
|
|
{
|
|
VasEBoot_errno = 0;
|
|
num = -1;
|
|
}
|
|
}
|
|
for (i = 0, vd = compat_head; vd; vd = vd->compat_next, i++)
|
|
if (i == num || VasEBoot_strcmp (name, vd->fullname) == 0)
|
|
break;
|
|
if (!vd)
|
|
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "not a virtdisk");
|
|
disk->data = vd;
|
|
disk->id = vd - virtdisks;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/sectors", vd->backend_dir);
|
|
buf = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
if (!buf)
|
|
return VasEBoot_errno;
|
|
disk->total_sectors = VasEBoot_strtoull (buf, 0, 10);
|
|
if (VasEBoot_errno)
|
|
return VasEBoot_errno;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/sector-size", vd->backend_dir);
|
|
buf = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
if (!buf)
|
|
return VasEBoot_errno;
|
|
secsize = VasEBoot_strtoull (buf, 0, 10);
|
|
if (VasEBoot_errno)
|
|
return VasEBoot_errno;
|
|
|
|
if ((secsize & (secsize - 1)) || !secsize || secsize < 512
|
|
|| secsize > VAS_EBOOT_XEN_PAGE_SIZE)
|
|
return VasEBoot_error (VAS_EBOOT_ERR_IO, "unsupported sector size %d", secsize);
|
|
|
|
disk->log_sector_size = VasEBoot_log2ull (secsize);
|
|
disk->total_sectors >>= disk->log_sector_size - 9;
|
|
|
|
return VAS_EBOOT_ERR_NONE;
|
|
}
|
|
|
|
static void
|
|
VasEBoot_virtdisk_close (VasEBoot_disk_t disk __attribute__ ((unused)))
|
|
{
|
|
}
|
|
|
|
static VasEBoot_err_t
|
|
VasEBoot_virtdisk_read (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector,
|
|
VasEBoot_size_t size, char *buf)
|
|
{
|
|
struct virtdisk *data = disk->data;
|
|
|
|
while (size)
|
|
{
|
|
VasEBoot_size_t cur;
|
|
struct blkif_request *req;
|
|
struct blkif_response *resp;
|
|
int sta = 0;
|
|
struct evtchn_send send;
|
|
cur = size;
|
|
if (cur > (unsigned) (VAS_EBOOT_XEN_PAGE_SIZE >> disk->log_sector_size))
|
|
cur = VAS_EBOOT_XEN_PAGE_SIZE >> disk->log_sector_size;
|
|
while (RING_FULL (&data->ring))
|
|
VasEBoot_xen_sched_op (SCHEDOP_yield, 0);
|
|
req = RING_GET_REQUEST (&data->ring, data->ring.req_prod_pvt);
|
|
req->operation = BLKIF_OP_READ;
|
|
req->nr_segments = 1;
|
|
req->handle = data->handle;
|
|
req->id = 0;
|
|
req->sector_number = sector << (disk->log_sector_size - 9);
|
|
req->seg[0].gref = data->dma_grant;
|
|
req->seg[0].first_sect = 0;
|
|
req->seg[0].last_sect = (cur << (disk->log_sector_size - 9)) - 1;
|
|
data->ring.req_prod_pvt++;
|
|
RING_PUSH_REQUESTS (&data->ring);
|
|
mb ();
|
|
send.port = data->evtchn;
|
|
VasEBoot_xen_event_channel_op (EVTCHNOP_send, &send);
|
|
|
|
while (!RING_HAS_UNCONSUMED_RESPONSES (&data->ring))
|
|
{
|
|
VasEBoot_xen_sched_op (SCHEDOP_yield, 0);
|
|
mb ();
|
|
}
|
|
while (1)
|
|
{
|
|
int wtd;
|
|
RING_FINAL_CHECK_FOR_RESPONSES (&data->ring, wtd);
|
|
if (!wtd)
|
|
break;
|
|
resp = RING_GET_RESPONSE (&data->ring, data->ring.rsp_cons);
|
|
data->ring.rsp_cons++;
|
|
if (resp->status)
|
|
sta = resp->status;
|
|
}
|
|
if (sta)
|
|
return VasEBoot_error (VAS_EBOOT_ERR_IO, "read failed");
|
|
VasEBoot_memcpy (buf, data->dma_page, cur << disk->log_sector_size);
|
|
size -= cur;
|
|
sector += cur;
|
|
buf += cur << disk->log_sector_size;
|
|
}
|
|
return VAS_EBOOT_ERR_NONE;
|
|
}
|
|
|
|
static VasEBoot_err_t
|
|
VasEBoot_virtdisk_write (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector,
|
|
VasEBoot_size_t size, const char *buf)
|
|
{
|
|
struct virtdisk *data = disk->data;
|
|
|
|
while (size)
|
|
{
|
|
VasEBoot_size_t cur;
|
|
struct blkif_request *req;
|
|
struct blkif_response *resp;
|
|
int sta = 0;
|
|
struct evtchn_send send;
|
|
cur = size;
|
|
if (cur > (unsigned) (VAS_EBOOT_XEN_PAGE_SIZE >> disk->log_sector_size))
|
|
cur = VAS_EBOOT_XEN_PAGE_SIZE >> disk->log_sector_size;
|
|
|
|
VasEBoot_memcpy (data->dma_page, buf, cur << disk->log_sector_size);
|
|
|
|
while (RING_FULL (&data->ring))
|
|
VasEBoot_xen_sched_op (SCHEDOP_yield, 0);
|
|
req = RING_GET_REQUEST (&data->ring, data->ring.req_prod_pvt);
|
|
req->operation = BLKIF_OP_WRITE;
|
|
req->nr_segments = 1;
|
|
req->handle = data->handle;
|
|
req->id = 0;
|
|
req->sector_number = sector << (disk->log_sector_size - 9);
|
|
req->seg[0].gref = data->dma_grant;
|
|
req->seg[0].first_sect = 0;
|
|
req->seg[0].last_sect = (cur << (disk->log_sector_size - 9)) - 1;
|
|
data->ring.req_prod_pvt++;
|
|
RING_PUSH_REQUESTS (&data->ring);
|
|
mb ();
|
|
send.port = data->evtchn;
|
|
VasEBoot_xen_event_channel_op (EVTCHNOP_send, &send);
|
|
|
|
while (!RING_HAS_UNCONSUMED_RESPONSES (&data->ring))
|
|
{
|
|
VasEBoot_xen_sched_op (SCHEDOP_yield, 0);
|
|
mb ();
|
|
}
|
|
while (1)
|
|
{
|
|
int wtd;
|
|
RING_FINAL_CHECK_FOR_RESPONSES (&data->ring, wtd);
|
|
if (!wtd)
|
|
break;
|
|
resp = RING_GET_RESPONSE (&data->ring, data->ring.rsp_cons);
|
|
data->ring.rsp_cons++;
|
|
if (resp->status)
|
|
sta = resp->status;
|
|
}
|
|
if (sta)
|
|
return VasEBoot_error (VAS_EBOOT_ERR_IO, "write failed");
|
|
size -= cur;
|
|
sector += cur;
|
|
buf += cur << disk->log_sector_size;
|
|
}
|
|
return VAS_EBOOT_ERR_NONE;
|
|
}
|
|
|
|
static struct VasEBoot_disk_dev VasEBoot_virtdisk_dev = {
|
|
.name = "xen",
|
|
.id = VAS_EBOOT_DISK_DEVICE_XEN,
|
|
.disk_iterate = VasEBoot_virtdisk_iterate,
|
|
.disk_open = VasEBoot_virtdisk_open,
|
|
.disk_close = VasEBoot_virtdisk_close,
|
|
.disk_read = VasEBoot_virtdisk_read,
|
|
.disk_write = VasEBoot_virtdisk_write,
|
|
.next = 0
|
|
};
|
|
|
|
static int
|
|
count (const char *dir __attribute__ ((unused)), void *data)
|
|
{
|
|
VasEBoot_size_t *ctr = data;
|
|
(*ctr)++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
fill (const char *dir, void *data)
|
|
{
|
|
VasEBoot_size_t *ctr = data;
|
|
domid_t dom;
|
|
/* "dir" is just a number, at most 19 characters. */
|
|
char fdir[200];
|
|
char num[20];
|
|
VasEBoot_err_t err;
|
|
void *buf;
|
|
struct evtchn_alloc_unbound alloc_unbound;
|
|
struct virtdisk **prev = &compat_head, *vd = compat_head;
|
|
|
|
/* Shouldn't happen unles some hotplug happened. */
|
|
if (vdiskcnt >= *ctr)
|
|
return 1;
|
|
virtdisks[vdiskcnt].handle = VasEBoot_strtoul (dir, 0, 10);
|
|
if (VasEBoot_errno)
|
|
{
|
|
VasEBoot_errno = 0;
|
|
return 0;
|
|
}
|
|
virtdisks[vdiskcnt].fullname = 0;
|
|
virtdisks[vdiskcnt].backend_dir = 0;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s/backend", dir);
|
|
virtdisks[vdiskcnt].backend_dir = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
if (!virtdisks[vdiskcnt].backend_dir)
|
|
goto out_fail_1;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/dev",
|
|
virtdisks[vdiskcnt].backend_dir);
|
|
buf = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
if (!buf)
|
|
{
|
|
VasEBoot_errno = 0;
|
|
virtdisks[vdiskcnt].fullname = VasEBoot_xasprintf ("xenid/%s", dir);
|
|
}
|
|
else
|
|
{
|
|
virtdisks[vdiskcnt].fullname = VasEBoot_xasprintf ("xen/%s", (char *) buf);
|
|
VasEBoot_free (buf);
|
|
}
|
|
if (!virtdisks[vdiskcnt].fullname)
|
|
goto out_fail_1;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s/backend-id", dir);
|
|
buf = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
if (!buf)
|
|
goto out_fail_1;
|
|
|
|
dom = VasEBoot_strtoul (buf, 0, 10);
|
|
VasEBoot_free (buf);
|
|
if (VasEBoot_errno)
|
|
goto out_fail_1;
|
|
|
|
virtdisks[vdiskcnt].shared_page =
|
|
VasEBoot_xen_alloc_shared_page (dom, &virtdisks[vdiskcnt].grant);
|
|
if (!virtdisks[vdiskcnt].shared_page)
|
|
goto out_fail_1;
|
|
|
|
virtdisks[vdiskcnt].dma_page =
|
|
VasEBoot_xen_alloc_shared_page (dom, &virtdisks[vdiskcnt].dma_grant);
|
|
if (!virtdisks[vdiskcnt].dma_page)
|
|
goto out_fail_2;
|
|
|
|
alloc_unbound.dom = DOMID_SELF;
|
|
alloc_unbound.remote_dom = dom;
|
|
|
|
VasEBoot_xen_event_channel_op (EVTCHNOP_alloc_unbound, &alloc_unbound);
|
|
virtdisks[vdiskcnt].evtchn = alloc_unbound.port;
|
|
|
|
SHARED_RING_INIT (virtdisks[vdiskcnt].shared_page);
|
|
FRONT_RING_INIT (&virtdisks[vdiskcnt].ring, virtdisks[vdiskcnt].shared_page,
|
|
VAS_EBOOT_XEN_PAGE_SIZE);
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s/ring-ref", dir);
|
|
VasEBoot_snprintf (num, sizeof (num), "%u", virtdisks[vdiskcnt].grant);
|
|
err = VasEBoot_xenstore_write_file (fdir, num, VasEBoot_strlen (num));
|
|
if (err)
|
|
goto out_fail_3;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s/event-channel", dir);
|
|
VasEBoot_snprintf (num, sizeof (num), "%u", virtdisks[vdiskcnt].evtchn);
|
|
err = VasEBoot_xenstore_write_file (fdir, num, VasEBoot_strlen (num));
|
|
if (err)
|
|
goto out_fail_3;
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s/protocol", dir);
|
|
err = VasEBoot_xenstore_write_file (fdir, XEN_IO_PROTO_ABI_NATIVE,
|
|
VasEBoot_strlen (XEN_IO_PROTO_ABI_NATIVE));
|
|
if (err)
|
|
goto out_fail_3;
|
|
|
|
struct gnttab_dump_table dt;
|
|
dt.dom = DOMID_SELF;
|
|
VasEBoot_xen_grant_table_op (GNTTABOP_dump_table, (void *) &dt, 1);
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s/state", dir);
|
|
err = VasEBoot_xenstore_write_file (fdir, "3", 1);
|
|
if (err)
|
|
goto out_fail_3;
|
|
|
|
while (1)
|
|
{
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/state",
|
|
virtdisks[vdiskcnt].backend_dir);
|
|
buf = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
if (!buf)
|
|
goto out_fail_3;
|
|
if (VasEBoot_strcmp (buf, "2") != 0)
|
|
break;
|
|
VasEBoot_free (buf);
|
|
VasEBoot_xen_sched_op (SCHEDOP_yield, 0);
|
|
}
|
|
VasEBoot_dprintf ("xen", "state=%s\n", (char *) buf);
|
|
VasEBoot_free (buf);
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "device/vbd/%s", dir);
|
|
|
|
virtdisks[vdiskcnt].frontend_dir = VasEBoot_strdup (fdir);
|
|
|
|
/* For compatibility with pv-VasEBoot maintain linked list sorted by handle
|
|
value in increasing order. This allows mapping of (hdX) disk names
|
|
from legacy menu.lst */
|
|
while (vd)
|
|
{
|
|
if (vd->handle > virtdisks[vdiskcnt].handle)
|
|
break;
|
|
prev = &vd->compat_next;
|
|
vd = vd->compat_next;
|
|
}
|
|
virtdisks[vdiskcnt].compat_next = vd;
|
|
*prev = &virtdisks[vdiskcnt];
|
|
|
|
vdiskcnt++;
|
|
return 0;
|
|
|
|
out_fail_3:
|
|
VasEBoot_xen_free_shared_page (virtdisks[vdiskcnt].dma_page);
|
|
out_fail_2:
|
|
VasEBoot_xen_free_shared_page (virtdisks[vdiskcnt].shared_page);
|
|
out_fail_1:
|
|
VasEBoot_free (virtdisks[vdiskcnt].backend_dir);
|
|
VasEBoot_free (virtdisks[vdiskcnt].fullname);
|
|
|
|
VasEBoot_errno = 0;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
VasEBoot_xendisk_init (void)
|
|
{
|
|
VasEBoot_size_t ctr = 0;
|
|
if (VasEBoot_xenstore_dir ("device/vbd", count, &ctr))
|
|
VasEBoot_errno = 0;
|
|
|
|
if (!ctr)
|
|
return;
|
|
|
|
virtdisks = VasEBoot_calloc (ctr, sizeof (virtdisks[0]));
|
|
if (!virtdisks)
|
|
return;
|
|
if (VasEBoot_xenstore_dir ("device/vbd", fill, &ctr))
|
|
VasEBoot_errno = 0;
|
|
|
|
VasEBoot_disk_dev_register (&VasEBoot_virtdisk_dev);
|
|
}
|
|
|
|
void
|
|
VasEBoot_xendisk_fini (void)
|
|
{
|
|
char fdir[200];
|
|
unsigned i;
|
|
|
|
for (i = 0; i < vdiskcnt; i++)
|
|
{
|
|
char *buf;
|
|
struct evtchn_close close_op = {.port = virtdisks[i].evtchn };
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/state",
|
|
virtdisks[i].frontend_dir);
|
|
VasEBoot_xenstore_write_file (fdir, "6", 1);
|
|
|
|
while (1)
|
|
{
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/state",
|
|
virtdisks[i].backend_dir);
|
|
buf = VasEBoot_xenstore_get_file (fdir, NULL);
|
|
VasEBoot_dprintf ("xen", "state=%s\n", (char *) buf);
|
|
|
|
if (!buf || VasEBoot_strcmp (buf, "6") == 0)
|
|
break;
|
|
VasEBoot_free (buf);
|
|
VasEBoot_xen_sched_op (SCHEDOP_yield, 0);
|
|
}
|
|
VasEBoot_free (buf);
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/ring-ref",
|
|
virtdisks[i].frontend_dir);
|
|
VasEBoot_xenstore_write_file (fdir, NULL, 0);
|
|
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/event-channel",
|
|
virtdisks[i].frontend_dir);
|
|
VasEBoot_xenstore_write_file (fdir, NULL, 0);
|
|
|
|
VasEBoot_xen_free_shared_page (virtdisks[i].dma_page);
|
|
VasEBoot_xen_free_shared_page (virtdisks[i].shared_page);
|
|
|
|
VasEBoot_xen_event_channel_op (EVTCHNOP_close, &close_op);
|
|
|
|
/* Prepare for handoff. */
|
|
VasEBoot_snprintf (fdir, sizeof (fdir), "%s/state",
|
|
virtdisks[i].frontend_dir);
|
|
VasEBoot_xenstore_write_file (fdir, "1", 1);
|
|
}
|
|
}
|