syslinux/syslinux-3.82-gfxboot.diff
OBS User autobuild ff0f0fdacd Accepting request 17451 from system:install:head
Copy from system:install:head/syslinux based on submit request 17451 from user oertel

OBS-URL: https://build.opensuse.org/request/show/17451
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/syslinux?expand=0&rev=16
2009-08-11 16:27:34 +00:00

2635 lines
54 KiB
Diff

diff --git a/core/isolinux.asm b/core/isolinux.asm
index 2627c2d..a0910fb 100644
--- a/core/isolinux.asm
+++ b/core/isolinux.asm
@@ -1135,73 +1135,23 @@ all_read:
; (which will be at 16 only for a single-session disk!); from the PVD
; we should be able to find the rest of what we need to know.
;
-get_fs_structures:
- mov eax,[bi_pvd]
- mov bx,trackbuf
- call getonesec
-
- mov eax,[trackbuf+156+2]
- mov [RootDir+dir_lba],eax
- mov [CurrentDir+dir_lba],eax
-%ifdef DEBUG_MESSAGES
- mov si,dbg_rootdir_msg
- call writemsg
- call writehex8
- call crlf
-%endif
- mov eax,[trackbuf+156+10]
- mov [RootDir+dir_len],eax
- mov [CurrentDir+dir_len],eax
- add eax,SECTOR_SIZE-1
- shr eax,SECTOR_SHIFT
- mov [RootDir+dir_clust],eax
- mov [CurrentDir+dir_clust],eax
-
- ; Look for an isolinux directory, and if found,
- ; make it the current directory instead of the root
- ; directory.
- ; Also copy the name of the directory to CurrentDirName
- mov word [CurrentDirName],ROOT_DIR_WORD ; Write '/',0 to the CurrentDirName
+ call iso_mount
mov di,boot_dir ; Search for /boot/isolinux
- mov al,02h
- push di
- call searchdir_iso
- pop di
- jnz .found_dir
- mov di,isolinux_dir
- mov al,02h ; Search for /isolinux
- push di
- call searchdir_iso
- pop di
- jz .no_isolinux_dir
+ call setcwd
+ jnc .found_dir
+ mov di,isolinux_dir ; Search for /isolinux
+ call setcwd
.found_dir:
- ; Copy current directory name to CurrentDirName
- push si
- push di
- mov si,di
- mov di,CurrentDirName
- call strcpy
- mov byte [di],0 ;done in case it's not word aligned
- dec di
- mov byte [di],'/'
- pop di
- pop si
- mov [CurrentDir+dir_len],eax
- mov eax,[si+file_left]
- mov [CurrentDir+dir_clust],eax
- xor eax,eax ; Free this file pointer entry
- xchg eax,[si+file_sector]
- mov [CurrentDir+dir_lba],eax
%ifdef DEBUG_MESSAGES
push si
mov si,dbg_isodir_msg
call writemsg
pop si
+ mov eax,[CurrentDir+dir_lba]
call writehex8
call crlf
%endif
-.no_isolinux_dir:
;
; Locate the configuration file
@@ -1706,6 +1656,90 @@ getfssec:
TRACER 'f'
ret
+;
+; setcwd: Set current working directory.
+;
+; On entry:
+; DI -> directory name
+; On exit:
+; CF = 1 -> error
+;
+; On error, the old working directory is kept.
+;
+setcwd:
+ mov al,02h
+ push di
+ call searchdir_iso
+ pop di
+ stc
+ jz .err
+ mov [CurrentDir+dir_len],eax
+ mov eax,[si+file_left]
+ mov [CurrentDir+dir_clust],eax
+ xor eax,eax
+ xchg eax,[si+file_sector]
+ mov [CurrentDir+dir_lba],eax
+ mov si,di
+ mov di,CurrentDirName
+ cmp si,di
+ jz .ok
+ mov cx,FILENAME_MAX
+ push ds
+ pop es
+.copy:
+ lodsb
+ stosb
+ or al,al
+ loopnz .copy
+ mov byte [di-1],0
+ jnz .err
+.ok:
+ clc
+.err:
+ ret
+
+;
+; Read fs meta data and setup RootDir and CurrentDir.
+;
+; On exit:
+; CF = 1 -> error
+;
+iso_mount:
+ mov eax,[bi_pvd]
+ mov bx,trackbuf
+ call getonesec
+
+ mov eax,[trackbuf+156+10]
+ mov [RootDir+dir_len],eax
+ add eax,SECTOR_SIZE-1
+ shr eax,SECTOR_SHIFT
+ mov [RootDir+dir_clust],eax
+ mov eax,[trackbuf+156+2]
+ mov [RootDir+dir_lba],eax
+
+ push ds
+ pop es
+ mov si,RootDir
+ mov di,CurrentDir
+ mov cx,dir_t_size
+ rep movsb
+
+%ifdef DEBUG_MESSAGES
+ mov si,dbg_rootdir_msg
+ call writemsg
+ call writehex8
+ call crlf
+%endif
+
+ mov di,CurrentDirName
+ call setcwd
+ jnc .ok
+ mov word [CurrentDirName],ROOT_DIR_WORD
+.ok:
+ clc
+ ret
+
+
; -----------------------------------------------------------------------------
; Common modules
; -----------------------------------------------------------------------------
diff --git a/core/abort.inc b/core/abort.inc
index 5b16b9d..cc59fa7 100644
--- a/core/abort.inc
+++ b/core/abort.inc
@@ -24,6 +24,10 @@
; assumes CS == DS
;
dot_pause:
+ push ax
+ mov al,5
+ call [comboot_hook]
+ pop ax
push si
mov si,dot_msg
call writestr_qchk
@@ -63,6 +67,8 @@ abort_check:
abort_load:
mov bx,error_or_command
abort_load_chain:
+ mov al,80h
+ call [comboot_hook] ; may not return
RESET_STACK_AND_SEGS AX
call writestr ; Expects SI -> error msg
diff --git a/core/loadhigh.inc b/core/loadhigh.inc
index 8ff9da1..91061fc 100644
--- a/core/loadhigh.inc
+++ b/core/loadhigh.inc
@@ -101,6 +101,8 @@ load_high:
ret
.overflow: mov si,err_nohighmem
+ mov al,83h
+ call [comboot_hook] ; may not return
jmp abort_load
section .data
diff --git a/core/runkernel.inc b/core/runkernel.inc
index 8bfc8b8..f458fc7 100644
--- a/core/runkernel.inc
+++ b/core/runkernel.inc
@@ -228,6 +228,8 @@ new_kernel:
mov [LoadFlags],al
any_kernel:
+ mov al,4
+ call [comboot_hook]
mov si,loading_msg
call writestr_qchk
mov si,KernelCName ; Print kernel name part of
@@ -319,6 +321,9 @@ load_initrd:
;
call abort_check ; Last chance!!
+ mov al,6
+ call [comboot_hook]
+
mov si,ready_msg
call writestr_qchk
@@ -489,6 +494,8 @@ old_kernel:
xor ax,ax
cmp word [InitRDPtr],ax ; Old kernel can't have initrd
je .load
+ mov al,82h
+ call [comboot_hook] ; may not return
mov si,err_oldkernel
jmp abort_load
.load:
@@ -613,6 +620,8 @@ loadinitrd:
ret
.notthere:
+ mov al,81h
+ call [comboot_hook] ; may not return
mov si,err_noinitrd
call writestr
mov si,InitRDCName
diff --git a/core/ui.inc b/core/ui.inc
index 1b40717..be4f385 100644
--- a/core/ui.inc
+++ b/core/ui.inc
@@ -468,6 +468,8 @@ bad_kernel:
.really:
mov si,KernelName
mov di,KernelCName
+ mov al,81h
+ call [comboot_hook] ; may not return
push di
call unmangle_name ; Get human form
mov si,err_notfound ; Complain about missing kernel
@@ -510,7 +512,10 @@ on_error:
;
; kernel_corrupt: Called if the kernel file does not seem healthy
;
-kernel_corrupt: mov si,err_notkernel
+kernel_corrupt:
+ mov al,82h
+ call [comboot_hook] ; may not return
+ mov si,err_notkernel
jmp abort_load
;
diff --git a/core/comboot.inc b/core/comboot.inc
index cdba16d..1a1dbfe 100644
--- a/core/comboot.inc
+++ b/core/comboot.inc
@@ -96,24 +96,23 @@ is_comboot_image:
shl ax,6 ; Kilobytes -> paragraphs
mov word [es:02h],ax
-%ifndef DEPEND
-%if real_mode_seg != comboot_seg
-%error "This code assumes real_mode_seg == comboot_seg"
-%endif
-%endif
; Copy the command line from high memory
+ push word real_mode_seg
+ pop ds
mov si,cmd_line_here
mov cx,125 ; Max cmdline len (minus space and CR)
mov di,081h ; Offset in PSP for command line
mov al,' ' ; DOS command lines begin with a space
stosb
-.loop: es lodsb
+.loop: lodsb
and al,al
jz .done
stosb
loop .loop
.done:
+ push cs
+ pop ds
mov al,0Dh ; CR after last character
stosb
diff --git a/core/layout.inc b/core/layout.inc
index 8c2e248..ca95d2b 100644
--- a/core/layout.inc
+++ b/core/layout.inc
@@ -123,4 +123,4 @@ real_mode_seg equ cache_seg + 1000h
pktbuf_seg equ cache_seg ; PXELINUX packet buffers
%endif
-comboot_seg equ real_mode_seg ; COMBOOT image loading zone
+comboot_seg equ real_mode_seg + 1000h ; COMBOOT image loading zone
diff --git a/core/runkernel.inc b/core/runkernel.inc
index f458fc7..f6ed644 100644
--- a/core/runkernel.inc
+++ b/core/runkernel.inc
@@ -165,7 +165,7 @@ opt_mem:
ret
opt_quiet:
- mov byte [QuietBoot],1
+ or byte [QuietBoot],1
ret
%if IS_PXELINUX
@@ -634,7 +634,7 @@ loadinitrd:
; assumes CS == DS
;
writestr_qchk:
- test byte [QuietBoot],01h
+ test byte [QuietBoot],03h
jz writestr
ret
@@ -689,4 +689,6 @@ KernelVersion resw 1 ; Kernel protocol version
;
InitRDPtr resw 1 ; Pointer to initrd= option in command line
LoadFlags resb 1 ; Loadflags from kernel
-QuietBoot resb 1 ; Set if a quiet boot is requested
+
+ section .data
+QuietBoot db 0 ; Set if a quiet boot is requested
diff --git a/core/ui.inc b/core/ui.inc
index be4f385..2bc56a1 100644
--- a/core/ui.inc
+++ b/core/ui.inc
@@ -596,7 +596,7 @@ kernel_good:
;
xor ax,ax
mov [InitRDPtr],ax
- mov [QuietBoot],al
+ and byte [QuietBoot],~1
%if IS_PXELINUX
mov [KeepPXE],al
%endif
diff --git a/core/runkernel.inc b/core/runkernel.inc
index f6ed644..286c9c8 100644
--- a/core/runkernel.inc
+++ b/core/runkernel.inc
@@ -259,7 +259,7 @@ read_kernel:
mov ecx,8000h ; 32K
sub ecx,esi ; Number of bytes to copy
add esi,(real_mode_seg << 4) ; Pointer to source
- mov edi,100000h ; Copy to address 100000h
+ mov edi,[KernelStart] ; Copy to kernel address
call bcopy ; Transfer to high memory
@@ -431,7 +431,7 @@ setup_move:
mov eax,10000h ; Target address of low kernel
stosd
- mov eax,100000h ; Where currently loaded
+ mov eax,[KernelStart] ; Where currently loaded
stosd
neg eax
add eax,[KernelEnd]
@@ -439,9 +439,13 @@ setup_move:
inc cx
mov bx,9000h ; Revised real mode segment
+ jmp .loading_initrd
.loading_high:
+ mov eax,[KernelStart]
+ mov [fs:su_code32start],eax
+.loading_initrd:
cmp word [InitRDPtr],0 ; Did we have an initrd?
je .no_initrd
@@ -692,3 +696,5 @@ LoadFlags resb 1 ; Loadflags from kernel
section .data
QuietBoot db 0 ; Set if a quiet boot is requested
+ alignz 4
+KernelStart dd 100000h
diff --git a/core/comboot.inc b/core/comboot.inc
index 1a1dbfe..ee3fd77 100644
--- a/core/comboot.inc
+++ b/core/comboot.inc
@@ -962,6 +962,45 @@ comapi_shufraw:
mov ecx,P_ECX
jmp shuffle_and_boot_raw
+
+;
+; INT 22h AX=0025h Set current working directory
+;
+%if IS_ISOLINUX
+comapi_setcwd:
+ mov si,P_BX
+ mov di,TmpDirName
+ mov cx,FILENAME_MAX
+ mov ds,P_ES
+.copy:
+ lodsb
+ stosb
+ or al,al
+ loopnz .copy
+ push cs
+ pop ds
+ stc
+ jnz .err
+ mov di,TmpDirName
+ call setcwd
+.err:
+ ret
+%else
+comapi_setcwd equ comapi_err
+%endif
+
+
+;
+; INT 22h AX=0026h Read filesystem meta data
+;
+%if IS_ISOLINUX
+comapi_mount:
+; call iso_mount
+ ret
+%else
+comapi_mount equ comapi_err
+%endif
+
section .data
%macro int21 2
@@ -969,6 +1008,88 @@ comapi_shufraw:
dw %2
%endmacro
+
+;
+; INT 22h AX=0027h Run command, return on error
+;
+; Terminates the COMBOOT program and executes the command line in
+; ES:BX as if it had been entered by the user.
+; CS:SI: comboot callback (via far call)
+; EDI kernel load address
+;
+comapi_run2:
+ push word P_CS
+ push word P_SI
+ pop dword [comboot_far]
+ push dword P_EDI
+ pop dword [KernelStart]
+ mov ds,P_ES
+ mov si,P_BX
+ mov di,command_line
+ call strcpy
+ push cs
+ pop ds
+ push cs
+ pop es
+ mov [comboot_sp_save],sp ; save stack pointer
+ mov word [comboot_hook],comboot_hook_entry
+ or byte [QuietBoot],2
+ jmp load_kernel ; Run a new kernel
+
+comapi_run2_cont:
+ mov word [comboot_hook],comboot_hook_nop
+ mov sp,[comboot_sp_save] ; fix stack pointer
+ and byte [QuietBoot],~2
+ clc
+ ret
+
+;
+; Callback function used at various places during kernel/initrd loading.
+;
+; The function either returns or continues at comapi_run2_cont.
+;
+; AL:
+; bit 7: 0/1 return/don't return
+; bit 0-6: function code
+; 0: abort kernel/initrd loading
+; 1: kernel/initrd not found
+; 2: kernel corrupt
+; 3: out of memory (while initrd loading)
+; 4: progress start
+; 5: progress increment
+; 6: progress end: kernel loaded, stop gfxboot
+;
+comboot_hook_entry:
+ pushad
+ push gs
+ push fs
+ push es
+ push ds
+ call far [comboot_far]
+ pop ds
+ pop es
+ pop fs
+ pop gs
+ popad
+ pushad
+ and al,7fh
+ cmp al,6
+ jnz .notlast
+ push es
+ mov si,DOSSaveVectors
+ mov di,4*20h
+ mov cx,20h
+ push word 0
+ pop es
+ rep movsd ; Restore DOS-range vectors
+ pop es
+.notlast:
+ popad
+ test al,80h
+ jnz comapi_run2_cont
+comboot_hook_nop:
+ ret
+
int21_table:
int21 00h, comboot_return
int21 01h, comboot_getkey
@@ -1021,8 +1142,15 @@ int22_table:
dw comapi_closedir ; 0022 close directory
dw comapi_shufsize ; 0023 query shuffler size
dw comapi_shufraw ; 0024 cleanup, shuffle and boot raw
+ dw comapi_setcwd ; 0025 set current working directory
+ dw comapi_mount ; 0026 read fs structures (aka mount)
+ dw comapi_run2 ; 0027 like 0003, but return on error
int22_count equ ($-int22_table)/2
+comboot_sp_save dw 0
+comboot_hook dw comboot_hook_nop
+comboot_far dd 0
+
APIKeyWait db 0
APIKeyFlag db 0
@@ -1041,8 +1169,9 @@ feature_flags_len equ ($-feature_flags)
err_notdos db ': attempted DOS system call INT ',0
err_comlarge db 'COMBOOT image too large.', CR, LF, 0
- section .bss1
+ section .bss2
alignb 4
DOSErrTramp resd 33 ; Error trampolines
+TmpDirName resb FILENAME_MAX
ConfigName resb FILENAME_MAX
CurrentDirName resb FILENAME_MAX
diff --git a/core/comboot.inc b/core/comboot.inc
index ee3fd77..a11b15b 100644
--- a/core/comboot.inc
+++ b/core/comboot.inc
@@ -1034,12 +1034,14 @@ comapi_run2:
mov [comboot_sp_save],sp ; save stack pointer
mov word [comboot_hook],comboot_hook_entry
or byte [QuietBoot],2
+ mov byte [comboot_run2_active],1
jmp load_kernel ; Run a new kernel
comapi_run2_cont:
mov word [comboot_hook],comboot_hook_nop
mov sp,[comboot_sp_save] ; fix stack pointer
and byte [QuietBoot],~2
+ mov byte [comboot_run2_active],0
clc
ret
@@ -1150,6 +1152,7 @@ int22_count equ ($-int22_table)/2
comboot_sp_save dw 0
comboot_hook dw comboot_hook_nop
comboot_far dd 0
+comboot_run2_active db 0
APIKeyWait db 0
APIKeyFlag db 0
diff --git a/core/ui.inc b/core/ui.inc
index 2bc56a1..59b5724 100644
--- a/core/ui.inc
+++ b/core/ui.inc
@@ -379,9 +379,13 @@ vk_check:
push word real_mode_seg
pop es
mov di,cmd_line_here
+ ; append line already included in this case
+ cmp byte [comboot_run2_active],0
+ jnz .no_append_copy
mov si,VKernelBuf+vk_append
mov cx,[VKernelBuf+vk_appendlen]
rep movsb
+.no_append_copy:
mov [CmdLinePtr],di ; Where to add rest of cmd
pop es
mov di,KernelName
diff --git a/core/comboot.inc b/core/comboot.inc
index a11b15b..b40c4ae 100644
--- a/core/comboot.inc
+++ b/core/comboot.inc
@@ -1067,6 +1067,7 @@ comboot_hook_entry:
push fs
push es
push ds
+ mov ecx,[KernelSize]
call far [comboot_far]
pop ds
pop es
diff --git a/core/ui.inc b/core/ui.inc
index 59b5724..02c17ff 100644
--- a/core/ui.inc
+++ b/core/ui.inc
@@ -633,6 +633,7 @@ kernel_good:
; At this point, EAX contains the size of the kernel, SI contains
; the file handle/cluster pointer, and ECX contains the extension (if any.)
;
+ mov [KernelSize],eax
movzx di,byte [KernelType]
add di,di
jmp [kerneltype_table+di]
diff --git a/doc/comboot.txt b/doc/comboot.txt
index f5fefda..f4ea31b 100644
--- a/doc/comboot.txt
+++ b/doc/comboot.txt
@@ -955,3 +955,32 @@ AX=0024h [3.80] Cleanup, shuffle and boot, raw version
with read/write data segments, matching the respective code
segment. For mode 0, B=0 and the limits will be 64K, for mode
1, B=1 and the limits will be 4 GB.
+
+
+AX=0025h [3.84] Set current working directory
+ Input: AX 00025h
+ ES:BX null-terminated directory name string
+ Output: None
+
+ Sets the current working directory. For SYSLINUX, ISOLINUX,
+ and PXELINUX, this will be an absolute path.
+
+
+AX=0026h [3.84] Read file system metadata [ISOLINUX]
+ Input: AX 00026h
+ Output: None
+
+ Reads filesystem data (e.g. after a CDROM change).
+
+
+AX=0027h [3.84] Run command
+ Input: AX 0027h
+ ES:BX null-terminated command string
+ SI comboot callback function (called via far call)
+ EDI kernel load address
+ Output: Does not return if the kernel loads correctly.
+
+ Executes the command line as if it had been entered by the user.
+ Note that this function does return (with CF = 0) if there are
+ problems or the user aborted the load. Else it terminates the
+ COMBOOT program and starts the kernel.
diff --git a/modules/Makefile b/modules/Makefile
index 80eb995..65661a4 100644
--- a/modules/Makefile
+++ b/modules/Makefile
@@ -19,6 +19,11 @@ include $(topdir)/MCONFIG.embedded
INCLUDES = -I$(com32)/include
+CFLAGS_COM = -O2 -Wall -Wno-pointer-sign -fomit-frame-pointer -m32 -march=i386 \
+ -fno-align-functions -fno-align-labels -fno-align-jumps -fno-align-loops \
+ -fno-builtin -nostdinc -I .
+ASMFLAGS_COM = -O99 -felf
+
BINS = pxechain.com gfxboot.com poweroff.com
all: $(BINS)
@@ -49,6 +54,15 @@ $(LIB): $(LIBOBJS)
%.ppm.gz: %.png
$(PNGTOPNM) $< | gzip -9 > $@
+libio.o: libio.asm
+ nasm $(ASMFLAGS_COM) -o $@ -l $*.lst $<
+
+gfxboot.o: gfxboot.c libio.h
+ $(CC) -g $(CFLAGS_COM) -c -o $@ $<
+
+gfxboot.com: gfxboot.o libio.o
+ ld -M -Tcom.ld -o $@ $^ >$*.map
+
tidy dist:
rm -f *.o *.a *.lst *.elf *.map .*.d
diff --git a/modules/com.ld b/modules/com.ld
new file mode 100644
index 0000000..a98f9aa
--- /dev/null
+++ b/modules/com.ld
@@ -0,0 +1,16 @@
+/*
+ linker script for DOS program (.COM)
+ */
+
+OUTPUT_FORMAT("binary")
+OUTPUT_ARCH(i386)
+SEARCH_DIR(".")
+ENTRY(_start)
+SECTIONS
+{
+ .init 0x100 : { *(.init) }
+ .text : { *(.text) }
+ .rodata : { *(.rodata*) }
+ .data : { *(.data) }
+ .bss : { __bss_start = .; *(.bss) }
+}
diff --git a/modules/gfxboot.c b/modules/gfxboot.c
new file mode 100644
index 0000000..6b0d300
--- /dev/null
+++ b/modules/gfxboot.c
@@ -0,0 +1,898 @@
+/*
+ *
+ * gfxboot.c
+ *
+ * A comboot program to load gfxboot graphics.
+ *
+ * It is based on work done by Sebastian Herbszt in gfxboot.asm.
+ *
+ * Copyright (c) 2009 Steffen Winterfeldt.
+ *
+ * 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, Inc., 53 Temple Place Ste 330, Boston MA
+ * 02111-1307, USA; either version 2 of the License, or (at your option) any
+ * later version; incorporated herein by reference.
+ *
+ */
+
+#include <libio.h>
+
+#define ID_SYSLINUX 0x31
+#define ID_PXELINUX 0x32
+#define ID_ISOLINUX 0x33
+#define ID_EXTLINUX 0x34
+
+#define MAX_CONFIG_LINE_LEN 2048
+#define MAX_CMDLINE_LEN 1024
+
+#define GFX_BC_INIT 0
+#define GFX_BC_DONE 1
+#define GFX_BC_INPUT 2
+#define GFX_BC_MENU_INIT 3
+#define GFX_BC_INFOBOX_INIT 4
+#define GFX_BC_INFOBOX_DONE 5
+#define GFX_BC_PROGRESS_INIT 6
+#define GFX_BC_PROGRESS_DONE 7
+#define GFX_BC_PROGRESS_UPDATE 8
+#define GFX_BC_PROGRESS_LIMIT 9 // unused
+#define GFX_BC_PASSWORD_INIT 10
+#define GFX_BC_PASSWORD_DONE 11
+
+// for now, static values
+#define MENU_LABEL_SIZE 128
+#define MENU_ARG_SIZE 512
+#define MENU_ENTRY_SIZE (MENU_LABEL_SIZE + MENU_ARG_SIZE)
+// entry 0 is reserved for the default entry
+#define MAX_MENU_ENTRIES (0x10000 / MENU_ENTRY_SIZE)
+
+
+typedef struct {
+ uint16_t handle;
+ uint16_t block_size;
+ int file_size; // file size (-1: unknown)
+ uint32_t buf; // must be block_size
+ unsigned buf_size; // in block_size units
+ unsigned data_len; // valid bytes in buf
+ unsigned buf_idx; // read pos in buffer
+} fd_t;
+
+
+// gfx config data (52 bytes)
+typedef struct __attribute__ ((packed)) {
+ uint8_t bootloader; // 0: boot loader type (0: lilo, 1: syslinux, 2: grub)
+ uint8_t sector_shift; // 1: sector shift
+ uint8_t media_type; // 2: media type (0: disk, 1: floppy, 2: cdrom)
+ uint8_t failsafe; // 3: turn on failsafe mode (bitmask)
+ // 0: SHIFT pressed
+ // 1: skip gfxboot
+ // 2: skip monitor detection
+ uint8_t sysconfig_size; // 4: size of sysconfig data
+ uint8_t boot_drive; // 5: BIOS boot drive
+ uint16_t callback; // 6: offset to callback handler
+ uint16_t bootloader_seg; // 8: code/data segment used by bootloader; must follow gfx_callback
+ uint16_t reserved_1; // 10
+ uint32_t user_info_0; // 12: data for info box
+ uint32_t user_info_1; // 16: data for info box
+ uint32_t bios_mem_size; // 20: BIOS memory size (in bytes)
+ uint16_t xmem_0; // 24: extended mem area 0 (start:size in MB; 12:4 bits)
+ uint16_t xmem_1; // 26: extended mem area 1
+ uint16_t xmem_2; // 28: extended mem area 2
+ uint16_t xmem_3; // 30: extended mem area 3
+ uint32_t file; // 32: start of gfx file
+ uint32_t archive_start; // 36: start of cpio archive
+ uint32_t archive_end; // 40: end of cpio archive
+ uint32_t mem0_start; // 44: low free memory start
+ uint32_t mem0_end; // 48: low free memory end
+} gfx_config_t;
+
+
+// menu description (18 bytes)
+typedef struct __attribute__ ((packed)) {
+ uint16_t entries;
+ uint32_t default_entry; // seg:ofs
+ uint32_t label_list; // seg:ofs
+ uint16_t label_size;
+ uint32_t arg_list; // seg:ofs
+ uint16_t arg_size;
+} menu_t;
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// global file descriptor, implicitly used by read(), getc(), fgets()
+fd_t fd;
+
+gfx_config_t gfx_config;
+menu_t menu;
+
+struct {
+ uint32_t jmp_table[12];
+ uint16_t code_seg;
+ char fname_buf[64];
+} gfx;
+
+unsigned comboot, comboot_len;
+unsigned io_buf, io_buf_len;
+unsigned menu_buf, menu_buf_len;
+unsigned freemem, freemem_len;
+
+int timeout;
+
+char cmdline[MAX_CMDLINE_LEN];
+char current_label[64];
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+int open(char *name);
+int open32(uint32_t name);
+int read(void *buf, int size);
+int read32(uint32_t buf, int size);
+int getc(void);
+char *fgets(char *s, int size);
+
+int strlen(char *s);
+int strcmp(char *s1, char *s2);
+char *skip_spaces(char *s);
+char *skip_nonspaces(char *s);
+void chop_line(char *s);
+
+int atoi(char *s);
+
+uint32_t get_config_file_name32(void);
+int read_config_file(void);
+
+unsigned magic_ok(uint32_t buf);
+unsigned find_file(uint32_t buf, unsigned len, unsigned *gfx_file_start, unsigned *file_len);
+
+int gfx_init(char *file);
+void gfx_done(void);
+int gfx_input(void);
+int gfx_menu_init(void);
+
+void gfx_infobox32(int type, uint32_t str1, uint32_t str2);
+void gfx_infobox(int type, char *str1, char *str2);
+
+void boot(void);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int main(int argc, char **argv)
+{
+ x86regs_t r;
+ uint8_t syslinux_id;
+ int menu_index;
+
+ r.eax = 0x0a; // Get Derivative-Specific Information
+ r.ecx = 9;
+ x86int(0x22, &r);
+ syslinux_id = (uint8_t) r.eax;
+ gfx_config.sector_shift = (uint8_t) r.ecx;
+ gfx_config.boot_drive = (uint8_t) r.edx;
+
+ // define our memory layout
+ // all must be at least 16 bytes aligned
+
+ // 64k comboot code
+ comboot = comboot_seg() << 4;
+ comboot_len = 0x10000;
+
+ // 16k file io buffer
+ io_buf = comboot + comboot_len;
+ io_buf_len = 0x4000;
+
+ // 64k for parsed menu data
+ menu_buf = io_buf + io_buf_len;
+ menu_buf_len = 0x10000;
+
+ // use remaining mem for gfx core
+ freemem = menu_buf + menu_buf_len;
+ // comboot api passes low memory end at address 2
+ freemem_len = ((*(uint16_t *) 2) << 4) - freemem;
+
+
+ gfx_config.bootloader = 1;
+ gfx_config.sysconfig_size = sizeof gfx_config;
+ gfx_config.bootloader_seg = comboot >> 4;
+
+ // not gfx_cb() directly, we need a wrapper
+ gfx_config.callback = (uint32_t) _gfx_cb;
+
+ if(syslinux_id == ID_PXELINUX) {
+ gfx_config.sector_shift = 11;
+ gfx_config.boot_drive = 0;
+ }
+
+ if(argc != 2) {
+ printf("Usage: gfxboot.com <bootlogo>\n");
+ return 0;
+ }
+
+ if(read_config_file()) {
+ printf("Error reading config file\n");
+ return 0;
+ }
+
+ if(gfx_init(argv[1])) {
+ printf("Error setting up gfxboot\n");
+ return 0;
+ }
+
+ gfx_menu_init();
+
+ for(;;) {
+ menu_index = gfx_input();
+
+ // abort gfx, return to text mode prompt
+ if(menu_index == -1) {
+ gfx_done();
+ break;
+ }
+
+ // get label name, is needed later for messages etc.
+ memcpy(current_label, cmdline, sizeof current_label);
+ current_label[sizeof current_label - 1] = 0;
+ *skip_nonspaces(current_label) = 0;
+
+ // does not return if it succeeds
+ boot();
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+// 0: ok, -1: error
+//
+int open32(uint32_t name)
+{
+ x86regs_t r;
+
+ fd.handle = 0;
+ fd.data_len = fd.buf_idx = 0;
+
+ r.esi = name & 0xf;
+ r.eax = 0x06; // Open file
+ r.es = name >> 4;
+ x86int(0x22, &r);
+
+ fd.block_size = r.ecx;
+ fd.file_size = r.eax;
+
+ if(
+ (r.eflags & X86_CF) ||
+ !fd.file_size || !fd.block_size || fd.block_size > io_buf_len
+ ) return -1;
+
+ fd.handle = r.esi;
+
+ fd.buf = io_buf;
+ fd.buf_size = io_buf_len / fd.block_size;
+
+ // printf("block size = 0x%x, file size = %d\n", fd.block_size, fd.file_size);
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+// 0: ok, -1: error
+//
+int open(char *name)
+{
+ return open32((uint32_t) name + comboot);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Note: buf is 32bit address
+//
+int read32(uint32_t buf, int size)
+{
+ x86regs_t r;
+ int i, len = 0;
+
+ while(size) {
+ i = fd.data_len - fd.buf_idx;
+
+ if(i > 0) {
+ i = i < size ? i : size;
+ memcpy32(buf, fd.buf + fd.buf_idx, i);
+ len += i;
+ buf += i;
+ size -= i;
+ fd.buf_idx += i;
+ }
+
+ if(!size || !fd.handle) break;
+
+ r.eax = 0x07; // Read file
+ r.ecx = fd.buf_size;
+ r.ebx = 0;
+ r.es = fd.buf >> 4;
+ r.esi = fd.handle;
+ x86int(0x22, &r);
+ fd.handle = r.esi;
+ fd.data_len = r.ecx;
+ fd.buf_idx = 0;
+ }
+
+ return len;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int read(void *buf, int size)
+{
+ return read32((uint32_t) buf + comboot, size);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int getc()
+{
+ char buf[1];
+
+ return read(buf, 1) ? *buf : EOF;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *fgets(char *s, int size)
+{
+ char *buf = s;
+ int c = EOF;
+
+ while(size--) {
+ c = getc();
+ if(c == EOF) break;
+ *buf++ = c;
+ if(c == 0x0a) break;
+ }
+
+ *buf = 0;
+
+ return c == EOF && s == buf ? 0 : s;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int strlen(char *s)
+{
+ char *s0 = s + 1;
+
+ while(*s++);
+
+ return s - s0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int strcmp(char *s1, char *s2)
+{
+ while(*s1 && *s1 == *s2) s1++, s2++;
+
+ return (uint8_t) *s1 - (uint8_t) *s2;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *skip_spaces(char *s)
+{
+ while(*s && (*s == ' ' || *s == '\t')) s++;
+
+ return s;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *skip_nonspaces(char *s)
+{
+ while(*s && *s != ' ' && *s != '\t') s++;
+
+ return s;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void chop_line(char *s)
+{
+ int i = strlen(s);
+
+ if(!i) return;
+
+ while(--i >= 0) {
+ if(s[i] == ' ' || s[i] == '\t' || s[i] == '\n') {
+ s[i] = 0;
+ }
+ else {
+ break;
+ }
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int atoi(char *s)
+{
+ int i = 0, sign = 1;
+
+ if(*s == '-') s++, sign = -1;
+ if(*s == '+') s++;
+
+ while(*s >= '0' && *s <= '9') {
+ i = 10 * i + *s++ - '0';
+ }
+
+ return sign * i;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+// config file name (32 bit address)
+//
+uint32_t get_config_file_name32()
+{
+ x86regs_t r;
+
+ r.eax = 0x0e; // Get configuration file name
+ x86int(0x22, &r);
+
+ return (r.es << 4) + (uint16_t) r.ebx;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Read and parse syslinux config file.
+//
+// return:
+// 0: ok, 1: error
+//
+int read_config_file(void)
+{
+ char *s, *t, buf[MAX_CONFIG_LINE_LEN];
+ unsigned u;
+ int menu_idx = 0;
+
+ // clear memory before we start
+ memset32(menu_buf, 0, menu_buf_len);
+
+ if(open32(get_config_file_name32()) == -1) return 1;
+
+ while((s = fgets(buf, sizeof buf))) {
+ chop_line(s);
+ s = skip_spaces(s);
+ if(!*s || *s == '#') continue;
+ t = skip_nonspaces(s);
+ if(*t) *t++ = 0;
+ t = skip_spaces(t);
+
+ if(!strcmp(s, "timeout")) {
+ timeout = atoi(t);
+ }
+
+ if(!strcmp(s, "default")) {
+ u = strlen(t);
+ if(u > MENU_LABEL_SIZE - 1) u = MENU_LABEL_SIZE - 1;
+ memcpy32(menu_buf, comboot + (uint32_t) t, u);
+ }
+
+ if(!strcmp(s, "label")) {
+ menu_idx++;
+ if(menu_idx < MAX_MENU_ENTRIES) {
+ u = strlen(t);
+ if(u > MENU_LABEL_SIZE - 1) u = MENU_LABEL_SIZE - 1;
+ memcpy32(
+ menu_buf + menu_idx * MENU_LABEL_SIZE,
+ comboot + (uint32_t) t,
+ u
+ );
+ }
+ }
+
+ if(!strcmp(s, "append")) {
+ if(menu_idx < MAX_MENU_ENTRIES) {
+ u = strlen(t);
+ if(u > MENU_ARG_SIZE - 1) u = MENU_ARG_SIZE - 1;
+ memcpy32(
+ menu_buf + menu_idx * MENU_ARG_SIZE + MAX_MENU_ENTRIES * MENU_LABEL_SIZE,
+ comboot + (uint32_t) t,
+ u
+ );
+ }
+ }
+ }
+
+ menu.entries = menu_idx;
+ menu.label_size = MENU_LABEL_SIZE;
+ menu.arg_size = MENU_ARG_SIZE;
+ menu.default_entry = ((menu_buf >> 4) << 16) + (menu_buf & 0xf);
+ u = menu_buf + MENU_LABEL_SIZE;
+ menu.label_list = ((u >> 4) << 16) + (u & 0xf);
+ u = menu_buf + MAX_MENU_ENTRIES * MENU_LABEL_SIZE + MENU_ARG_SIZE;
+ menu.arg_list = ((u >> 4) << 16) + (u & 0xf);
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Check header and return code start offset.
+//
+// Note: buf is 32bit address
+//
+unsigned magic_ok(uint32_t buf)
+{
+ if(
+ _mem32(buf) == 0x0b2d97f00 && /* magic id */
+ (_mem8(buf + 4) == 8) /* version 8 */
+ ) {
+ return _mem32(buf + 8);
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Search cpio archive for gfx file.
+//
+// Note: buf is 32bit address
+//
+unsigned find_file(uint32_t buf, unsigned len, unsigned *gfx_file_start, unsigned *file_len)
+{
+ unsigned i, fname_len, code_start = 0;
+
+ *gfx_file_start = 0;
+
+ for(i = 0; i < len;) {
+ if((len - i) >= 0x1a && _mem16(buf + i) == 0x71c7) {
+ fname_len = _mem16(buf + i + 20);
+ *file_len = _mem16(buf + i + 24) + (_mem16(buf + i + 22) << 16);
+ i += 26 + fname_len;
+ i = ((i + 1) & ~1);
+ if((code_start = magic_ok(buf + i))) {
+ *gfx_file_start = i;
+ return code_start;
+ }
+ i += *file_len;
+ i = ((i + 1) & ~1);
+ }
+ else {
+ break;
+ }
+ }
+
+ return code_start;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_cb(x86regs_t *r)
+{
+ uint8_t f_nr = r->eax;
+
+ switch(f_nr) {
+ case 0: // cb_status
+ // edx = filename buffer (64 bytes)
+ r->edx = comboot + (uint32_t) gfx.fname_buf;
+ r->eax = 0;
+ // printf("<0x%x, %p + 0x%x>", r->edx, gfx.fname_buf, comboot);
+ break;
+
+ case 1: // cb_fopen
+ // file name in gfx.fname_buf
+ // al = 0: ok, 1: file not found
+ // ecx = file length (al = 0)
+ if(open(gfx.fname_buf) == -1) {
+ r->eax = 1;
+ }
+ else {
+ r->eax = 0;
+ r->ecx = fd.file_size;
+ }
+ break;
+
+ case 2: // cb_fread
+ // edx = buffer address (linear)
+ // ecx = data length (< 64k)
+ if(!fd.handle) {
+ r->eax = r->ecx = 0;
+ break;
+ }
+ r->esi = fd.handle;
+ r->ebx = 0;
+ r->es = io_buf >> 4;
+ r->ecx = io_buf_len / fd.block_size;
+ r->eax = 7;
+ x86int(0x22, r);
+ fd.handle = r->esi;
+ if((r->eflags & X86_CF)) {
+ r->eax = 1;
+ }
+ else {
+ r->edx = io_buf;
+ r->eax = 0;
+ }
+ break;
+
+ case 3: // cb_getcwd
+ // edx filename
+ r->eax = 0x1f; // Get current working directory
+ x86int(0x22, r);
+ r->edx = (r->es << 4) + (uint16_t) r->ebx;
+ r->eax = 0;
+ break;
+
+ case 4: // cb_chdir
+ r->es = comboot >> 4;
+ r->ebx = (uint32_t) gfx.fname_buf;
+ r->eax = 0x25; // Get current working directory
+ x86int(0x22, r);
+ break;
+
+ case 5: // cb_readsector
+ // in: edx = sector
+ // out: edx = buffer (linear address)
+ r->esi = r->edi = 0;
+ r->ebx = 0;
+ r->es = io_buf >> 4;
+ r->ecx = 1;
+ r->eax = 0x19; // Read disk
+ x86int(0x22, r);
+ r->eax = 0;
+ r->edx = io_buf;
+ break;
+
+ case 6: // cb_mount
+ r->eax = 0x26;
+ x86int(0x22, r);
+ r->eax = (r->eflags & X86_CF) ? 1 : 0;
+ break;
+
+ default:
+ r->eax = 0xff;
+ }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+// 0: ok, 1: error
+//
+int gfx_init(char *file)
+{
+ x86regs_t r;
+
+ int file_max, file_size, ofs;
+ unsigned u, code_start, file_start = 0, file_len = 0;
+
+ gfx_config.mem0_start = freemem;
+ gfx_config.mem0_end = freemem + freemem_len;
+
+ gfx_config.bios_mem_size = 256 << 20; // fake
+
+ gfx_config.xmem_0 = 0x17; // 7MB starting at 16MB
+
+#if 0
+ printf("mem = 0x%05x, mem0_start = 0x%05x, mem0_end = 0x%05x\n",
+ gfx.mem, gfx_config.mem0_start, gfx_config.mem0_end
+ );
+#endif
+
+ if(open(file) == -1) return 1;
+
+ // leave room for later alignment
+ gfx_config.archive_start = gfx_config.mem0_start + 0x10;
+
+ // read at most that much
+ file_size = file_max = gfx_config.mem0_end - gfx_config.archive_start;
+
+ if(fd.file_size != -1 && fd.file_size > file_size) return 1;
+
+ // if we have the real size, use it
+ if(fd.file_size != -1) file_size = fd.file_size;
+
+ file_size = read32(gfx_config.archive_start, file_size);
+
+ if(!file_size || file_size == file_max) return 1;
+
+ gfx_config.archive_end = gfx_config.archive_start + file_size;
+
+ // update free mem pointer & align it a bit
+ gfx_config.mem0_start = (gfx_config.archive_end + 3) & ~3;
+
+ // locate file inside cpio archive
+ if(!(code_start = find_file(gfx_config.archive_start, file_size, &file_start, &file_len))) {
+ printf("%s: invalid file format\n", file);
+
+ return 1;
+ }
+
+#if 0
+ printf("code_start = 0x%x, archive_start = 0x%x, file size = 0x%x, file_start = 0x%x, file_len = 0x%x\n",
+ code_start, gfx_config.archive_start, file_size, file_start, file_len
+ );
+#endif
+
+ if((ofs = (gfx_config.archive_start + file_start + code_start) & 0xf)) {
+ printf("oops: needs to be aligned!\n");
+
+ memcpy32(gfx_config.archive_start - ofs, gfx_config.archive_start, file_size);
+ gfx_config.archive_start -= ofs;
+ gfx_config.archive_end -= ofs;
+
+ return 1;
+ }
+
+ gfx_config.file = gfx_config.archive_start + file_start;
+ gfx.code_seg = (gfx_config.file + code_start) >> 4;
+
+ for(u = 0; u < sizeof gfx.jmp_table / sizeof *gfx.jmp_table; u++) {
+ gfx.jmp_table[u] = (gfx.code_seg << 16) + _mem16(gfx_config.file + code_start + 2 * u);
+ }
+
+#if 0
+ for(u = 0; u < sizeof gfx.jmp_table / sizeof *gfx.jmp_table; u++) {
+ printf("%d: 0x%08x\n", u, gfx.jmp_table[u]);
+ }
+#endif
+
+ // we are ready to start
+
+ r.esi = comboot + (uint32_t) &gfx_config;
+ farcall(gfx.jmp_table[GFX_BC_INIT], &r);
+
+ if((r.eflags & X86_CF)) {
+ printf("graphics initialization failed\n");
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_done()
+{
+ x86regs_t r;
+
+ farcall(gfx.jmp_table[GFX_BC_DONE], &r);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+// boot menu index (-1: go to text mode prompt)
+//
+int gfx_input()
+{
+ x86regs_t r;
+
+ r.edi = comboot + (uint32_t) cmdline;
+ r.ecx = sizeof cmdline;
+ r.eax = timeout;
+ timeout = 0; // use timeout only first time
+ farcall(gfx.jmp_table[GFX_BC_INPUT], &r);
+ if((r.eflags & X86_CF)) r.eax = 1;
+
+ if(r.eax == 1) return -1;
+
+ return r.ebx;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_menu_init()
+{
+ x86regs_t r;
+
+ r.esi = comboot + (uint32_t) &menu;
+ farcall(gfx.jmp_table[GFX_BC_MENU_INIT], &r);
+
+ return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_infobox(int type, char *str1, char *str2)
+{
+ gfx_infobox32(type, comboot + (uint32_t) str1, str2 ? comboot + (uint32_t) str2 : 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_infobox32(int type, uint32_t str1, uint32_t str2)
+{
+ x86regs_t r;
+
+ r.eax = type;
+ r.esi = str1;
+ r.edi = str2;
+ farcall(gfx.jmp_table[GFX_BC_INFOBOX_INIT], &r);
+ r.edi = r.eax = 0;
+ farcall(gfx.jmp_table[GFX_BC_INPUT], &r);
+ farcall(gfx.jmp_table[GFX_BC_INFOBOX_DONE], &r);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Load & run kernel.
+//
+// Returns only on error.
+//
+void boot()
+{
+ x86regs_t r;
+
+ r.es = comboot >> 4;
+ r.ebx = (uint32_t) cmdline;
+ r.edi = 8 << 20; // kernel load address: 8MB
+ r.esi = (uint32_t) _syslinux_hook; // cs:si = syslinux callbacks
+ r.eax = 0x27; // Load & run kernel (extended API)
+ x86int(0x22, &r);
+ if(!(r.eflags & X86_CF)) return;
+
+ r.es = comboot >> 4;
+ r.ebx = (uint32_t) cmdline;
+ r.eax = 3; // Run command
+ x86int(0x22, &r);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void syslinux_hook(x86regs_t *r)
+{
+ uint8_t f_nr = r->eax;
+
+ // bit 7: 0/1 continue/don't continue kernel loading in syslinux
+
+ switch(f_nr & 0x7f) {
+ case 0: // abort kernel/initrd loading
+ gfx_infobox(0, "abort kernel loading", 0);
+ break;
+
+ case 1: // kernel/initrd not found
+ gfx_infobox(0, "kernel not found: ", current_label);
+ break;
+
+ case 2: // kernel corrupt
+ gfx_infobox(0, "kernel broken", 0);
+ break;
+
+ case 3: // out of memory (while initrd loading)
+ gfx_infobox(0, "out of memory", 0);
+ break;
+
+ case 4: // progress start
+ r->eax = r->ecx >> gfx_config.sector_shift; // kernel size in sectors
+ r->eax = 0;
+ r->esi = comboot + (uint32_t) current_label;
+ farcall(gfx.jmp_table[GFX_BC_PROGRESS_INIT], r);
+ break;
+
+ case 5: // progress increment
+ // always 64k
+ r->eax = 0x10000 >> gfx_config.sector_shift;
+ farcall(gfx.jmp_table[GFX_BC_PROGRESS_UPDATE], r);
+ break;
+
+ case 6: // progress end: kernel loaded, stop gfxboot
+ farcall(gfx.jmp_table[GFX_BC_PROGRESS_DONE], r);
+ gfx_done();
+ break;
+ }
+}
+
+
diff --git a/modules/libio.asm b/modules/libio.asm
new file mode 100644
index 0000000..57bcde3
--- /dev/null
+++ b/modules/libio.asm
@@ -0,0 +1,854 @@
+;
+; libio.asm
+;
+; A very minimalistic libc fragment.
+;
+; Copyright (c) 2009 Steffen Winterfeldt.
+;
+; 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, Inc., 53 Temple Place Ste 330, Boston MA 02111-1307,
+; USA; either version 2 of the License, or (at your option) any later
+; version; incorporated herein by reference.
+;
+
+
+; max argv elements passed to main()
+%define MAX_ARGS 8
+
+
+ bits 16
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; interface functions
+;
+; Make sure not to modify registers!
+;
+
+ global printf
+ global getchar
+ global clrscr
+ global memcpy
+ global memcpy32
+ global memset
+ global memset32
+ global x86int
+ global farcall
+ global reboot
+
+ global _gfx_cb
+ extern gfx_cb
+ global _syslinux_hook
+ extern syslinux_hook
+
+ global _start
+ extern _main
+
+ extern __bss_start
+
+ section .init
+
+_start:
+ cld
+
+ ; clear static memory
+ mov di,__bss_start
+ mov cx,sp
+ sub cx,di
+ xor ax,ax
+ rep stosb
+
+ ; parse args
+ mov bx,80h
+ movzx si,byte [bx]
+ mov byte [si+81h],0dh ; just make sure
+ xor ecx,ecx
+ sub sp,MAX_ARGS * 4
+ mov ebp,esp
+ inc cx
+cmd_10:
+ inc bx
+ call skip_spaces
+ cmp al,0dh
+ jz cmd_60
+ imul si,cx,4
+ mov [bp+si],ebx
+ call skip_nonspaces
+ mov byte [bx],0
+ inc cx
+ cmp cx,MAX_ARGS
+ jae cmd_60
+ cmp al,0dh
+ jnz cmd_10
+cmd_60:
+ mov byte [bx],0
+
+ mov [bp],ebx ; argv[0] = ""
+
+ push ebp
+ push ecx
+
+ call dword _main
+
+ add sp,MAX_ARGS * 4 + 8
+
+ ret
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+skip_spaces:
+ mov al,[bx]
+ cmp al,0dh
+ jz skip_spaces_90
+ cmp al,' '
+ jz skip_spaces_10
+ cmp al,9
+ jnz skip_spaces_90
+skip_spaces_10:
+ inc bx
+ jmp skip_spaces
+skip_spaces_90:
+ ret
+
+skip_nonspaces:
+ mov al,[bx]
+ cmp al,0dh
+ jz skip_nonspaces_90
+ cmp al,' '
+ jz skip_nonspaces_90
+ cmp al,9
+ jz skip_nonspaces_90
+ inc bx
+ jmp skip_nonspaces
+skip_nonspaces_90:
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ section .text
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Write text to console.
+;
+; args on stack
+;
+; Note: 32 bit call/ret!
+;
+printf:
+ mov [pf_args],sp
+
+ pushad
+
+ call pf_next_arg
+ call pf_next_arg
+ mov si,ax
+printf_10:
+ lodsb
+ or al,al
+ jz printf_90
+ cmp al,'%'
+ jnz printf_70
+ mov byte [pf_pad],' '
+ lodsb
+ dec si
+ cmp al,'0'
+ jnz printf_20
+ mov [pf_pad],al
+printf_20:
+ call get_number
+ mov [pf_num],ecx
+ lodsb
+ or al,al
+ jz printf_90
+ cmp al,'%'
+ jz printf_70
+
+ cmp al,'S'
+ jnz printf_23
+ mov byte [pf_raw_char],1
+ jmp printf_24
+printf_23:
+ cmp al,'s'
+ jnz printf_30
+printf_24:
+ push si
+
+ call pf_next_arg
+ mov si,ax
+ call puts
+
+ sub ecx,[pf_num]
+ neg ecx
+ mov al,' '
+ call putc_n
+
+ pop si
+
+ mov byte [pf_raw_char],0
+ jmp printf_10
+
+printf_30:
+ cmp al,'u'
+ jnz printf_35
+
+ mov dx,10
+printf_31:
+ push si
+
+ call pf_next_arg
+ or dh,dh
+ jz printf_34
+ test eax,eax
+ jns printf_34
+ neg eax
+ push eax
+ mov al,'-'
+ call putc
+ pop eax
+printf_34:
+ mov cl,[pf_num]
+ mov ch,[pf_pad]
+ call number
+ call puts
+
+ pop si
+
+ jmp printf_10
+
+printf_35:
+ cmp al,'x'
+ jnz printf_36
+
+printf_35a:
+ mov dx,10h
+ jmp printf_31
+
+printf_36:
+ cmp al,'d'
+ jnz printf_37
+printf_36a:
+ mov dx,10ah
+ jmp printf_31
+
+printf_37:
+ cmp al,'i'
+ jz printf_36a
+
+ cmp al,'p'
+ jnz printf_40
+ mov al,'0'
+ call putc
+ mov al,'x'
+ call putc
+ jmp printf_35a
+
+printf_40:
+ cmp al,'c'
+ jnz printf_45
+
+ push si
+ call pf_next_arg
+ call putc
+ pop si
+ jmp printf_10
+printf_45:
+
+ ; more ...
+
+
+printf_70:
+ call putc
+ jmp printf_10
+printf_90:
+ popad
+
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Get next printf arg from [pf_args].
+;
+; return:
+; eax arg
+;
+; changes no regs
+;
+pf_next_arg:
+ movzx eax,word [pf_args]
+ add word [pf_args],4
+ mov eax,[eax]
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Convert string to number.
+;
+; si string
+;
+; return:
+; ecx number
+; si points past number
+; CF not a number
+;
+get_number:
+
+ xor ecx,ecx
+ mov ah,1
+get_number_10:
+ lodsb
+ or al,al
+ jz get_number_90
+ sub al,'0'
+ jb get_number_90
+ cmp al,9
+ ja get_number_90
+ movzx eax,al
+ imul ecx,ecx,10
+ add ecx,eax
+ jmp get_number_10
+get_number_90:
+ dec si
+ shr ah,1
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Convert a number to string.
+;
+; eax number
+; cl field size
+; ch padding char
+; dl base
+;
+; return:
+; si string
+;
+number:
+ mov di,num_buf
+ push ax
+ push cx
+ mov al,ch
+ mov cx,num_buf_end - num_buf
+ rep stosb
+ pop cx
+ pop ax
+ movzx cx,cl
+ movzx ebx,dl
+number_10:
+ xor edx,edx
+ div ebx
+ cmp dl,9
+ jbe number_20
+ add dl,27h
+number_20:
+ add dl,'0'
+ dec edi
+ mov [di],dl
+ or eax,eax
+ jz number_30
+ cmp di,num_buf
+ ja number_10
+number_30:
+ mov si,di
+ or cx,cx
+ jz number_90
+ cmp cx,num_buf_end - num_buf
+ jae number_90
+ mov si,num_buf_end
+ sub si,cx
+number_90:
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Write string.
+;
+; si text
+;
+; return:
+; cx length
+;
+puts:
+ xor cx,cx
+puts_10:
+ lodsb
+ or al,al
+ jz puts_90
+ call putc
+ inc cx
+ jmp puts_10
+puts_90:
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Write char multiple times.
+;
+; al char
+; cx count (does nothing if count <= 0)
+;
+putc_n:
+ cmp cx,0
+ jle putc_n_90
+ call putc
+ dec cx
+ jmp putc_n
+putc_n_90:
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Print char.
+;
+; al char
+;
+putc:
+ pusha
+ cmp al,0ah
+ jnz putc_30
+ push ax
+ mov al,0dh
+ call putc_50
+ pop ax
+putc_30:
+ call putc_50
+ popa
+ ret
+putc_50:
+ mov bx,7
+ mov ah,0eh
+ int 10h
+ ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Read char from stdin.
+;
+; return:
+; eax char
+;
+; Note: 32 bit call/ret!
+;
+getchar:
+ pushad
+ mov ah,10h
+ int 16h
+ mov [gc_tmp],al
+ popad
+ movzx eax,byte [gc_tmp]
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Clear screen.
+;
+; Note: 32 bit call/ret!
+;
+clrscr:
+ pushad
+ push es
+ push word 40h
+ pop es
+ mov ax,600h
+ mov bh,7
+ xor cx,cx
+ mov dl,[es:4ah]
+ or dl,dl
+ jnz clrscr_20
+ mov dl,80
+clrscr_20:
+ dec dl
+ mov dh,[es:84h]
+ or dh,dh
+ jnz clrscr_30
+ mov dh,24
+clrscr_30:
+ int 10h
+ mov ah,2
+ mov bh,[es:62h]
+ xor dx,dx
+ int 10h
+ pop es
+ popad
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memcpy(dst, src, size).
+;
+; args on stack
+;
+; return:
+; eax dst
+;
+; Note: 32 bit call/ret!
+;
+memcpy:
+ pushad
+
+ mov edi,[esp+0x20+4]
+ mov esi,[esp+0x20+8]
+ mov ecx,[esp+0x20+12]
+
+ rep movsb
+
+ popad
+
+ mov eax,[esp+4]
+
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memset(dst, val, size).
+;
+; args on stack
+;
+; return:
+; eax dst
+;
+; Note: 32 bit call/ret!
+;
+memset:
+ pushad
+
+ mov edi,[esp+0x20+4]
+ mov al,[esp+0x20+8]
+ mov ecx,[esp+0x20+12]
+
+ rep stosb
+
+ popad
+
+ mov eax,[esp+4]
+
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memset32(dst, val, size).
+;
+; args on stack
+;
+; return:
+; eax dst
+;
+; Note: 32 bit call/ret!
+;
+memset32:
+ pushad
+
+ push es
+
+ mov ebx,[esp+0x22+4] ; edi
+ mov al,[esp+0x22+8]
+ mov edx,[esp+0x22+12]
+
+memset32_20:
+ mov edi,ebx
+ mov ecx,ebx
+ and di,0fh
+ shr ecx,4
+ mov es,cx
+
+ mov ecx,0fff0h
+ cmp edx,ecx
+ ja memset32_40
+ mov ecx,edx
+memset32_40:
+ add ebx,ecx
+ sub edx,ecx
+
+ rep stosb
+
+ jnz memset32_20
+
+ pop es
+
+ popad
+
+ mov eax,[esp+4]
+
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memcpy32(dst, src, size).
+;
+; dst, src are 32bit linear addresses
+;
+; args on stack
+;
+; return:
+; eax dst
+;
+; Note: 32 bit call/ret!
+;
+memcpy32:
+ pushad
+
+ push ds
+ push es
+
+ mov ebx,[esp+0x24+4] ; edi
+ mov eax,[esp+0x24+8] ; esi
+ mov edx,[esp+0x24+12]
+
+memcpy32_20:
+ mov edi,ebx
+ mov ecx,ebx
+ and di,0fh
+ shr ecx,4
+ mov es,cx
+
+ mov esi,eax
+ mov ecx,eax
+ and si,0fh
+ shr ecx,4
+ mov ds,cx
+
+ mov ecx,0fff0h
+ cmp edx,ecx
+ ja memcpy32_40
+ mov ecx,edx
+memcpy32_40:
+ add ebx,ecx
+ add eax,ecx
+ sub edx,ecx
+
+ rep movsb
+
+ jnz memcpy32_20
+
+ pop es
+ pop ds
+
+ popad
+
+ mov eax,[esp+4]
+
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; x86int(int, *regs).
+;
+; args on stack
+;
+; Note: 32 bit call/ret!
+;
+x86int:
+ pushad
+
+ mov al,[esp+0x20+4]
+ mov [x86int_p],al
+ mov ebx,[esp+0x20+8]
+
+ mov ecx,[bx+8]
+ mov edx,[bx+0ch]
+ mov esi,[bx+10h]
+ mov edi,[bx+14h]
+ mov ebp,[bx+18h]
+ mov ah,[bx+1ch] ; eflags
+ sahf
+ mov eax,[bx]
+
+ mov es,[bx+22h]
+ mov fs,[bx+24h]
+ mov gs,[bx+26h]
+ mov ds,[bx+20h]
+
+ mov ebx,[cs:bx+4]
+
+ int 0h
+x86int_p equ $-1
+
+ push ebx
+ mov ebx,[esp+0x24+8]
+ pop dword [cs:bx+4]
+
+ mov [cs:bx],eax
+ mov [cs:bx+20h],ds
+
+ mov ax,cs
+ mov ds,ax
+
+ mov [cs:bx+22h],es
+ mov [cs:bx+24h],fs
+ mov [cs:bx+26h],gs
+
+ mov es,ax
+ mov fs,ax
+ mov gs,ax
+
+ mov [bx+8],ecx
+ mov [bx+0ch],edx
+ mov [bx+10h],esi
+ mov [bx+14h],edi
+ mov [bx+18h],ebp
+ pushfd
+ pop dword [bx+1ch]
+
+ popad
+
+ o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; farcall(addr, *regs).
+;
+; args on stack
+;
+; Note: 32 bit call/ret!
+;
+farcall:
+ pushad
+
+ mov ebx,[esp+0x20+8]
+
+ mov ecx,[bx+8]
+ mov edx,[bx+0ch]
+ mov esi,[bx+10h]
+ mov edi,[bx+14h]
+ mov ebp,[bx+18h]
+ mov ah,[bx+1ch] ; eflags
+ sahf
+ mov eax,[bx]
+
+ mov [farcall_stack],sp
+ sub word [farcall_stack],1000h ; 4k stack should be enough for gfxboot
+ mov [farcall_stack+2],ss
+
+ mov es,[bx+22h]
+ mov fs,[bx+24h]
+ mov gs,[bx+26h]
+ mov ds,[bx+20h]
+
+ mov ebx,[cs:bx+4]
+
+ call far [esp+0x20+4]
+
+ push ebx
+ mov ebx,[esp+0x24+8]
+ pop dword [cs:bx+4]
+
+ mov [cs:bx],eax
+ mov [cs:bx+20h],ds
+
+ mov ax,cs
+ mov ds,ax
+
+ mov [cs:bx+22h],es
+ mov [cs:bx+24h],fs
+ mov [cs:bx+26h],gs
+
+ mov es,ax
+ mov fs,ax
+ mov gs,ax
+
+ mov [bx+8],ecx
+ mov [bx+0ch],edx
+ mov [bx+10h],esi
+ mov [bx+14h],edi
+ mov [bx+18h],ebp
+ pushfd
+ pop dword [bx+1ch]
+
+ popad
+
+ o32 ret
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; wrapper around gfx_cb()
+;
+; we need to switch stack to ensure ss = cs = ds = es for gcc
+;
+_gfx_cb:
+ push cs
+ pop ds
+ push cs
+ pop es
+ mov [cb_stack],sp
+ mov [cb_stack+2],ss
+ lss sp,[farcall_stack]
+ sub sp,28h ; sizeof x86regs_t
+ mov [esp+18h],ebp
+ mov ebp,esp
+ push ebp
+ mov [bp],eax
+ mov [bp+4],ebx
+ mov [bp+8],ecx
+ mov [bp+0ch],edx
+ mov [bp+10h],esi
+ mov [bp+14h],edi
+ call dword gfx_cb
+ lea ebp,[esp+4]
+ mov eax,[bp]
+ mov ebx,[bp+4]
+ mov ecx,[bp+8]
+ mov edx,[bp+0ch]
+ mov esi,[bp+10h]
+ mov edi,[bp+14h]
+ mov ebp,[bp+18h]
+ lss sp,[cb_stack]
+ retf
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; wrapper around syslinux_hook()
+;
+; ensure cs = ds = es for gcc
+;
+_syslinux_hook:
+ push cs
+ pop ds
+ push cs
+ pop es
+ sub sp,28h ; sizeof x86regs_t
+ mov [esp+18h],ebp
+ mov ebp,esp
+ push ebp
+ mov [bp],eax
+ mov [bp+4],ebx
+ mov [bp+8],ecx
+ mov [bp+0ch],edx
+ mov [bp+10h],esi
+ mov [bp+14h],edi
+ call dword syslinux_hook
+ lea ebp,[esp+4]
+ mov eax,[bp]
+ mov ebx,[bp+4]
+ mov ecx,[bp+8]
+ mov edx,[bp+0ch]
+ mov esi,[bp+10h]
+ mov edi,[bp+14h]
+ mov ebp,[bp+18h]
+ add sp,28h+4
+ retf
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+reboot:
+ mov word [472h],1234h
+ jmp 0ffffh:0
+ int 19h
+ jmp $
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ section .data
+
+farcall_stack dd 0
+cb_stack dd 0
+
+; buffer for number conversions
+; must be large enough for ps_status_info()
+num_buf times 23h db 0
+num_buf_end db 0
+
+; temp data for printf
+pf_args dw 0
+pf_num dd 0
+pf_sig db 0
+pf_pad db 0
+pf_raw_char db 0
+gc_tmp db 0
+
diff --git a/modules/libio.h b/modules/libio.h
new file mode 100644
index 0000000..b7a3dc6
--- /dev/null
+++ b/modules/libio.h
@@ -0,0 +1,131 @@
+/*
+ *
+ * libio.h include file for libio
+ *
+ * Copyright (c) 2009 Steffen Winterfeldt.
+ *
+ * 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, Inc., 53 Temple Place Ste 330, Boston MA
+ * 02111-1307, USA; either version 2 of the License, or (at your option) any
+ * later version; incorporated herein by reference.
+ *
+ */
+
+#ifndef _LIBIO_H
+#define _LIBIO_H
+
+
+asm(".code16gcc\n");
+
+
+#define int8_t char
+#define int16_t short
+#define int32_t int
+#define uint8_t unsigned char
+#define uint16_t unsigned short
+#define uint32_t unsigned
+
+#define X86_CF 0x0001
+#define X86_PF 0x0004
+#define X86_AF 0x0010
+#define X86_ZF 0x0040
+#define X86_SF 0x0080
+#define X86_TF 0x0100
+#define X86_IF 0x0200
+#define X86_DF 0x0400
+#define X86_OF 0x0800
+
+#define EOF -1
+
+#define main _main
+
+
+typedef struct __attribute ((packed)) {
+ uint32_t eax, ebx, ecx, edx, esi, edi, ebp, eflags;
+ uint16_t ds, es, fs, gs;
+} x86regs_t;
+
+
+static inline uint16_t comboot_seg(void)
+{
+ uint16_t u;
+
+ asm("mov %%cs, %0" : "=r" (u));
+
+ return u;
+}
+
+
+static inline uint8_t _mem8(uint32_t p)
+{
+ uint8_t u;
+
+ asm(
+ "movl %1,%%esi\n"
+ "shrl $4,%%esi\n"
+ "mov %%si,%%fs\n"
+ "movl %1,%%esi\n"
+ "and $15, %%si\n"
+ "movb %%fs:(%%si),%0\n"
+ : "=abcd" (u) : "r" (p) : "si"
+ );
+
+ return u;
+}
+
+
+static inline uint16_t _mem16(uint32_t p)
+{
+ uint16_t u;
+
+ asm(
+ "movl %1,%%esi\n"
+ "shrl $4,%%esi\n"
+ "mov %%si,%%fs\n"
+ "movl %1,%%esi\n"
+ "and $15, %%si\n"
+ "movw %%fs:(%%si),%0\n"
+ : "=r" (u) : "r" (p) : "si"
+ );
+
+ return u;
+}
+
+
+static inline uint32_t _mem32(uint32_t p)
+{
+ uint32_t u;
+
+ asm(
+ "movl %1,%%esi\n"
+ "shrl $4,%%esi\n"
+ "mov %%si,%%fs\n"
+ "movl %1,%%esi\n"
+ "and $15, %%si\n"
+ "movl %%fs:(%%si),%0\n"
+ : "=r" (u) : "r" (p) : "si"
+ );
+
+ return u;
+}
+
+
+int _main(int argc, char **argv);
+void printf(char *format, ...) __attribute__ ((format (printf, 1, 2)));
+int getchar(void);
+void clrscr(void);
+void *memcpy(void *dest, const void *src, int n);
+uint32_t memcpy32(uint32_t dest, uint32_t src, int n);
+void *memset(void *dest, int c, int n);
+uint32_t memset32(uint32_t dest, int c, int n);
+void x86int(unsigned intr, x86regs_t *regs);
+void farcall(uint32_t seg_ofs, x86regs_t *regs);
+void gfx_cb(x86regs_t *r);
+void _gfx_cb(void);
+void syslinux_hook(x86regs_t *r);
+void _syslinux_hook(void);
+void reboot(void);
+
+#endif /* _LIBIO_H */
+