| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2013 Free Software Foundation, Inc. |
| * |
| * GRUB 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. |
| * |
| * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/disk.h> |
| #include <grub/dl.h> |
| #include <grub/mm.h> |
| #include <grub/types.h> |
| #include <grub/misc.h> |
| #include <grub/err.h> |
| #include <grub/term.h> |
| #include <grub/i18n.h> |
| #include <grub/xen.h> |
| #include <grub/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; |
| grub_xen_grant_t grant; |
| grub_xen_evtchn_t evtchn; |
| void *dma_page; |
| grub_xen_grant_t dma_grant; |
| struct virtdisk *compat_next; |
| }; |
| |
| #define xen_wmb() mb() |
| #define xen_mb() mb() |
| |
| static struct virtdisk *virtdisks; |
| static grub_size_t vdiskcnt; |
| struct virtdisk *compat_head; |
| |
| static int |
| grub_virtdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, |
| grub_disk_pull_t pull) |
| { |
| grub_size_t i; |
| |
| if (pull != GRUB_DISK_PULL_NONE) |
| return 0; |
| |
| for (i = 0; i < vdiskcnt; i++) |
| if (hook (virtdisks[i].fullname, hook_data)) |
| return 1; |
| return 0; |
| } |
| |
| static grub_err_t |
| grub_virtdisk_open (const char *name, grub_disk_t disk) |
| { |
| int i; |
| grub_uint32_t secsize; |
| char fdir[200]; |
| char *buf; |
| int num = -1; |
| struct virtdisk *vd; |
| |
| /* For compatibility with pv-grub legacy menu.lst accept hdX as disk name */ |
| if (name[0] == 'h' && name[1] == 'd' && name[2]) |
| { |
| num = grub_strtoul (name + 2, 0, 10); |
| if (grub_errno) |
| { |
| grub_errno = 0; |
| num = -1; |
| } |
| } |
| for (i = 0, vd = compat_head; vd; vd = vd->compat_next, i++) |
| if (i == num || grub_strcmp (name, vd->fullname) == 0) |
| break; |
| if (!vd) |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a virtdisk"); |
| disk->data = vd; |
| disk->id = vd - virtdisks; |
| |
| grub_snprintf (fdir, sizeof (fdir), "%s/sectors", vd->backend_dir); |
| buf = grub_xenstore_get_file (fdir, NULL); |
| if (!buf) |
| return grub_errno; |
| disk->total_sectors = grub_strtoull (buf, 0, 10); |
| if (grub_errno) |
| return grub_errno; |
| |
| grub_snprintf (fdir, sizeof (fdir), "%s/sector-size", vd->backend_dir); |
| buf = grub_xenstore_get_file (fdir, NULL); |
| if (!buf) |
| return grub_errno; |
| secsize = grub_strtoull (buf, 0, 10); |
| if (grub_errno) |
| return grub_errno; |
| |
| if ((secsize & (secsize - 1)) || !secsize || secsize < 512 |
| || secsize > GRUB_XEN_PAGE_SIZE) |
| return grub_error (GRUB_ERR_IO, "unsupported sector size %d", secsize); |
| |
| for (disk->log_sector_size = 0; |
| (1U << disk->log_sector_size) < secsize; disk->log_sector_size++); |
| |
| disk->total_sectors >>= disk->log_sector_size - 9; |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static void |
| grub_virtdisk_close (grub_disk_t disk __attribute__ ((unused))) |
| { |
| } |
| |
| static grub_err_t |
| grub_virtdisk_read (grub_disk_t disk, grub_disk_addr_t sector, |
| grub_size_t size, char *buf) |
| { |
| struct virtdisk *data = disk->data; |
| |
| while (size) |
| { |
| grub_size_t cur; |
| struct blkif_request *req; |
| struct blkif_response *resp; |
| int sta = 0; |
| struct evtchn_send send; |
| cur = size; |
| if (cur > (unsigned) (GRUB_XEN_PAGE_SIZE >> disk->log_sector_size)) |
| cur = GRUB_XEN_PAGE_SIZE >> disk->log_sector_size; |
| while (RING_FULL (&data->ring)) |
| grub_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; |
| grub_xen_event_channel_op (EVTCHNOP_send, &send); |
| |
| while (!RING_HAS_UNCONSUMED_RESPONSES (&data->ring)) |
| { |
| grub_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 grub_error (GRUB_ERR_IO, "read failed"); |
| grub_memcpy (buf, data->dma_page, cur << disk->log_sector_size); |
| size -= cur; |
| sector += cur; |
| buf += cur << disk->log_sector_size; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| grub_virtdisk_write (grub_disk_t disk, grub_disk_addr_t sector, |
| grub_size_t size, const char *buf) |
| { |
| struct virtdisk *data = disk->data; |
| |
| while (size) |
| { |
| grub_size_t cur; |
| struct blkif_request *req; |
| struct blkif_response *resp; |
| int sta = 0; |
| struct evtchn_send send; |
| cur = size; |
| if (cur > (unsigned) (GRUB_XEN_PAGE_SIZE >> disk->log_sector_size)) |
| cur = GRUB_XEN_PAGE_SIZE >> disk->log_sector_size; |
| |
| grub_memcpy (data->dma_page, buf, cur << disk->log_sector_size); |
| |
| while (RING_FULL (&data->ring)) |
| grub_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; |
| grub_xen_event_channel_op (EVTCHNOP_send, &send); |
| |
| while (!RING_HAS_UNCONSUMED_RESPONSES (&data->ring)) |
| { |
| grub_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 grub_error (GRUB_ERR_IO, "write failed"); |
| size -= cur; |
| sector += cur; |
| buf += cur << disk->log_sector_size; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static struct grub_disk_dev grub_virtdisk_dev = { |
| .name = "xen", |
| .id = GRUB_DISK_DEVICE_XEN, |
| .iterate = grub_virtdisk_iterate, |
| .open = grub_virtdisk_open, |
| .close = grub_virtdisk_close, |
| .read = grub_virtdisk_read, |
| .write = grub_virtdisk_write, |
| .next = 0 |
| }; |
| |
| static int |
| count (const char *dir __attribute__ ((unused)), void *data) |
| { |
| grub_size_t *ctr = data; |
| (*ctr)++; |
| |
| return 0; |
| } |
| |
| static int |
| fill (const char *dir, void *data) |
| { |
| grub_size_t *ctr = data; |
| domid_t dom; |
| /* "dir" is just a number, at most 19 characters. */ |
| char fdir[200]; |
| char num[20]; |
| grub_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 = grub_strtoul (dir, 0, 10); |
| if (grub_errno) |
| { |
| grub_errno = 0; |
| return 0; |
| } |
| virtdisks[vdiskcnt].fullname = 0; |
| virtdisks[vdiskcnt].backend_dir = 0; |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/backend", dir); |
| virtdisks[vdiskcnt].backend_dir = grub_xenstore_get_file (fdir, NULL); |
| if (!virtdisks[vdiskcnt].backend_dir) |
| goto out_fail_1; |
| |
| grub_snprintf (fdir, sizeof (fdir), "%s/dev", |
| virtdisks[vdiskcnt].backend_dir); |
| buf = grub_xenstore_get_file (fdir, NULL); |
| if (!buf) |
| { |
| grub_errno = 0; |
| virtdisks[vdiskcnt].fullname = grub_xasprintf ("xenid/%s", dir); |
| } |
| else |
| { |
| virtdisks[vdiskcnt].fullname = grub_xasprintf ("xen/%s", (char *) buf); |
| grub_free (buf); |
| } |
| if (!virtdisks[vdiskcnt].fullname) |
| goto out_fail_1; |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/backend-id", dir); |
| buf = grub_xenstore_get_file (fdir, NULL); |
| if (!buf) |
| goto out_fail_1; |
| |
| dom = grub_strtoul (buf, 0, 10); |
| grub_free (buf); |
| if (grub_errno) |
| goto out_fail_1; |
| |
| virtdisks[vdiskcnt].shared_page = |
| grub_xen_alloc_shared_page (dom, &virtdisks[vdiskcnt].grant); |
| if (!virtdisks[vdiskcnt].shared_page) |
| goto out_fail_1; |
| |
| virtdisks[vdiskcnt].dma_page = |
| grub_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; |
| |
| grub_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, |
| GRUB_XEN_PAGE_SIZE); |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/ring-ref", dir); |
| grub_snprintf (num, sizeof (num), "%u", virtdisks[vdiskcnt].grant); |
| err = grub_xenstore_write_file (fdir, num, grub_strlen (num)); |
| if (err) |
| goto out_fail_3; |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/event-channel", dir); |
| grub_snprintf (num, sizeof (num), "%u", virtdisks[vdiskcnt].evtchn); |
| err = grub_xenstore_write_file (fdir, num, grub_strlen (num)); |
| if (err) |
| goto out_fail_3; |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/protocol", dir); |
| err = grub_xenstore_write_file (fdir, XEN_IO_PROTO_ABI_NATIVE, |
| grub_strlen (XEN_IO_PROTO_ABI_NATIVE)); |
| if (err) |
| goto out_fail_3; |
| |
| struct gnttab_dump_table dt; |
| dt.dom = DOMID_SELF; |
| grub_xen_grant_table_op (GNTTABOP_dump_table, (void *) &dt, 1); |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/state", dir); |
| err = grub_xenstore_write_file (fdir, "3", 1); |
| if (err) |
| goto out_fail_3; |
| |
| while (1) |
| { |
| grub_snprintf (fdir, sizeof (fdir), "%s/state", |
| virtdisks[vdiskcnt].backend_dir); |
| buf = grub_xenstore_get_file (fdir, NULL); |
| if (!buf) |
| goto out_fail_3; |
| if (grub_strcmp (buf, "2") != 0) |
| break; |
| grub_free (buf); |
| grub_xen_sched_op (SCHEDOP_yield, 0); |
| } |
| grub_dprintf ("xen", "state=%s\n", (char *) buf); |
| grub_free (buf); |
| |
| grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s", dir); |
| |
| virtdisks[vdiskcnt].frontend_dir = grub_strdup (fdir); |
| |
| /* For compatibility with pv-grub 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: |
| grub_xen_free_shared_page (virtdisks[vdiskcnt].dma_page); |
| out_fail_2: |
| grub_xen_free_shared_page (virtdisks[vdiskcnt].shared_page); |
| out_fail_1: |
| grub_free (virtdisks[vdiskcnt].backend_dir); |
| grub_free (virtdisks[vdiskcnt].fullname); |
| |
| grub_errno = 0; |
| return 0; |
| } |
| |
| void |
| grub_xendisk_init (void) |
| { |
| grub_size_t ctr = 0; |
| if (grub_xenstore_dir ("device/vbd", count, &ctr)) |
| grub_errno = 0; |
| |
| if (!ctr) |
| return; |
| |
| virtdisks = grub_calloc (ctr, sizeof (virtdisks[0])); |
| if (!virtdisks) |
| return; |
| if (grub_xenstore_dir ("device/vbd", fill, &ctr)) |
| grub_errno = 0; |
| |
| grub_disk_dev_register (&grub_virtdisk_dev); |
| } |
| |
| void |
| grub_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 }; |
| |
| grub_snprintf (fdir, sizeof (fdir), "%s/state", |
| virtdisks[i].frontend_dir); |
| grub_xenstore_write_file (fdir, "6", 1); |
| |
| while (1) |
| { |
| grub_snprintf (fdir, sizeof (fdir), "%s/state", |
| virtdisks[i].backend_dir); |
| buf = grub_xenstore_get_file (fdir, NULL); |
| grub_dprintf ("xen", "state=%s\n", (char *) buf); |
| |
| if (!buf || grub_strcmp (buf, "6") == 0) |
| break; |
| grub_free (buf); |
| grub_xen_sched_op (SCHEDOP_yield, 0); |
| } |
| grub_free (buf); |
| |
| grub_snprintf (fdir, sizeof (fdir), "%s/ring-ref", |
| virtdisks[i].frontend_dir); |
| grub_xenstore_write_file (fdir, NULL, 0); |
| |
| grub_snprintf (fdir, sizeof (fdir), "%s/event-channel", |
| virtdisks[i].frontend_dir); |
| grub_xenstore_write_file (fdir, NULL, 0); |
| |
| grub_xen_free_shared_page (virtdisks[i].dma_page); |
| grub_xen_free_shared_page (virtdisks[i].shared_page); |
| |
| grub_xen_event_channel_op (EVTCHNOP_close, &close_op); |
| |
| /* Prepare for handoff. */ |
| grub_snprintf (fdir, sizeof (fdir), "%s/state", |
| virtdisks[i].frontend_dir); |
| grub_xenstore_write_file (fdir, "1", 1); |
| } |
| } |