/* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include 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_v2_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_v2_t *entry; /* Avoid 0. */ for (entry = grub_xen_grant_table; entry < grub_xen_grant_table + total_grants; entry++) if (!entry->hdr.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->full_page.pad0 = 0; entry->full_page.frame = mfn; entry->full_page.hdr.domid = dom; mb (); entry->full_page.hdr.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_v2_t *entry; mfn = grub_xen_ptr2mfn (ptr); for (entry = grub_xen_grant_table + 1; entry < grub_xen_grant_table + total_grants; entry++) if (entry->hdr.flags && entry->full_page.frame == mfn) { mb (); entry->hdr.flags = 0; mb (); entry->full_page.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 = 2; 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; }