* Images preloaded by the boot loader had to be modules to be of any use to the kernel. Extended the mechanism so that any images not accepted by the module code would later be tried to be added as drivers by the devfs. This is a little hacky ATM, since the devfs manages the drivers using a hash map keyed by the drivers inode ID, which those drivers obviously don't have. * The devfs emulates read_pages() using read(), if the device driver doesn't implement the former (all old-style drivers), thus making it possible to BFS, which uses the file cache which in turn requires read_pages(), on the device. write_pages() emulation is still missing. * Replaced the kernel_args::boot_disk structure by a KMessage, which can more flexibly be extended and deals more gracefully with arbitrarily-size data. The disk_identifier structure still exists, though. It is added as message field in cases where needed (non net boot). Moved the boot_drive_number field of the bios_ia32 platform specific args into the message. * Made the stage 1 PXE boot loader superfluous. Moved the relevant initialization code into the stage 2 loader, which can now be loaded directly via PXE. * The PXE boot loader does now download a boot tgz archive via TFTP. It does no longer use the RemoteDisk protocol (it could actually be removed from the boot loader). It also parses the DHCP options in the DHCPACK packet provided by PXE and extracts the root path to be mounted by the kernel. * Reorganized the boot volume search in the kernel (vfs_boot.cpp) and added support for network boot. In this case the net stack is initialized and the network interface the boot loader used is brought up and configured. Since NBD and RemoteDisk are our only options for net boot (and those aren't really configurable dynamically) ATM, the the boot device is found automatically by the disk device manager. Booting via PXE does work to some degree now. The most grievous problem is that loading certain drivers or kernel modules (or related activity) causes a reboot (likely a triple fault, though one wonders where our double fault handler is on vacation). Namely the keyboard and mouse input server add-ons need to be deactivated as well as the media server. A smaller problem is the net server, which apparently tries to (re-)configure the network interface we're using to boot, which obviously doesn't work out that well. So, if all this stuff is disabled Haiku does fully boot, when using the RemoteDisk protocol (not being able to use keyboard or mouse doesn't make this a particular fascinating experience, though ;-)). I had no luck with NBD -- it seemed to have protocol problems with the servers I tried. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21611 a95241bf-73f2-0310-859d-f6bbb57e9c96
534 lines
9.5 KiB
C++
534 lines
9.5 KiB
C++
/*
|
|
* Copyright 2003-2007, Axel Dörfler, [email protected]. All rights reserved.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include "menu.h"
|
|
#include "loader.h"
|
|
#include "RootFileSystem.h"
|
|
#include "load_driver_settings.h"
|
|
|
|
#include <OS.h>
|
|
|
|
#include <util/kernel_cpp.h>
|
|
#include <boot/menu.h>
|
|
#include <boot/stage2.h>
|
|
#include <boot/vfs.h>
|
|
#include <boot/platform.h>
|
|
#include <boot/stdio.h>
|
|
#include <safemode.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
MenuItem::MenuItem(const char *label, Menu *subMenu)
|
|
:
|
|
fLabel(strdup(label)),
|
|
fTarget(NULL),
|
|
fIsMarked(false),
|
|
fIsSelected(false),
|
|
fIsEnabled(true),
|
|
fType(MENU_ITEM_STANDARD),
|
|
fMenu(NULL),
|
|
fSubMenu(subMenu),
|
|
fData(NULL),
|
|
fHelpText(NULL)
|
|
{
|
|
if (subMenu != NULL)
|
|
subMenu->fSuperItem = this;
|
|
}
|
|
|
|
|
|
MenuItem::~MenuItem()
|
|
{
|
|
if (fSubMenu != NULL)
|
|
fSubMenu->fSuperItem = NULL;
|
|
|
|
free(const_cast<char *>(fLabel));
|
|
}
|
|
|
|
|
|
void
|
|
MenuItem::SetTarget(menu_item_hook target)
|
|
{
|
|
fTarget = target;
|
|
}
|
|
|
|
|
|
/** Marks or unmarks a menu item. A marked menu item usually gets a visual
|
|
* clue like a checkmark that distinguishes it from others.
|
|
* For menus of type CHOICE_MENU, there can only be one marked item - the
|
|
* chosen one.
|
|
*/
|
|
|
|
void
|
|
MenuItem::SetMarked(bool marked)
|
|
{
|
|
if (marked && fMenu != NULL && fMenu->Type() == CHOICE_MENU) {
|
|
// always set choice text of parent if we were marked
|
|
fMenu->SetChoiceText(Label());
|
|
}
|
|
|
|
if (fIsMarked == marked)
|
|
return;
|
|
|
|
if (marked && fMenu != NULL && fMenu->Type() == CHOICE_MENU) {
|
|
// unmark previous item
|
|
MenuItem *markedItem = fMenu->FindMarked();
|
|
if (markedItem != NULL)
|
|
markedItem->SetMarked(false);
|
|
}
|
|
|
|
fIsMarked = marked;
|
|
|
|
if (fMenu != NULL)
|
|
fMenu->Draw(this);
|
|
}
|
|
|
|
|
|
void
|
|
MenuItem::Select(bool selected)
|
|
{
|
|
if (fIsSelected == selected)
|
|
return;
|
|
|
|
if (selected && fMenu != NULL) {
|
|
// unselect previous item
|
|
MenuItem *selectedItem = fMenu->FindSelected();
|
|
if (selectedItem != NULL)
|
|
selectedItem->Select(false);
|
|
}
|
|
|
|
fIsSelected = selected;
|
|
|
|
if (fMenu != NULL)
|
|
fMenu->Draw(this);
|
|
}
|
|
|
|
|
|
void
|
|
MenuItem::SetType(menu_item_type type)
|
|
{
|
|
fType = type;
|
|
}
|
|
|
|
|
|
void
|
|
MenuItem::SetEnabled(bool enabled)
|
|
{
|
|
if (fIsEnabled == enabled)
|
|
return;
|
|
|
|
fIsEnabled = enabled;
|
|
|
|
if (fMenu != NULL)
|
|
fMenu->Draw(this);
|
|
}
|
|
|
|
|
|
void
|
|
MenuItem::SetData(const void *data)
|
|
{
|
|
fData = data;
|
|
}
|
|
|
|
|
|
/** This sets a help text that is shown when the item is
|
|
* selected.
|
|
* Note, unlike the label, the string is not copied, it's
|
|
* just referenced and has to stay valid as long as the
|
|
* item's menu is being used.
|
|
*/
|
|
|
|
void
|
|
MenuItem::SetHelpText(const char *text)
|
|
{
|
|
fHelpText = text;
|
|
}
|
|
|
|
|
|
void
|
|
MenuItem::SetMenu(Menu *menu)
|
|
{
|
|
fMenu = menu;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
Menu::Menu(menu_type type, const char *title)
|
|
:
|
|
fTitle(title),
|
|
fChoiceText(NULL),
|
|
fCount(0),
|
|
fIsHidden(true),
|
|
fType(type),
|
|
fSuperItem(NULL)
|
|
{
|
|
}
|
|
|
|
|
|
Menu::~Menu()
|
|
{
|
|
// take all remaining items with us
|
|
|
|
MenuItem *item;
|
|
while ((item = fItems.Head()) != NULL) {
|
|
fItems.Remove(item);
|
|
delete item;
|
|
}
|
|
}
|
|
|
|
|
|
MenuItem *
|
|
Menu::ItemAt(int32 index)
|
|
{
|
|
if (index < 0 || index >= fCount)
|
|
return NULL;
|
|
|
|
MenuItemIterator iterator = ItemIterator();
|
|
MenuItem *item;
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (index-- == 0)
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int32
|
|
Menu::IndexOf(MenuItem *searchedItem)
|
|
{
|
|
MenuItemIterator iterator = ItemIterator();
|
|
MenuItem *item;
|
|
int32 index = 0;
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (item == searchedItem)
|
|
return index;
|
|
|
|
index++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int32
|
|
Menu::CountItems() const
|
|
{
|
|
return fCount;
|
|
}
|
|
|
|
|
|
MenuItem *
|
|
Menu::FindItem(const char *label)
|
|
{
|
|
MenuItemIterator iterator = ItemIterator();
|
|
MenuItem *item;
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (item->Label() != NULL && !strcmp(item->Label(), label))
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MenuItem *
|
|
Menu::FindMarked()
|
|
{
|
|
MenuItemIterator iterator = ItemIterator();
|
|
MenuItem *item;
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (item->IsMarked())
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MenuItem *
|
|
Menu::FindSelected(int32 *_index)
|
|
{
|
|
MenuItemIterator iterator = ItemIterator();
|
|
MenuItem *item;
|
|
int32 index = 0;
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (item->IsSelected()) {
|
|
if (_index != NULL)
|
|
*_index = index;
|
|
return item;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
Menu::AddItem(MenuItem *item)
|
|
{
|
|
item->fMenu = this;
|
|
fItems.Add(item);
|
|
fCount++;
|
|
}
|
|
|
|
|
|
status_t
|
|
Menu::AddSeparatorItem()
|
|
{
|
|
MenuItem *item = new(nothrow) MenuItem();
|
|
if (item == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
item->SetType(MENU_ITEM_SEPARATOR);
|
|
|
|
AddItem(item);
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
MenuItem *
|
|
Menu::RemoveItemAt(int32 index)
|
|
{
|
|
if (index < 0 || index >= fCount)
|
|
return NULL;
|
|
|
|
MenuItemIterator iterator = ItemIterator();
|
|
MenuItem *item;
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (index-- == 0) {
|
|
RemoveItem(item);
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
Menu::RemoveItem(MenuItem *item)
|
|
{
|
|
item->fMenu = NULL;
|
|
fItems.Remove(item);
|
|
fCount--;
|
|
}
|
|
|
|
|
|
void
|
|
Menu::Draw(MenuItem *item)
|
|
{
|
|
if (!IsHidden())
|
|
platform_update_menu_item(this, item);
|
|
}
|
|
|
|
|
|
void
|
|
Menu::Run()
|
|
{
|
|
platform_run_menu(this);
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
static bool
|
|
user_menu_boot_volume(Menu *menu, MenuItem *item)
|
|
{
|
|
Menu *super = menu->Supermenu();
|
|
if (super == NULL) {
|
|
// huh?
|
|
return true;
|
|
}
|
|
|
|
MenuItem *bootItem = super->ItemAt(super->CountItems() - 1);
|
|
bootItem->SetEnabled(true);
|
|
bootItem->Select(true);
|
|
bootItem->SetData(item->Data());
|
|
|
|
gKernelArgs.boot_volume.SetBool(BOOT_VOLUME_USER_SELECTED, true);
|
|
return true;
|
|
}
|
|
|
|
|
|
static Menu *
|
|
add_boot_volume_menu(Directory *bootVolume)
|
|
{
|
|
Menu *menu = new(nothrow) Menu(CHOICE_MENU, "Select Boot Volume");
|
|
MenuItem *item;
|
|
void *cookie;
|
|
int32 count = 0;
|
|
|
|
if (gRoot->Open(&cookie, O_RDONLY) == B_OK) {
|
|
Directory *volume;
|
|
while (gRoot->GetNextNode(cookie, (Node **)&volume) == B_OK) {
|
|
// only list bootable volumes
|
|
if (!is_bootable(volume))
|
|
continue;
|
|
|
|
char name[B_FILE_NAME_LENGTH];
|
|
if (volume->GetName(name, sizeof(name)) == B_OK) {
|
|
menu->AddItem(item = new(nothrow) MenuItem(name));
|
|
item->SetTarget(user_menu_boot_volume);
|
|
item->SetData(volume);
|
|
|
|
if (volume == bootVolume) {
|
|
item->SetMarked(true);
|
|
item->Select(true);
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
gRoot->Close(cookie);
|
|
}
|
|
|
|
if (count == 0) {
|
|
// no boot volume found yet
|
|
menu->AddItem(item = new(nothrow) MenuItem("<No boot volume found>"));
|
|
item->SetType(MENU_ITEM_NO_CHOICE);
|
|
item->SetEnabled(false);
|
|
}
|
|
|
|
menu->AddSeparatorItem();
|
|
|
|
menu->AddItem(item = new(nothrow) MenuItem("Rescan volumes"));
|
|
item->SetHelpText("Please insert a Haiku CD-ROM or attach a USB disk - "
|
|
"depending on your system, you can then boot from them.");
|
|
item->SetType(MENU_ITEM_NO_CHOICE);
|
|
if (count == 0)
|
|
item->Select(true);
|
|
|
|
menu->AddItem(item = new(nothrow) MenuItem("Return to main menu"));
|
|
item->SetType(MENU_ITEM_NO_CHOICE);
|
|
|
|
if (gKernelArgs.boot_volume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false))
|
|
menu->SetChoiceText("CD-ROM or hard drive");
|
|
|
|
return menu;
|
|
}
|
|
|
|
|
|
static Menu *
|
|
add_safe_mode_menu()
|
|
{
|
|
Menu *safeMenu = new(nothrow) Menu(SAFE_MODE_MENU, "Safe Mode Options");
|
|
MenuItem *item;
|
|
|
|
safeMenu->AddItem(item = new(nothrow) MenuItem("Safe mode"));
|
|
item->SetData(B_SAFEMODE_SAFE_MODE);
|
|
item->SetType(MENU_ITEM_MARKABLE);
|
|
item->SetHelpText("Puts the system into safe mode. This can be enabled independently "
|
|
"from the other options.");
|
|
|
|
safeMenu->AddItem(item = new(nothrow) MenuItem("Disable user add-ons"));
|
|
item->SetData(B_SAFEMODE_DISABLE_USER_ADD_ONS);
|
|
item->SetType(MENU_ITEM_MARKABLE);
|
|
item->SetHelpText("Prevent all user installed add-ons to be loaded. Only the add-ons "
|
|
"in the system directory will be used.");
|
|
|
|
safeMenu->AddItem(item = new(nothrow) MenuItem("Disable IDE DMA"));
|
|
item->SetData(B_SAFEMODE_DISABLE_IDE_DMA);
|
|
item->SetType(MENU_ITEM_MARKABLE);
|
|
|
|
platform_add_menus(safeMenu);
|
|
|
|
safeMenu->AddItem(item = new(nothrow) MenuItem("Enable on screen debug output"));
|
|
item->SetData("debug_screen");
|
|
item->SetType(MENU_ITEM_MARKABLE);
|
|
|
|
safeMenu->AddSeparatorItem();
|
|
safeMenu->AddItem(item = new(nothrow) MenuItem("Return to main menu"));
|
|
|
|
return safeMenu;
|
|
}
|
|
|
|
|
|
static void
|
|
apply_safe_mode_options(Menu *menu)
|
|
{
|
|
MenuItemIterator iterator = menu->ItemIterator();
|
|
MenuItem *item;
|
|
char buffer[2048];
|
|
int32 pos = 0;
|
|
|
|
buffer[0] = '\0';
|
|
|
|
while ((item = iterator.Next()) != NULL) {
|
|
if (item->Type() == MENU_ITEM_SEPARATOR || !item->IsMarked()
|
|
|| item->Data() == NULL || (uint32)pos > sizeof(buffer))
|
|
continue;
|
|
|
|
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%s true\n",
|
|
(const char *)item->Data());
|
|
}
|
|
|
|
add_safe_mode_settings(buffer);
|
|
}
|
|
|
|
|
|
static bool
|
|
user_menu_reboot(Menu *menu, MenuItem *item)
|
|
{
|
|
platform_exit();
|
|
return true;
|
|
}
|
|
|
|
|
|
status_t
|
|
user_menu(Directory **_bootVolume)
|
|
{
|
|
Menu *menu = new(nothrow) Menu(MAIN_MENU);
|
|
Menu *safeModeMenu = NULL;
|
|
MenuItem *item;
|
|
|
|
// Add boot volume
|
|
menu->AddItem(item = new(nothrow) MenuItem("Select boot volume",
|
|
add_boot_volume_menu(*_bootVolume)));
|
|
|
|
// Add safe mode
|
|
menu->AddItem(item = new(nothrow) MenuItem("Select safe mode options",
|
|
safeModeMenu = add_safe_mode_menu()));
|
|
|
|
// Add platform dependent menus
|
|
platform_add_menus(menu);
|
|
|
|
menu->AddSeparatorItem();
|
|
if (*_bootVolume == NULL) {
|
|
menu->AddItem(item = new(nothrow) MenuItem("Reboot"));
|
|
item->SetTarget(user_menu_reboot);
|
|
}
|
|
|
|
menu->AddItem(item = new(nothrow) MenuItem("Continue booting"));
|
|
if (*_bootVolume == NULL) {
|
|
item->SetEnabled(false);
|
|
menu->ItemAt(0)->Select(true);
|
|
}
|
|
|
|
menu->Run();
|
|
|
|
// See if a new boot device has been selected, and propagate that back
|
|
if (item->Data() != NULL)
|
|
*_bootVolume = (Directory *)item->Data();
|
|
|
|
apply_safe_mode_options(safeModeMenu);
|
|
delete menu;
|
|
|
|
return B_OK;
|
|
}
|
|
|