| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2011 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/xen.h> |
| #include <grub/term.h> |
| #include <grub/misc.h> |
| #include <grub/env.h> |
| #include <grub/mm.h> |
| #include <grub/kernel.h> |
| #include <grub/offsets.h> |
| #include <grub/memory.h> |
| #include <grub/i386/tsc.h> |
| #include <grub/term.h> |
| #include <grub/loader.h> |
| |
| grub_addr_t grub_modbase; |
| struct start_info *grub_xen_start_page_addr; |
| volatile struct xencons_interface *grub_xen_xcons; |
| volatile struct shared_info *grub_xen_shared_info; |
| volatile struct xenstore_domain_interface *grub_xen_xenstore; |
| volatile grant_entry_v1_t *grub_xen_grant_table; |
| static const grub_size_t total_grants = |
| GRUB_XEN_PAGE_SIZE / sizeof (grub_xen_grant_table[0]); |
| grub_size_t grub_xen_n_allocated_shared_pages; |
| |
| static grub_xen_mfn_t |
| grub_xen_ptr2mfn (void *ptr) |
| { |
| grub_xen_mfn_t *mfn_list = |
| (grub_xen_mfn_t *) grub_xen_start_page_addr->mfn_list; |
| return mfn_list[(grub_addr_t) ptr >> GRUB_XEN_LOG_PAGE_SIZE]; |
| } |
| |
| void * |
| grub_xen_alloc_shared_page (domid_t dom, grub_xen_grant_t * grnum) |
| { |
| void *ret; |
| grub_xen_mfn_t mfn; |
| volatile grant_entry_v1_t *entry; |
| |
| /* Avoid 0. */ |
| for (entry = grub_xen_grant_table; |
| entry < grub_xen_grant_table + total_grants; entry++) |
| if (!entry->flags) |
| break; |
| |
| if (entry == grub_xen_grant_table + total_grants) |
| { |
| grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of grant entries"); |
| return NULL; |
| } |
| ret = grub_memalign (GRUB_XEN_PAGE_SIZE, GRUB_XEN_PAGE_SIZE); |
| if (!ret) |
| return NULL; |
| mfn = grub_xen_ptr2mfn (ret); |
| entry->frame = mfn; |
| entry->domid = dom; |
| mb (); |
| entry->flags = GTF_permit_access; |
| mb (); |
| *grnum = entry - grub_xen_grant_table; |
| grub_xen_n_allocated_shared_pages++; |
| return ret; |
| } |
| |
| void |
| grub_xen_free_shared_page (void *ptr) |
| { |
| grub_xen_mfn_t mfn; |
| volatile grant_entry_v1_t *entry; |
| |
| mfn = grub_xen_ptr2mfn (ptr); |
| for (entry = grub_xen_grant_table + 1; |
| entry < grub_xen_grant_table + total_grants; entry++) |
| if (entry->flags && entry->frame == mfn) |
| { |
| mb (); |
| entry->flags = 0; |
| mb (); |
| entry->frame = 0; |
| mb (); |
| } |
| grub_xen_n_allocated_shared_pages--; |
| } |
| |
| void |
| grub_machine_get_bootlocation (char **device __attribute__ ((unused)), |
| char **path __attribute__ ((unused))) |
| { |
| } |
| |
| static grub_uint8_t window[GRUB_XEN_PAGE_SIZE] |
| __attribute__ ((aligned (GRUB_XEN_PAGE_SIZE))); |
| |
| #ifdef __x86_64__ |
| #define NUMBER_OF_LEVELS 4 |
| #else |
| #define NUMBER_OF_LEVELS 3 |
| #endif |
| |
| #define LOG_POINTERS_PER_PAGE 9 |
| #define POINTERS_PER_PAGE (1 << LOG_POINTERS_PER_PAGE) |
| |
| void |
| grub_xen_store_send (const void *buf_, grub_size_t len) |
| { |
| const grub_uint8_t *buf = buf_; |
| struct evtchn_send send; |
| int event_sent = 0; |
| while (len) |
| { |
| grub_size_t avail, inbuf; |
| grub_size_t prod, cons; |
| mb (); |
| prod = grub_xen_xenstore->req_prod; |
| cons = grub_xen_xenstore->req_cons; |
| if (prod >= cons + sizeof (grub_xen_xenstore->req)) |
| { |
| if (!event_sent) |
| { |
| send.port = grub_xen_start_page_addr->store_evtchn; |
| grub_xen_event_channel_op (EVTCHNOP_send, &send); |
| event_sent = 1; |
| } |
| grub_xen_sched_op (SCHEDOP_yield, 0); |
| continue; |
| } |
| event_sent = 0; |
| avail = cons + sizeof (grub_xen_xenstore->req) - prod; |
| inbuf = (~prod & (sizeof (grub_xen_xenstore->req) - 1)) + 1; |
| if (avail > inbuf) |
| avail = inbuf; |
| if (avail > len) |
| avail = len; |
| grub_memcpy ((void *) &grub_xen_xenstore->req[prod & (sizeof (grub_xen_xenstore->req) - 1)], |
| buf, avail); |
| buf += avail; |
| len -= avail; |
| mb (); |
| grub_xen_xenstore->req_prod += avail; |
| mb (); |
| if (!event_sent) |
| { |
| send.port = grub_xen_start_page_addr->store_evtchn; |
| grub_xen_event_channel_op (EVTCHNOP_send, &send); |
| event_sent = 1; |
| } |
| grub_xen_sched_op (SCHEDOP_yield, 0); |
| } |
| } |
| |
| void |
| grub_xen_store_recv (void *buf_, grub_size_t len) |
| { |
| grub_uint8_t *buf = buf_; |
| struct evtchn_send send; |
| int event_sent = 0; |
| while (len) |
| { |
| grub_size_t avail, inbuf; |
| grub_size_t prod, cons; |
| mb (); |
| prod = grub_xen_xenstore->rsp_prod; |
| cons = grub_xen_xenstore->rsp_cons; |
| if (prod <= cons) |
| { |
| if (!event_sent) |
| { |
| send.port = grub_xen_start_page_addr->store_evtchn; |
| grub_xen_event_channel_op (EVTCHNOP_send, &send); |
| event_sent = 1; |
| } |
| grub_xen_sched_op (SCHEDOP_yield, 0); |
| continue; |
| } |
| event_sent = 0; |
| avail = prod - cons; |
| inbuf = (~cons & (sizeof (grub_xen_xenstore->req) - 1)) + 1; |
| if (avail > inbuf) |
| avail = inbuf; |
| if (avail > len) |
| avail = len; |
| grub_memcpy (buf, |
| (void *) &grub_xen_xenstore->rsp[cons & (sizeof (grub_xen_xenstore->rsp) - 1)], |
| avail); |
| buf += avail; |
| len -= avail; |
| mb (); |
| grub_xen_xenstore->rsp_cons += avail; |
| mb (); |
| if (!event_sent) |
| { |
| send.port = grub_xen_start_page_addr->store_evtchn; |
| grub_xen_event_channel_op(EVTCHNOP_send, &send); |
| event_sent = 1; |
| } |
| grub_xen_sched_op(SCHEDOP_yield, 0); |
| } |
| } |
| |
| void * |
| grub_xenstore_get_file (const char *dir, grub_size_t *len) |
| { |
| struct xsd_sockmsg msg; |
| char *buf; |
| grub_size_t dirlen = grub_strlen (dir) + 1; |
| |
| if (len) |
| *len = 0; |
| |
| grub_memset (&msg, 0, sizeof (msg)); |
| msg.type = XS_READ; |
| msg.len = dirlen; |
| grub_xen_store_send (&msg, sizeof (msg)); |
| grub_xen_store_send (dir, dirlen); |
| grub_xen_store_recv (&msg, sizeof (msg)); |
| buf = grub_malloc (msg.len + 1); |
| if (!buf) |
| return NULL; |
| grub_dprintf ("xen", "msg type = %d, len = %d\n", msg.type, msg.len); |
| grub_xen_store_recv (buf, msg.len); |
| buf[msg.len] = '\0'; |
| if (msg.type == XS_ERROR) |
| { |
| grub_error (GRUB_ERR_IO, "couldn't read xenstorage `%s': %s", dir, buf); |
| grub_free (buf); |
| return NULL; |
| } |
| if (len) |
| *len = msg.len; |
| return buf; |
| } |
| |
| grub_err_t |
| grub_xenstore_write_file (const char *dir, const void *buf, grub_size_t len) |
| { |
| struct xsd_sockmsg msg; |
| grub_size_t dirlen = grub_strlen (dir) + 1; |
| char *resp; |
| |
| grub_memset (&msg, 0, sizeof (msg)); |
| msg.type = XS_WRITE; |
| msg.len = dirlen + len; |
| grub_xen_store_send (&msg, sizeof (msg)); |
| grub_xen_store_send (dir, dirlen); |
| grub_xen_store_send (buf, len); |
| grub_xen_store_recv (&msg, sizeof (msg)); |
| resp = grub_malloc (msg.len + 1); |
| if (!resp) |
| return grub_errno; |
| grub_dprintf ("xen", "msg type = %d, len = %d\n", msg.type, msg.len); |
| grub_xen_store_recv (resp, msg.len); |
| resp[msg.len] = '\0'; |
| if (msg.type == XS_ERROR) |
| { |
| grub_dprintf ("xen", "error = %s\n", resp); |
| grub_error (GRUB_ERR_IO, "couldn't read xenstorage `%s': %s", |
| dir, resp); |
| grub_free (resp); |
| return grub_errno; |
| } |
| grub_free (resp); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* FIXME: error handling. */ |
| grub_err_t |
| grub_xenstore_dir (const char *dir, |
| int (*hook) (const char *dir, void *hook_data), |
| void *hook_data) |
| { |
| struct xsd_sockmsg msg; |
| char *buf; |
| char *ptr; |
| grub_size_t dirlen = grub_strlen (dir) + 1; |
| |
| grub_memset (&msg, 0, sizeof (msg)); |
| msg.type = XS_DIRECTORY; |
| msg.len = dirlen; |
| grub_xen_store_send (&msg, sizeof (msg)); |
| grub_xen_store_send (dir, dirlen); |
| grub_xen_store_recv (&msg, sizeof (msg)); |
| buf = grub_malloc (msg.len + 1); |
| if (!buf) |
| return grub_errno; |
| grub_dprintf ("xen", "msg type = %d, len = %d\n", msg.type, msg.len); |
| grub_xen_store_recv (buf, msg.len); |
| buf[msg.len] = '\0'; |
| if (msg.type == XS_ERROR) |
| { |
| grub_err_t err; |
| err = grub_error (GRUB_ERR_IO, "couldn't read xenstorage `%s': %s", |
| dir, buf); |
| grub_free (buf); |
| return err; |
| } |
| for (ptr = buf; ptr < buf + msg.len; ptr += grub_strlen (ptr) + 1) |
| if (hook (ptr, hook_data)) |
| break; |
| grub_free (buf); |
| return grub_errno; |
| } |
| |
| unsigned long gntframe = 0; |
| |
| #define MAX_N_UNUSABLE_PAGES 4 |
| |
| static int |
| grub_xen_is_page_usable (grub_xen_mfn_t mfn) |
| { |
| if (mfn == grub_xen_start_page_addr->console.domU.mfn) |
| return 0; |
| if (mfn == grub_xen_start_page_addr->shared_info) |
| return 0; |
| if (mfn == grub_xen_start_page_addr->store_mfn) |
| return 0; |
| if (mfn == gntframe) |
| return 0; |
| return 1; |
| } |
| |
| static grub_uint64_t |
| page2offset (grub_uint64_t page) |
| { |
| return page << 12; |
| } |
| |
| #if defined (__x86_64__) && defined (__code_model_large__) |
| #define MAX_TOTAL_PAGES (1LL << (64 - 12)) |
| #elif defined (__x86_64__) |
| #define MAX_TOTAL_PAGES (1LL << (31 - 12)) |
| #else |
| #define MAX_TOTAL_PAGES (1LL << (32 - 12)) |
| #endif |
| |
| static void |
| map_all_pages (void) |
| { |
| grub_uint64_t total_pages = grub_xen_start_page_addr->nr_pages; |
| grub_uint64_t i, j; |
| grub_xen_mfn_t *mfn_list = |
| (grub_xen_mfn_t *) grub_xen_start_page_addr->mfn_list; |
| grub_uint64_t *pg = (grub_uint64_t *) window; |
| grub_uint64_t oldpgstart, oldpgend; |
| struct gnttab_setup_table gnttab_setup; |
| struct gnttab_set_version gnttab_setver; |
| grub_size_t n_unusable_pages = 0; |
| struct mmu_update m2p_updates[2 * MAX_N_UNUSABLE_PAGES]; |
| |
| if (total_pages > MAX_TOTAL_PAGES - 4) |
| total_pages = MAX_TOTAL_PAGES - 4; |
| |
| grub_memset (&gnttab_setver, 0, sizeof (gnttab_setver)); |
| |
| gnttab_setver.version = 1; |
| grub_xen_grant_table_op (GNTTABOP_set_version, &gnttab_setver, 1); |
| |
| grub_memset (&gnttab_setup, 0, sizeof (gnttab_setup)); |
| gnttab_setup.dom = DOMID_SELF; |
| gnttab_setup.nr_frames = 1; |
| gnttab_setup.frame_list.p = &gntframe; |
| |
| grub_xen_grant_table_op (GNTTABOP_setup_table, &gnttab_setup, 1); |
| |
| for (j = 0; j < total_pages - n_unusable_pages; j++) |
| while (!grub_xen_is_page_usable (mfn_list[j])) |
| { |
| grub_xen_mfn_t t; |
| if (n_unusable_pages >= MAX_N_UNUSABLE_PAGES) |
| { |
| struct sched_shutdown arg; |
| arg.reason = SHUTDOWN_crash; |
| grub_xen_sched_op (SCHEDOP_shutdown, &arg); |
| while (1); |
| } |
| t = mfn_list[j]; |
| mfn_list[j] = mfn_list[total_pages - n_unusable_pages - 1]; |
| mfn_list[total_pages - n_unusable_pages - 1] = t; |
| |
| m2p_updates[2 * n_unusable_pages].ptr |
| = page2offset (mfn_list[j]) | MMU_MACHPHYS_UPDATE; |
| m2p_updates[2 * n_unusable_pages].val = j; |
| m2p_updates[2 * n_unusable_pages + 1].ptr |
| = page2offset (mfn_list[total_pages - n_unusable_pages - 1]) |
| | MMU_MACHPHYS_UPDATE; |
| m2p_updates[2 * n_unusable_pages + 1].val = total_pages |
| - n_unusable_pages - 1; |
| |
| n_unusable_pages++; |
| } |
| |
| grub_xen_mmu_update (m2p_updates, 2 * n_unusable_pages, NULL, DOMID_SELF); |
| |
| total_pages += 4; |
| |
| grub_uint64_t lx[NUMBER_OF_LEVELS], nlx; |
| grub_uint64_t paging_start = total_pages - 4 - n_unusable_pages, curpage; |
| |
| for (nlx = total_pages, i = 0; i < (unsigned) NUMBER_OF_LEVELS; i++) |
| { |
| nlx = (nlx + POINTERS_PER_PAGE - 1) >> LOG_POINTERS_PER_PAGE; |
| /* PAE wants all 4 root directories present. */ |
| #ifdef __i386__ |
| if (i == 1) |
| nlx = 4; |
| #endif |
| lx[i] = nlx; |
| paging_start -= nlx; |
| } |
| |
| oldpgstart = grub_xen_start_page_addr->pt_base >> 12; |
| oldpgend = oldpgstart + grub_xen_start_page_addr->nr_pt_frames; |
| |
| curpage = paging_start; |
| |
| int l; |
| |
| for (l = NUMBER_OF_LEVELS - 1; l >= 1; l--) |
| { |
| for (i = 0; i < lx[l]; i++) |
| { |
| grub_xen_update_va_mapping (&window, |
| page2offset (mfn_list[curpage + i]) | 7, |
| UVMF_INVLPG); |
| grub_memset (&window, 0, sizeof (window)); |
| |
| for (j = i * POINTERS_PER_PAGE; |
| j < (i + 1) * POINTERS_PER_PAGE && j < lx[l - 1]; j++) |
| pg[j - i * POINTERS_PER_PAGE] = |
| page2offset (mfn_list[curpage + lx[l] + j]) |
| #ifdef __x86_64__ |
| | 4 |
| #endif |
| | 3; |
| } |
| curpage += lx[l]; |
| } |
| |
| for (i = 0; i < lx[0]; i++) |
| { |
| grub_xen_update_va_mapping (&window, |
| page2offset (mfn_list[curpage + i]) | 7, |
| UVMF_INVLPG); |
| grub_memset (&window, 0, sizeof (window)); |
| |
| for (j = i * POINTERS_PER_PAGE; |
| j < (i + 1) * POINTERS_PER_PAGE && j < total_pages; j++) |
| if (j < paging_start && !(j >= oldpgstart && j < oldpgend)) |
| pg[j - i * POINTERS_PER_PAGE] = page2offset (mfn_list[j]) | 0x7; |
| else if (j < grub_xen_start_page_addr->nr_pages) |
| pg[j - i * POINTERS_PER_PAGE] = page2offset (mfn_list[j]) | 5; |
| else if (j == grub_xen_start_page_addr->nr_pages) |
| { |
| pg[j - i * POINTERS_PER_PAGE] = |
| page2offset (grub_xen_start_page_addr->console.domU.mfn) | 7; |
| grub_xen_xcons = (void *) (grub_addr_t) page2offset (j); |
| } |
| else if (j == grub_xen_start_page_addr->nr_pages + 1) |
| { |
| pg[j - i * POINTERS_PER_PAGE] = |
| grub_xen_start_page_addr->shared_info | 7; |
| grub_xen_shared_info = (void *) (grub_addr_t) page2offset (j); |
| } |
| else if (j == grub_xen_start_page_addr->nr_pages + 2) |
| { |
| pg[j - i * POINTERS_PER_PAGE] = |
| page2offset (grub_xen_start_page_addr->store_mfn) | 7; |
| grub_xen_xenstore = (void *) (grub_addr_t) page2offset (j); |
| } |
| else if (j == grub_xen_start_page_addr->nr_pages + 3) |
| { |
| pg[j - i * POINTERS_PER_PAGE] = page2offset (gntframe) | 7; |
| grub_xen_grant_table = (void *) (grub_addr_t) page2offset (j); |
| } |
| } |
| |
| grub_xen_update_va_mapping (&window, 0, UVMF_INVLPG); |
| |
| mmuext_op_t op[3]; |
| |
| op[0].cmd = MMUEXT_PIN_L1_TABLE + (NUMBER_OF_LEVELS - 1); |
| op[0].arg1.mfn = mfn_list[paging_start]; |
| op[1].cmd = MMUEXT_NEW_BASEPTR; |
| op[1].arg1.mfn = mfn_list[paging_start]; |
| op[2].cmd = MMUEXT_UNPIN_TABLE; |
| op[2].arg1.mfn = mfn_list[oldpgstart]; |
| |
| grub_xen_mmuext_op (op, 3, NULL, DOMID_SELF); |
| |
| for (i = oldpgstart; i < oldpgend; i++) |
| grub_xen_update_va_mapping ((void *) (grub_addr_t) page2offset (i), |
| page2offset (mfn_list[i]) | 7, UVMF_INVLPG); |
| void *new_start_page, *new_mfn_list; |
| new_start_page = (void *) (grub_addr_t) page2offset (paging_start - 1); |
| grub_memcpy (new_start_page, grub_xen_start_page_addr, 4096); |
| grub_xen_start_page_addr = new_start_page; |
| new_mfn_list = (void *) (grub_addr_t) |
| page2offset (paging_start - 1 |
| - ((grub_xen_start_page_addr->nr_pages |
| * sizeof (grub_uint64_t) + 4095) / 4096)); |
| grub_memcpy (new_mfn_list, mfn_list, grub_xen_start_page_addr->nr_pages |
| * sizeof (grub_uint64_t)); |
| grub_xen_start_page_addr->pt_base = page2offset (paging_start); |
| grub_xen_start_page_addr->mfn_list = (grub_addr_t) new_mfn_list; |
| |
| grub_addr_t heap_start = grub_modules_get_end (); |
| grub_addr_t heap_end = (grub_addr_t) new_mfn_list; |
| |
| grub_mm_init_region ((void *) heap_start, heap_end - heap_start); |
| } |
| |
| extern char _end[]; |
| |
| void |
| grub_machine_init (void) |
| { |
| #ifdef __i386__ |
| grub_xen_vm_assist (VMASST_CMD_enable, VMASST_TYPE_pae_extended_cr3); |
| #endif |
| |
| grub_modbase = ALIGN_UP ((grub_addr_t) _end |
| + GRUB_KERNEL_MACHINE_MOD_GAP, |
| GRUB_KERNEL_MACHINE_MOD_ALIGN); |
| |
| map_all_pages (); |
| |
| grub_console_init (); |
| |
| grub_tsc_init (); |
| |
| grub_xendisk_init (); |
| |
| grub_boot_init (); |
| } |
| |
| void |
| grub_exit (void) |
| { |
| struct sched_shutdown arg; |
| |
| arg.reason = SHUTDOWN_poweroff; |
| grub_xen_sched_op (SCHEDOP_shutdown, &arg); |
| while (1); |
| } |
| |
| void |
| grub_machine_fini (int flags __attribute__ ((unused))) |
| { |
| grub_xendisk_fini (); |
| grub_boot_fini (); |
| } |
| |
| grub_err_t |
| grub_machine_mmap_iterate (grub_memory_hook_t hook, void *hook_data) |
| { |
| grub_uint64_t total_pages = grub_xen_start_page_addr->nr_pages; |
| grub_uint64_t usable_pages = grub_xen_start_page_addr->pt_base >> 12; |
| if (hook (0, page2offset (usable_pages), GRUB_MEMORY_AVAILABLE, hook_data)) |
| return GRUB_ERR_NONE; |
| |
| hook (page2offset (usable_pages), page2offset (total_pages - usable_pages), |
| GRUB_MEMORY_RESERVED, hook_data); |
| |
| return GRUB_ERR_NONE; |
| } |