diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:56:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:56:35 +0000 |
commit | eba0cfa6b0bef4f2e73c8630a7efa3944df8b0f8 (patch) | |
tree | 74c37eede1f0634cc5de1c63c934edaa1630c6bc /purgatory/arch/ia64 | |
parent | Initial commit. (diff) | |
download | kexec-tools-eba0cfa6b0bef4f2e73c8630a7efa3944df8b0f8.tar.xz kexec-tools-eba0cfa6b0bef4f2e73c8630a7efa3944df8b0f8.zip |
Adding upstream version 1:2.0.27.upstream/1%2.0.27upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'purgatory/arch/ia64')
-rw-r--r-- | purgatory/arch/ia64/Makefile | 17 | ||||
-rw-r--r-- | purgatory/arch/ia64/console-ia64.c | 47 | ||||
-rw-r--r-- | purgatory/arch/ia64/entry.S | 73 | ||||
-rw-r--r-- | purgatory/arch/ia64/io.h | 108 | ||||
-rw-r--r-- | purgatory/arch/ia64/purgatory-ia64.c | 307 | ||||
-rw-r--r-- | purgatory/arch/ia64/purgatory-ia64.h | 5 | ||||
-rw-r--r-- | purgatory/arch/ia64/vga.c | 143 |
7 files changed, 700 insertions, 0 deletions
diff --git a/purgatory/arch/ia64/Makefile b/purgatory/arch/ia64/Makefile new file mode 100644 index 0000000..4a2564c --- /dev/null +++ b/purgatory/arch/ia64/Makefile @@ -0,0 +1,17 @@ +# +# Purgatory ia64 +# +ia64_PURGATORY_SRCS += purgatory/arch/ia64/entry.S +ia64_PURGATORY_SRCS += purgatory/arch/ia64/purgatory-ia64.c +ia64_PURGATORY_SRCS += purgatory/arch/ia64/console-ia64.c +ia64_PURGATORY_SRCS += purgatory/arch/ia64/vga.c + +ia64_PURGATORY_EXTRA_CFLAGS = -ffixed-r28 + +# sha256.c needs to be compiled without optimization, else +# purgatory fails to execute on ia64. +ia64_PURGATORY_SHA256_CFLAGS = -O0 + +dist += purgatory/arch/ia64/Makefile $(ia64_PURGATORY_SRCS) \ + purgatory/arch/ia64/io.h purgatory/arch/ia64/purgatory-ia64.h + diff --git a/purgatory/arch/ia64/console-ia64.c b/purgatory/arch/ia64/console-ia64.c new file mode 100644 index 0000000..99d97ca --- /dev/null +++ b/purgatory/arch/ia64/console-ia64.c @@ -0,0 +1,47 @@ +#include <purgatory.h> +#include "io.h" + +#define VGABASE UNCACHED(0xb8000) + +/* code based on i386 console code + * TODO add serial support + */ +#define MAX_YPOS 25 +#define MAX_XPOS 80 + +unsigned long current_ypos = 1, current_xpos = 0; + +static void putchar_vga(int ch) +{ + int i, k, j; + + if (current_ypos >= MAX_YPOS) { + /* scroll 1 line up */ + for (k = 1, j = 0; k < MAX_YPOS; k++, j++) { + for (i = 0; i < MAX_XPOS; i++) { + writew(readw(VGABASE + 2*(MAX_XPOS*k + i)), + VGABASE + 2*(MAX_XPOS*j + i)); + } + } + for (i = 0; i < MAX_XPOS; i++) + writew(0x720, VGABASE + 2*(MAX_XPOS*j + i)); + current_ypos = MAX_YPOS-1; + } + if (ch == '\n') { + current_xpos = 0; + current_ypos++; + } else if (ch != '\r') { + writew(((0x7 << 8) | (unsigned short) ch), + VGABASE + 2*(MAX_XPOS*current_ypos + + current_xpos++)); + if (current_xpos >= MAX_XPOS) { + current_xpos = 0; + current_ypos++; + } + } +} + +void putchar(int ch) +{ + putchar_vga(ch); +} diff --git a/purgatory/arch/ia64/entry.S b/purgatory/arch/ia64/entry.S new file mode 100644 index 0000000..f05434f --- /dev/null +++ b/purgatory/arch/ia64/entry.S @@ -0,0 +1,73 @@ +/* + * purgatory: setup code + * + * Copyright (C) 2005-2006 Zou Nan hai (nanhai.zou@intel.com) + * + * This program 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 (version 2 of the License). + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#define DECLARE_DATA8(name) \ +.global name; \ +.size name, 8; \ +name: data8 0x0 + +.global __dummy_efi_function +.align 32 +.proc __dummy_efi_function +__dummy_efi_function: + mov r8=r0;; + br.ret.sptk.many rp;; +.global __dummy_efi_function_end +__dummy_efi_function_end: +.endp __dummy_efi_function + +.global purgatory_start +.align 32 +.proc purgatory_start +purgatory_start: + movl r2=__gp_value;; + ld8 gp=[r2];; + br.call.sptk.many b0=purgatory + ;; + alloc r2 = ar.pfs, 0, 0, 2, 0 + ;; + mov out0=r28 + movl out1=__vmcode_base; + br.call.sptk.many b0=ia64_env_setup + movl r10=__kernel_entry;; + ld8 r14=[r10];; + movl r10=__boot_param_base;; + ld8 r28=[r10];; + mov b6=r14;; + mov ar.lc=r0 + mov ar.ec=r0 + cover;; + invala;; + br.call.sptk.many b0=b6 +.endp purgatory_start + +DECLARE_DATA8(__kernel_entry) +DECLARE_DATA8(__vmcode_base) +DECLARE_DATA8(__vmcode_size) +DECLARE_DATA8(__ramdisk_base) +DECLARE_DATA8(__ramdisk_size) +DECLARE_DATA8(__command_line) +DECLARE_DATA8(__command_line_len) +DECLARE_DATA8(__efi_memmap_base) +DECLARE_DATA8(__efi_memmap_size) +DECLARE_DATA8(__boot_param_base) +DECLARE_DATA8(__loaded_segments) +DECLARE_DATA8(__loaded_segments_num) + +DECLARE_DATA8(__gp_value) +DECLARE_DATA8(__noio) diff --git a/purgatory/arch/ia64/io.h b/purgatory/arch/ia64/io.h new file mode 100644 index 0000000..0f78580 --- /dev/null +++ b/purgatory/arch/ia64/io.h @@ -0,0 +1,108 @@ +#ifndef IO_H +#define IO_H +#define UNCACHED(x) (void *)((x)|(1UL<<63)) +#define MF() asm volatile ("mf.a" ::: "memory") +#define IO_SPACE_ENCODING(p) ((((p) >> 2) << 12) | (p & 0xfff)) +extern long __noio; +static inline void *io_addr (unsigned long port) +{ + unsigned long offset; + unsigned long io_base; + asm volatile ("mov %0=ar.k0":"=r"(io_base)); + offset = IO_SPACE_ENCODING(port); + return UNCACHED(io_base | offset); +} + +static inline unsigned int inb (unsigned long port) +{ + volatile unsigned char *addr = io_addr(port); + unsigned char ret = 0; + if (!__noio) { + ret = *addr; + MF(); + } + return ret; +} + +static inline unsigned int inw (unsigned long port) +{ + volatile unsigned short *addr = io_addr(port); + unsigned short ret = 0; + + if (!__noio) { + ret = *addr; + MF(); + } + return ret; +} + +static inline unsigned int inl (unsigned long port) +{ + volatile unsigned int *addr = io_addr(port); + unsigned int ret ; + if (!__noio) { + ret = *addr; + MF(); + } + return ret; +} + +static inline void outb (unsigned char val, unsigned long port) +{ + volatile unsigned char *addr = io_addr(port); + + if (!__noio) { + *addr = val; + MF(); + } +} + +static inline void outw (unsigned short val, unsigned long port) +{ + volatile unsigned short *addr = io_addr(port); + + if (!__noio) { + *addr = val; + MF(); + } +} + +static inline void outl (unsigned int val, unsigned long port) +{ + volatile unsigned int *addr = io_addr(port); + + if (!__noio) { + *addr = val; + MF(); + } +} + +static inline unsigned char readb(const volatile void *addr) +{ + return __noio ? 0 :*(volatile unsigned char *) addr; +} +static inline unsigned short readw(const volatile void *addr) +{ + return __noio ? 0 :*(volatile unsigned short *) addr; +} +static inline unsigned int readl(const volatile void *addr) +{ + return __noio ? 0 :*(volatile unsigned int *) addr; +} + +static inline void writeb(unsigned char b, volatile void *addr) +{ + if (!__noio) + *(volatile unsigned char *) addr = b; +} +static inline void writew(unsigned short b, volatile void *addr) +{ + if (!__noio) + *(volatile unsigned short *) addr = b; +} +static inline void writel(unsigned int b, volatile void *addr) +{ + if (!__noio) + *(volatile unsigned int *) addr = b; +} +#endif diff --git a/purgatory/arch/ia64/purgatory-ia64.c b/purgatory/arch/ia64/purgatory-ia64.c new file mode 100644 index 0000000..e138e9c --- /dev/null +++ b/purgatory/arch/ia64/purgatory-ia64.c @@ -0,0 +1,307 @@ +/* + * purgatory: setup code + * + * Copyright (C) 2005-2006 Zou Nan hai (nanhai.zou@intel.com) + * + * This program 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 (version 2 of the License). + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <purgatory.h> +#include <stdint.h> +#include <string.h> +#include "purgatory-ia64.h" + +#define PAGE_OFFSET 0xe000000000000000UL + +#define EFI_PAGE_SHIFT 12 +#define EFI_PAGE_SIZE (1UL<<EFI_PAGE_SHIFT) +#define EFI_PAGE_ALIGN(x) ((x + EFI_PAGE_SIZE - 1)&~(EFI_PAGE_SIZE-1)) +/* Memory types: */ +#define EFI_RESERVED_TYPE 0 +#define EFI_LOADER_CODE 1 +#define EFI_LOADER_DATA 2 +#define EFI_BOOT_SERVICES_CODE 3 +#define EFI_BOOT_SERVICES_DATA 4 +#define EFI_RUNTIME_SERVICES_CODE 5 +#define EFI_RUNTIME_SERVICES_DATA 6 +#define EFI_CONVENTIONAL_MEMORY 7 +#define EFI_UNUSABLE_MEMORY 8 +#define EFI_ACPI_RECLAIM_MEMORY 9 +#define EFI_ACPI_MEMORY_NVS 10 +#define EFI_MEMORY_MAPPED_IO 11 +#define EFI_MEMORY_MAPPED_IO_PORT_SPACE 12 +#define EFI_PAL_CODE 13 +#define EFI_MAX_MEMORY_TYPE 14 + +typedef struct { + uint64_t signature; + uint32_t revision; + uint32_t headersize; + uint32_t crc32; + uint32_t reserved; +} efi_table_hdr_t; + +typedef struct { + efi_table_hdr_t hdr; + unsigned long get_time; + unsigned long set_time; + unsigned long get_wakeup_time; + unsigned long set_wakeup_time; + unsigned long set_virtual_address_map; + unsigned long convert_pointer; + unsigned long get_variable; + unsigned long get_next_variable; + unsigned long set_variable; + unsigned long get_next_high_mono_count; + unsigned long reset_system; +} efi_runtime_services_t; + +typedef struct { + efi_table_hdr_t hdr; + unsigned long fw_vendor; /* physical addr of + CHAR16 vendor string */ + uint32_t fw_revision; + unsigned long con_in_handle; + unsigned long con_in; + unsigned long con_out_handle; + unsigned long con_out; + unsigned long stderr_handle; + unsigned long stderr; + unsigned long runtime; + unsigned long boottime; + unsigned long nr_tables; + unsigned long tables; +} efi_system_table_t; + +struct ia64_boot_param { + uint64_t command_line; /* physical address of + command linearguments */ + uint64_t efi_systab; /* physical address of + EFI system table */ + uint64_t efi_memmap; /* physical address of + EFI memory map */ + uint64_t efi_memmap_size; /* size of EFI memory map */ + uint64_t efi_memdesc_size; /* size of an EFI memory map + descriptor */ + uint32_t efi_memdesc_version; /* memory descriptor version */ + struct { + uint16_t num_cols; /* number of columns on console + output device */ + uint16_t num_rows; /* number of rows on console + output device */ + uint16_t orig_x; /* cursor's x position */ + uint16_t orig_y; /* cursor's y position */ + } console_info; + uint64_t fpswa; /* physical address of + the fpswa interface */ + uint64_t initrd_start; + uint64_t initrd_size; + + uint64_t vmcode_start; + uint64_t vmcode_size; +}; + +typedef struct { + uint32_t type; + uint32_t pad; + uint64_t phys_addr; + uint64_t virt_addr; + uint64_t num_pages; + uint64_t attribute; +} efi_memory_desc_t; + +struct loaded_segment { + unsigned long start; + unsigned long end; +}; + +struct kexec_boot_params { + uint64_t vmcode_base; + uint64_t vmcode_size; + uint64_t ramdisk_base; + uint64_t ramdisk_size; + uint64_t command_line; + uint64_t command_line_len; + uint64_t efi_memmap_base; + uint64_t efi_memmap_size; + uint64_t boot_param_base; + struct loaded_segment *loaded_segments; + unsigned long loaded_segments_num; +}; + +void +setup_arch(void) +{ + reset_vga(); +} + +inline unsigned long PA(unsigned long addr) +{ + return addr & 0x0fffffffffffffffLL; +} + +void +patch_efi_memmap(struct kexec_boot_params *params, + struct ia64_boot_param *boot_param) +{ + void *dest = (void *)params->efi_memmap_base; + void *src = (void *)boot_param->efi_memmap; + uint64_t orig_type; + efi_memory_desc_t *src_md, *dst_md; + void *src_end = src + boot_param->efi_memmap_size; + unsigned long i; + for (; src < src_end; src += boot_param->efi_memdesc_size, + dest += boot_param->efi_memdesc_size) { + unsigned long mstart, mend; + src_md = src; + dst_md = dest; + if (src_md->num_pages == 0) + continue; + mstart = src_md->phys_addr; + mend = src_md->phys_addr + + (src_md->num_pages << EFI_PAGE_SHIFT); + *dst_md = *src_md; + if (src_md->type == EFI_LOADER_DATA) + dst_md->type = EFI_CONVENTIONAL_MEMORY; + /* segments are already sorted and aligned to 4K */ + orig_type = dst_md->type; + for (i = 0; i < params->loaded_segments_num; i++) { + struct loaded_segment *seg; + unsigned long start_pages, mid_pages, end_pages; + + seg = ¶ms->loaded_segments[i]; + if (seg->start < mstart || seg->start >= mend) + continue; + + while (seg->end > mend && src < src_end) { + src += boot_param->efi_memdesc_size; + src_md = src; + /* TODO check contig and attribute here */ + mend = src_md->phys_addr + + (src_md->num_pages << EFI_PAGE_SHIFT); + } + if (seg->end < mend && src < src_end) { + void *src_next; + efi_memory_desc_t *src_next_md; + src_next = src + boot_param->efi_memdesc_size; + src_next_md = src_next; + if (src_next_md->type == + EFI_CONVENTIONAL_MEMORY) { + /* TODO check contig and attribute */ + src += boot_param->efi_memdesc_size; + src_md = src; + mend = src_md->phys_addr + + (src_md->num_pages << + EFI_PAGE_SHIFT); + } + + } + start_pages = (seg->start - mstart) >> EFI_PAGE_SHIFT; + mid_pages = (seg->end - seg->start) >> EFI_PAGE_SHIFT; + end_pages = (mend - seg->end) >> EFI_PAGE_SHIFT; + if (start_pages) { + dst_md->num_pages = start_pages; + dest += boot_param->efi_memdesc_size; + dst_md = dest; + *dst_md = *src_md; + } + dst_md->phys_addr = seg->start; + dst_md->num_pages = mid_pages; + dst_md->type = EFI_LOADER_DATA; + if (!end_pages) + break; + dest += boot_param->efi_memdesc_size; + dst_md = dest; + *dst_md = *src_md; + dst_md->phys_addr = seg->end; + dst_md->num_pages = end_pages; + dst_md->type = orig_type; + mstart = seg->end; + } + } + + boot_param->efi_memmap_size = dest - (void *)params->efi_memmap_base; +} + +void +flush_icache_range(char *start, unsigned long len) +{ + unsigned long i, addr; + addr = (unsigned long)start & ~31UL; + len += (unsigned long)start - addr; + for (i = 0;i < len; i += 32) + asm volatile("fc.i %0"::"r"(start + i):"memory"); + asm volatile (";;sync.i;;":::"memory"); + asm volatile ("srlz.i":::"memory"); +} + +extern char __dummy_efi_function[], __dummy_efi_function_end[]; + + +void +ia64_env_setup(struct ia64_boot_param *boot_param, + struct kexec_boot_params *params) +{ + unsigned long len; + efi_system_table_t *systab; + efi_runtime_services_t *runtime; + unsigned long *set_virtual_address_map; + char *command_line = (char *)params->command_line; + uint64_t command_line_len = params->command_line_len; + struct ia64_boot_param *new_boot_param = + (struct ia64_boot_param *) params->boot_param_base; + memcpy(new_boot_param, boot_param, 4096); + + /* + * patch efi_runtime->set_virtual_address_map to a dummy function + * + * The EFI specification mandates that set_virtual_address_map only + * takes effect the first time that it is called, and that + * subsequent calls will return error. By replacing it with a + * dummy function the new OS can think it is calling it again + * without either the OS or any buggy EFI implementations getting + * upset. + * + * Note: as the EFI specification says that set_virtual_address_map + * will only take affect the first time it is called, the mapping + * can't be updated, and thus mapping of the old and new OS really + * needs to be the same. + */ + len = __dummy_efi_function_end - __dummy_efi_function; + memcpy(command_line + command_line_len, + __dummy_efi_function, len); + systab = (efi_system_table_t *)new_boot_param->efi_systab; + runtime = (efi_runtime_services_t *)PA(systab->runtime); + set_virtual_address_map = + (unsigned long *)PA(runtime->set_virtual_address_map); + *(set_virtual_address_map) = + (unsigned long)(command_line + command_line_len); + flush_icache_range(command_line + command_line_len, len); + + patch_efi_memmap(params, new_boot_param); + + new_boot_param->efi_memmap = params->efi_memmap_base; + new_boot_param->command_line = params->command_line; + new_boot_param->console_info.orig_x = 0; + new_boot_param->console_info.orig_y = 0; + new_boot_param->initrd_start = params->ramdisk_base; + new_boot_param->initrd_size = params->ramdisk_size; + new_boot_param->vmcode_start = params->vmcode_base; + new_boot_param->vmcode_size = params->vmcode_size; +} + +/* This function can be used to execute after the SHA256 verification. */ +void post_verification_setup_arch(void) +{ + /* Nothing for now */ +} diff --git a/purgatory/arch/ia64/purgatory-ia64.h b/purgatory/arch/ia64/purgatory-ia64.h new file mode 100644 index 0000000..8fd3af1 --- /dev/null +++ b/purgatory/arch/ia64/purgatory-ia64.h @@ -0,0 +1,5 @@ +#ifndef PURGATORY_IA64_H +#define PURGATORY_IA64_H + +void reset_vga(void); +#endif /* PURGATORY_IA64_H */ diff --git a/purgatory/arch/ia64/vga.c b/purgatory/arch/ia64/vga.c new file mode 100644 index 0000000..dceadb7 --- /dev/null +++ b/purgatory/arch/ia64/vga.c @@ -0,0 +1,143 @@ +#include "io.h" +void reset_vga(void) +{ + /* Hello */ + inb(0x3da); + outb(0, 0x3c0); + + /* Sequencer registers */ + outw(0x0300, 0x3c4); + outw(0x0001, 0x3c4); + outw(0x0302, 0x3c4); + outw(0x0003, 0x3c4); + outw(0x0204, 0x3c4); + + /* Ensure CRTC regs 0-7 are unlocked by clearing bit 7 of CRTC[17] */ + outw(0x0e11, 0x3d4); + /* CRTC registers */ + outw(0x5f00, 0x3d4); + outw(0x4f01, 0x3d4); + outw(0x5002, 0x3d4); + outw(0x8203, 0x3d4); + outw(0x5504, 0x3d4); + outw(0x8105, 0x3d4); + outw(0xbf06, 0x3d4); + outw(0x1f07, 0x3d4); + outw(0x0008, 0x3d4); + outw(0x4f09, 0x3d4); + outw(0x200a, 0x3d4); + outw(0x0e0b, 0x3d4); + outw(0x000c, 0x3d4); + outw(0x000d, 0x3d4); + outw(0x010e, 0x3d4); + outw(0xe00f, 0x3d4); + outw(0x9c10, 0x3d4); + outw(0x8e11, 0x3d4); + outw(0x8f12, 0x3d4); + outw(0x2813, 0x3d4); + outw(0x1f14, 0x3d4); + outw(0x9615, 0x3d4); + outw(0xb916, 0x3d4); + outw(0xa317, 0x3d4); + outw(0xff18, 0x3d4); + + /* Graphic registers */ + outw(0x0000, 0x3ce); + outw(0x0001, 0x3ce); + outw(0x0002, 0x3ce); + outw(0x0003, 0x3ce); + outw(0x0004, 0x3ce); + outw(0x1005, 0x3ce); + outw(0x0e06, 0x3ce); + outw(0x0007, 0x3ce); + outw(0xff08, 0x3ce); + + /* Attribute registers */ + inb(0x3da); + outb(0x00, 0x3c0); + outb(0x00, 0x3c0); + + inb(0x3da); + outb(0x01, 0x3c0); + outb(0x01, 0x3c0); + + inb(0x3da); + outb(0x02, 0x3c0); + outb(0x02, 0x3c0); + + inb(0x3da); + outb(0x03, 0x3c0); + outb(0x03, 0x3c0); + + inb(0x3da); + outb(0x04, 0x3c0); + outb(0x04, 0x3c0); + + inb(0x3da); + outb(0x05, 0x3c0); + outb(0x05, 0x3c0); + + inb(0x3da); + outb(0x06, 0x3c0); + outb(0x14, 0x3c0); + + inb(0x3da); + outb(0x07, 0x3c0); + outb(0x07, 0x3c0); + + inb(0x3da); + outb(0x08, 0x3c0); + outb(0x38, 0x3c0); + + inb(0x3da); + outb(0x09, 0x3c0); + outb(0x39, 0x3c0); + + inb(0x3da); + outb(0x0a, 0x3c0); + outb(0x3a, 0x3c0); + + inb(0x3da); + outb(0x0b, 0x3c0); + outb(0x3b, 0x3c0); + + inb(0x3da); + outb(0x0c, 0x3c0); + outb(0x3c, 0x3c0); + + inb(0x3da); + outb(0x0d, 0x3c0); + outb(0x3d, 0x3c0); + + inb(0x3da); + outb(0x0e, 0x3c0); + outb(0x3e, 0x3c0); + + inb(0x3da); + outb(0x0f, 0x3c0); + outb(0x3f, 0x3c0); + + inb(0x3da); + outb(0x10, 0x3c0); + outb(0x0c, 0x3c0); + + inb(0x3da); + outb(0x11, 0x3c0); + outb(0x00, 0x3c0); + + inb(0x3da); + outb(0x12, 0x3c0); + outb(0x0f, 0x3c0); + + inb(0x3da); + outb(0x13, 0x3c0); + outb(0x08, 0x3c0); + + inb(0x3da); + outb(0x14, 0x3c0); + outb(0x00, 0x3c0); + + /* Goodbye */ + inb(0x3da); + outb(0x20, 0x3c0); +} |