Compare commits

...

19 Commits
master ... dev

Author SHA1 Message Date
Yann MAGNIN 49508b417c vxGOS - v0.7.0-14 : RaspberryPi3b FrameBuffer and ASLR
*add*
> [scripts]
  | [patch_kernel_img] a small tool used to "patch" kernel image to a
        512 bytes-aligned image size. This is required for the
        RaspberryPi Imager
> [boards]
  | [common] add endianness information
  | [raspi3b] add GOT section patching
  | [raspi3b] add default font assets
  | [raspi3b] add GPU board information dumping routine at startup
  | [raspi3b] add GPU framebuffer (physical HDMI seems not work) support
  | [raspi3b] add GPU mailbox support
  | [raspi3b] add GPU memory allocator support

*update*
> [scripts]
  | [vxdev] now support endian handling using VXDEV_TOOLCHAIN_ENDIANNESS
> [sdk]
  | [converter] support endian encoding selection (LSB, MSB)
> [boards]
  | [raspi3b] proper link common display interface with framebuffer interface
  | [raspi3b] proper link fake entry with real entry
  | [raspr3b] proper send relocation information to the bootloader entry
  | [raspi3b] proper isolate the MiniUART driver

*fix*
> [scripts]
  | [vxdev] force ELF section displayed in "wide" format
> [boards]
  | [fxcg50] swap selected and unselected icon
  | [raspi3b] proper support relocation TAG
  | [raspi3b] proper generate 512 bytes-aligned image
  | [raspi3b] fix bootstrap ALSR patching mechanism
2023-06-17 14:14:17 +02:00
Yann MAGNIN b316b98004 vxGOS - v0.7.0-13 : RaspberryPi3b bootstrap and MiniUART driver
*add*
> [boards]
  | [raspi3b] add auxiliary hardware module bitfields (power, IRQ and UART1)
  | [raspi3b] add gpio hardware module bitfields
  | [raspi3b] add bootloader fake entry (test entry)
  | [raspi3b] add bootloader stack

*fix*
> [boards]
  | [raspi3b] patch missing image size in image header
  | [raspi3b] add missing "dfont()" BIOS build request
> [bootloader]
  | [linker] move bootloader stack
2023-06-10 13:33:21 +02:00
Yann MAGNIN 4ea5758074 vxGOS - v0.7.0-12 : proper Casio's devices application generation
*add*
> [boards]
  | [fx9860] add addin icon

*update*
> [boards]
  | [fx9860] icon support
  | [fx9860] generation date support
  | [fxcg50] icon support
  | [fxcg50] generation date support

*fix*
> [boards]
  | [fx9860] fix bad icon wiping offset
2023-06-08 13:48:02 +02:00
Yann MAGNIN d596c74634 vxGOS - v0.7.0-11 : add fx9860 support
*add*
> [vxgos]
  | [fx9860] add vxsdk build entry
  | [fx9860] add G1A file generation
  | [fx9860] add ASLR support
  | [fx9860] add bios primitives
  | [fx9860] add new 3x5 font
  | [fx9860] add custom GCC toolchain dependency for vxSDK
> [sdk]
  | [converter] support assets filter

*update*
> [scripts]
  | [vxdev] proper support "per board" assets requirements declaration
> [bootloader]
  | [display] proper font BIOS-dependant (board-specific) interface

*fix*
> [sdk]
  | [converter] fix default font border size
  | [converter] remove useless core function exposition
> [bootloader]
  | [assets] fix font8x9 to font8x12 (the real size)
> [scripts]
  | [vxdev] convert assets only if needed
2023-06-05 20:57:31 +02:00
Yann MAGNIN cefccb8bb1 vxGOS - v0.7.0-10 : add raspi3b support (WIP)
*add*
> [vxgos]
  | [raspi3b] add vxsdk build entry
  | [raspi3b] add bzImage generation
  | [raspi3b] add ASLR support (WIP)
  | [raspi3b] add fake bios primitives support (WIP)
> [bootloader]
  | [linker] add `.bootloader.header` section for image that need header

*update*
> [scripts]
  | [vxdev] isolate compiler flags from "vxsdk.toml" to "compiles.toml"
  | [vxdev] generate toolchain file for CMake (remove obscure env hook used by the vxSDK)

*fix*
> [bootloader]
  | [linker] disable got section size assert
  | [linker] ignore `.note.*` section
2023-06-02 12:51:39 +02:00
Yann MAGNIN 584e95e11b vxGOS - v0.7.0-9 : fix console issues
*add*
> [scripts]
  | [vxdev] add log level selection
> [bootloader]
  | [unitests] input buffer checks

*update*
> [bootloader]
  | [console] isolate line discipline
  | [console] isolate cursor interaction
  | [console] isolate buffer closing ("\n\0" insertion)
  | [console] isolate input buffer initialisation and closing
  | [console] rework complete input buffer drawing logic
  | [console] proper save font information
  | [display] allow dascii font NULL argument (auto fetch font information)
  | [unitests] output buffer checks

*fix*
> [scripts]
  | [vxdev] fix GCDA/GCDO file purge
  | [vxdev] fix coverage color
  | [vxdev] fix coverage error decoding
  | [vxdev] fix requirements.txt parsing error (abort all)
> [bootloader]
  | [console] fix various input buffer error
2023-05-14 20:44:28 +02:00
Yann MAGNIN 18c917f257 vxGOS - v0.7.0-8 : Improve CMake abstraction + fix console output buffer
*add*
> [bootloader]
  | [console] add reset output buffer primitive (for unitests mainly)

*update*
> [scripts]
  | [vxdev] change CMake abstraction interface (unitests + build)
> [bootloader]
  | [unitests] switch cr_assert_eq -> cr_expect_eq to avoid memory leak error
  | [unitests] check output buffer behavior

*fix*
> [scripts]
  | [vxdev] fix missing debug logs
  | [vxdev] fix GCDA checksum error if binary change (purge GCDA file each time)
> [bootloader]
  | [console] fix various output buffer error
2023-05-13 13:43:11 +02:00
Yann MAGNIN f75210e0fb vxGOS - v0.7.0-7 : Improve unitests interface + PoC code coverage
*add*
> [script]
  | [vxdev] proper support CMake's generator coherency
  | [vxdev] gcovr (code coverage) abstraction
  | [vxdev] unitests now support a requirements.txt which describes tests

*update*
> [scripts]
  | [vxdev] rename KASLR for more appropriate ASLR
  | [vxdev] change CMake generation interface
  | [vxdev] proper isolate Criterion abstraction
> [bootloader]
  | [unitests] change unitests architecture
  | [unitests] add check for output buffer code coverage

*fix*
> [bootloader]
  | [console] first fix for output buffer (code coverage)
2023-05-11 18:50:43 +02:00
Yann MAGNIN 8acbd8bceb vxGOS - v0.7.0-6 : PoC unitests interface
*add*
> [script]
  | [vxdev] add unitests interface
  | [vxdev] add bootloader unitests support
  | [vxdev] add cmake unitests generation
> [bootloader]
  | [unitests] add console open/close primitives unitests

*update*
> [script]
  | [vxdev] depreciate 'configure'
  | [vxdev] depreciate 'install'
  | [vxdev] depreciate 'uninstall'
  | [vxdev] rename build cmake abstraction
  | [vxdev] move KASLR script to build-specific module

*fix*
> [script]
  | [vxdev] fix generated build cmake template layout
> [bootloader]
  | [fxcg50] remove nonbreaking spaces
  | [console] close: proper clear console object
  | [console] open:  proper return error code
2023-05-04 15:00:59 +02:00
Yann MAGNIN 73711a76b2 vxGOS - v0.7.0-5 : Refacto bootloader display + PoC assets converter
*add*
> [sdk]
  | [converter] add font assets converter
  | [converter] add converter user interface
  | [converter] add font output generator selection
> [bootloader]
  | [assets] add default font
  | [display] add character drawing primitive

*update*
> [scripts/vxdev]
  | [bootloader] generate bootloader assets using SDK converter
> [bootloader]
  | [display] move all drawing primitives to "common source" (no bios)
  | [fxcg50] add small r61524 (screen) driver for `_bios_dupdate()`

*fix*
> [scripts/vxdev]
  | [kaslr] proper handle R_SH_GOT32 reloc
2023-05-01 15:25:35 +02:00
Yann MAGNIN b3e1a6ed6c vxGOS - v0.7.0-4 : PoC complete console (not stable)
*add*
> [bootloader]
  | [fxcg50] small driver for SH7305 KEYSC hardware module
  | [fxcg50] high-level key handling (special and common)
  | [console] add read primitive

*update*
> [bootloader]
  | [console] proper isolate input buffer and output buffer
  | [console] expose console buffer primitives
  | [console] isolate the input buffer primitives
  | [console] isolate the output buffer primitives
2023-04-28 14:05:53 +02:00
Yann MAGNIN ac38d420f7 vxGOS - v0.7.0-3 : refacto bios and board-specific sources architecture
*add*
> [vxdev]
  | [kernel] add sources handling
> [bootloader]
  | [ulib] add small as possible libc routines
  | [ulib] add calloc, memset, memcpy, free, malloc, vsnprintf

*update*
> [.nvimrc]
  | proper handle compilation flags declaration
> [scripts]
  | [checkers] add pylint checker (WIP)
  | [vxdev]
    | [cli] diable installation an uninstallation steps
    | [core] prepare kernel build support
    | [core] centralize KASLR generation
    | [bootloader] proper support libs link
    | [bootloader] proper support new board-specific architecture
    | [cmake] proper support libs
    | [cmake] disable install and uninstall steps
    | [kaslr] automatically invoke board-specific KASLR generating routine
> [bootloader]
  | [fxcg50] fragmentation BIOS primitives
  | [console] first implementation of the writing primitive
  | [console] prepare reading routine
  | [display] add some drawing primitive (dinfo, dline, dpixel)
> [vxsdk]
  | [toml] remove libc request

*fix*
> [bootloader]
  | [fxcg50] proper send argument high-level routine
2023-04-27 11:42:12 +02:00
Yann MAGNIN 74571c7882 vxkernel - v0.7.0-2 : fix bootloader bootstrap + prepare kernel build
*add*
> [bootloader]
  | [console] prepare console API (WIP)
  | [main] simple welcome message using console API (WIP)

*update*
> [kernel]
  | small architecture change (WIP)

*fix*
> [vxdev]
  | [kaslr] proper check for unsupported relocation symbols methods
> [bootloader]
  | [fxcg50/bios] fix `printMiniMini()` syscall wrapper
  | [fxcg50/asm] fix syscall trampoline code address
  | [display] fix alloca security check
2023-04-14 17:18:00 +02:00
Yann MAGNIN 597b25682a vxkernel - v0.7.0-1 : architecture overhaul + bootloader KASLR resolve
---

The objective of this early rework of the kernel architecture is to prepare the
project to run on other hardware than the Casio calculator (like Raspberry Pi
devices). This is why I switched the kernel workflow to integrate a custom
bootloader for the particular kernel image we generate.

The v0.7.0 of the kernel is not released since the USB driver is not stable
yet. But the need to deeply rework the architecture is prioritized. So, as this
is a critical update, I update the minor version (which is a major indicator
here since I don't have the mature version yet (v1.x.x)).

Also, I work on a brand-new micro-kernel (for the Raspberry Pi 3b and Google
Pixels), so, the name of this project, Vhex, has migrated to a temporary
name(?): VxGOS (for Vhex Gaming Operating System), which will be changed as
soon as the v0.8.0 is released. (I choose this name only because the word
"gaming" make me laugh. Take showers folks).

The objective of version 0.8.0 is to support:

    - architecture rework
    - internal bootloader
    - proper KASLR support
    - stack protection (libSSP)

---

*update*
> [common]
  | [kernel] move kernel source file in the new "vxgos" root directory
  | [boards] move board-related files / scripts in the new "vxgos"
  | [bootloader] support fxcg50 self-translation from ROM to RAM
  | [bootloader] support fxcg50 KASLR patching
  | [scripts] move `vxdev` tool in the new "scripts" root directory
  | [scripts] move norm related files to "checker" directory
  | [vxsdk] update the vxSDK indication file
2023-04-13 13:59:46 +02:00
Yann MAGNIN 18e895dfb7 vxkernel - v0.7.0-5 : setup libSSP (WIP) + support USB description step (WIP)
*add*
> [cpu]
  | add SH-CPU context structure
  | try to support Stack-Protector but seems not insert check during compile-time
  | add stack abord routine invocation (WIP)
> [usb]
  | add configuration structure
  | add configuration handling
  | add USB logger (WIP)
  | add USB high-level abstraction for debugging (WIP)
  | add USB driver
  | add fake SDL2 USB driver

*update*
> [usb]
  | clean DCP configuration API (WIP)
  | prepare proper pipe handling (WIP)
  | isolate USB protocole initialisation
  | enable some interruption
> [vxsdk.toml]
  | force enable libSSP (but seems not work o(x_x)o)

*fix*
> [usb]
  | fix endpoint type ID
> [display]
  | [dtext] workaround to possible stack corruption (WIP)
> [hypervisor]
  | [switch] fix NULL power primitives
> [vxdev]
  | use `/usr/bin/env python3` instead of `python3`
2023-04-03 10:52:05 +02:00
Yann MAGNIN c6aa4b5ac4 vxkernel - v0.7.0-4 : add USB request handling (WIP)
*add*
> [vhex/display]
  | [text] add va_list version of dtext() and dprint()
> [vhex/defs]
  | [types] add x_union_alone
> [vhex/usb]
  | [request] add USB request (WIP)
  | [pipe] add USB pipe API (WIP)
  | [dcp] add USB DCP API (WIP)
  | [macros] add interrupt clearing helper
  | [descriptor] add main USB descriptors

*update*
> [vhex/defs]
  | [attributes] rename VPACKEDENUM -> VPACKED_ALONE
> [vhex/usb]
  | [interrupt] enable setup interruptions
  | [open] perform USB device configuration check

*fix*
> [vhex/defs]
  | [call] fix last argument injection
> [vhex/usb]
  | [macros] fix delay for register sync.
  | [open] force-clear interrupt flags befor enabling interruption
2023-01-27 16:21:44 +01:00
Yann MAGNIN 42c3fabb34 vxkernel - v0.7.0-3 : add USB power handling (WIP)
*add*
> [vhex/driver/mpu/sh/sh7305/]
  | [tmu] add `sh7305_tmu_sleep_us_spinwait()`
  | [usb] add power management API
  | [usb] add open() primitive (WIP)
  | [usb] add interrupt handler (WIP)
  | [usb] add register sync macro
  | [usb] add interrupt handler

*update*
> [boards/fxcg50]
  | [fxcg50.ld] remove useless interruption gates indication
  | [fxcg50.ld] proper calculate the gap for interrupt gates
  | [fxcg50.ld] add doc about `.bss` section behaviour
> [vhex/defs]
  | [call.h] add predefined VHEX_CALL_* handlers
  | [driver.h] add USB indication (WIP)
> [vhex/driver/mpu/sh/sh7305/]
  | [tmu] proper type exposition (force `sh7305_*`)
  | [tmu] isolate TMU TSTR register
  | [tmu] use `.balign 4` instead of `.align` to minimize potential gap during linking
  | [usb] rename registers exposition

*fix*
> [vhex/module]
  | [hypervisor] fix UNUSED driver loading
> [vhex/include/vhex/defs]
  | [call] fix ALE linter error in VHEX_CALL (use Lephe' tricks)
> [nvimrc]
  | fix assembly file linter (use our custom GCC)
  | fix "_DEFAULT_SOURCE" exposition
> [vhex/driver/mpu/sh/sh7305]
  | [intc] explicit INTC_* enum offset
  | [usb] add missing INTENB0.NRDYE bit
2023-01-25 11:13:25 +01:00
Yann MAGNIN 3a42d0e055 vxkernel - v0.7.0-2 : add USB logger API
*add*
> [include]
  | [usb] add USB logger API
  | [usb] add USB logger structur
> [src/modules]
  | [usb] add USB module information
  | [usb] add USB logger API

*fix*
> [display/draw]
  | [dclear] return 0 if C_NONE color is passed
> [vxsdk.toml]
  | [sdl2] force export `_GNU_SOURCE`
> [nvimrc]
  | force display trailing spaces
  | force display non printable characters
2023-01-23 15:22:09 +01:00
Yann MAGNIN 5ea6e33f46 vxkernel - v0.7.0-1 : fix linker script + update SDL2 keycode + add USB info
*add*
> [include/driver/sh7305/]
  | [usb] add USB hardware bitfields

*update*
> [display/draw]
  | [line] update all line-drawing prototype to `d*line(int color, ...)`
  | [rect] rename `drect_filled()` -> `drect()`
> [drivers/mpu/sdl2]
  | [keyboard] remove hardcoded number of translated key to facilitate update
  | [keyboard] add [0] to [9] keys support
  | [keyboard] change [SHIFT] and [ALPHA] key translation
  | [keyboard] proper use of `SDLK_*` macros for SDL2 key

*fix*
> [.nvimrc]
  | fix header file which are considered as a C++ file
> [boards/fxcg50]
  | [fxcg50.ld] switch tab to 4 spaces
  | [fxcg50.ld] proper inject `.got*` and `.data` sections in `userram`
  | [fxcg50.ld] isolate `.vhex.vbr` section before `.bss`
  | [fxcg50.ld] explicit that the `.bss` section should not be stored in ELF
  | [fxcg50.ld] isolate `.vhex.vbr` gaps in `.bss` section
  | [fxcg50.ld] do not inject `.xram` section in `userram`
> [display/draw]
  | [rect] fix missing header
2023-01-22 18:39:43 +01:00
363 changed files with 12583 additions and 1525 deletions

47
.nvimrc
View File

@ -1,12 +1,51 @@
" indicate the compiler to use
let g:ale_asm_gcc_executable = 'sh-elf-vhex-gcc'
let g:ale_c_gcc_executable = 'sh-elf-vhex-gcc'
let g:ale_c_cc_executable = 'sh-elf-vhex-gcc'
let g:ale_cpp_gcc_executable = 'sh-elf-vhex-gcc'
" Setup GCC as linter
"let g:ale_linters = {'c' : ['sh-elf-vhex-gcc']}
" Find GiteaPC include directory information
let s:_hdrflags = [
\ '-I./vxgos/bootloader/include',
\ '-I./vxgos/bootloader/boards/fxcg50/include/',
\ '-I./vxgos/bootloader/boards/raspi3b/include/',
\ ]
let s:_cflags = [
\ '-DFXCG50',
\ '-std=c11',
\ '-Wall',
\ '-Wextra',
\ ]
let s:_asmflags = [
\ '-DFXCG50',
\ '-x assembler-with-cpp',
\ '-Wall',
\ '-Wextra',
\ '-m4-nofpu',
\ '-mb',
\ '-Wa,--dsp',
\ ]
" Patch include header
let g:ale_c_cc_options = '-std=c11 -Wall -I./kernel/include -I./boards/*/include'
" convert table into string
let s:_hdrflags = join(s:_hdrflags)
let s:_asmflags = join(s:_asmflags)
let s:_cflags = join(s:_cflags)
" Patch option
" @note
" - we setup cpp because header file is considered ad C++ file
" - we force sh-elf-vhex for assembly file only
let g:ale_asm_gcc_options = s:_asmflags.' '.s:_hdrflags
let g:ale_c_gcc_options = s:_cflags.' '.s:_hdrflags
let g:ale_c_cc_options = s:_cflags.' '.s:_hdrflags
let g:ale_cpp_cc_options = s:_cflags.' '.s:_hdrflags
let g:ale_cpp_gcc_options = s:_cflags.' '.s:_hdrflags
" Enable completion
let g:ale_completion_enabled = 1
" Enable linter when enter is pressed
let g:ale_lint_on_enter = 1
" Force display non-printable chars
set list

View File

@ -1,109 +0,0 @@
cmake_minimum_required(VERSION 3.15)
#---
# handle vxSDK-specific
#---
if(
NOT DEFINED ENV{VXSDK_PKG_NAME}
OR NOT DEFINED ENV{VXSDK_PKG_VERSION}
OR NOT DEFINED ENV{VXSDK_BUILD_CFLAGS}
OR NOT DEFINED ENV{VXSDK_PREFIX_BUILD}
OR NOT DEFINED ENV{VXSDK_PREFIX_INSTALL})
message(FATAL_ERROR "You should use the vxSDK to build this project")
endif()
set(VXSDK_PKG_NAME $ENV{VXSDK_PKG_NAME})
set(VXSDK_PKG_VERSION $ENV{VXSDK_PKG_VERSION})
set(VXSDK_PREFIX_BUILD $ENV{VXSDK_PREFIX_BUILD})
set(VXSDK_PREFIX_INSTALL $ENV{VXSDK_PREFIX_INSTALL})
set(VXSDK_BUILD_CFLAGS $ENV{VXSDK_BUILD_CFLAGS})
#---
# handle configuration-specific information
#---
set(VXKERNEL_CONF ${VXSDK_PREFIX_BUILD}/vxkernel_config.cmake)
if(NOT EXISTS ${VXKERNEL_CONF})
message(FATAL_ERROR "You should performs project configuration")
endif()
include(${VXKERNEL_CONF})
if(NOT DEFINED VXKERNEL_ENABLE_MODULES)
message(FATAL_ERROR "Missing module declaration")
elseif(NOT DEFINED VXKERNEL_ENABLE_DRIVERS)
message(FATAL_ERROR "Missing driver declaration")
endif()
#---
# handle project-specific information
#---
project(${VXSDK_PKG_NAME} VERSION ${VXSDK_PKG_VERSION} LANGUAGES C ASM)
#---
# Handle source listing
#---
set(VXKERNEL_SRC_TARGETS "")
set(VXKERNEL_INC_TARGETS "")
set(VXKERNEL_DIR_TARGETS "")
foreach(mod ${VXKERNEL_ENABLE_MODULES})
list(APPEND VXKERNEL_DIR_TARGETS kernel/src/modules/${mod})
endforeach()
foreach(drv ${VXKERNEL_ENABLE_DRIVERS})
list(APPEND VXKERNEL_DIR_TARGETS kernel/src/drivers/${drv})
endforeach()
set(board_prefix boards/${VXKERNEL_ENABLE_BOARD})
if(EXISTS ${board_prefix}/src)
list(APPEND VXKERNEL_DIR_TARGETS ${board_prefix}/src)
endif()
foreach(dir ${VXKERNEL_DIR_TARGETS})
file(GLOB_RECURSE dir_src ${dir}/*.c ${dir}/*.S ${dir}/*.s)
list(APPEND VXKERNEL_SRC_TARGETS ${dir_src})
endforeach()
file(GLOB dir_src kernel/src/*.c kernel/src/*.S kernel/src/*.s)
list(APPEND VXKERNEL_SRC_TARGETS ${dir_src})
list(APPEND VXKERNEL_INC_TARGETS kernel/include ./)
if(EXISTS ${board_prefix}/include)
list(APPEND VXKERNEL_INC_TARGETS ${board_prefix}/include)
endif()
if(DEFINED VXKERNEL_CMAKE_DEBUG)
message(
FATAL_ERROR
"dir list = ${VXKERNEL_DIR_TARGETS}\n"
"src list = ${VXKERNEL_SRC_TARGETS}\n"
"inc list = ${VXKERNEL_INC_TARGETS}\n"
)
endif()
#---
# Compile-specific declaration
#---
include_directories(${VXKERNEL_INC_TARGETS})
add_compile_options(-Wall -Wextra ${VXSDK_BUILD_CFLAGS})
add_library(vxkernel ${VXKERNEL_SRC_TARGETS})
#---
# Install-specific declaration
#---
set(CMAKE_INSTALL_PREFIX ${VXSDK_PREFIX_INSTALL})
install(TARGETS vxkernel DESTINATION "lib")
install(DIRECTORY kernel/include/ DESTINATION "include")
install(FILES ${VXKERNEL_INSTALL_EXTRA_FILES} DESTINATION "lib")

View File

@ -1,19 +0,0 @@
[meta]
description = "Casio's Fxcg50 calculator board"
author = 'Yann MAGNIN'
[config]
modules = [
'display',
'fs',
'hypervisor',
'keyboard',
'kmalloc',
'timer',
'rtc',
'dma'
]
drivers = [
'screen:R61524',
'mpu:sh:sh7305'
]

View File

@ -1,212 +0,0 @@
/*
Linker script for the fxcg50 platform.
*/
OUTPUT_FORMAT("elf32-sh", "elf32-sh", "elf32-sh")
OUTPUT_ARCH(sh4)
ENTRY(_initialize)
/*
** Linker script for user executables.
*/
MEMORY
{
/* virtual memory, read-write segment */
userram (WX) : o = 0x00000000, l = 1M
/* On-chip X memory */
xram (rwx): o = 0xe5007000, l = 8k
}
SECTIONS
{
/* Code */
.text : {
PROVIDE(___kernel_reloc_start = .);
*(.text);
*(.text.*);
_bctors = . ;
*(.ctors .ctors.*)
_ectors = . ;
_bdtors = . ;
*(.dtors .dtors.*)
_edtors = . ;
} > userram
/* vhex's interrupt handler blocks (.vhex.blocks)
Although vhex's blocks end up in VBR space, they are relocated at
startup by the library/drivers, so we store them here for now */
.vhex.blocks : {
KEEP(*(.vhex.blocks));
} > userram
/* Exposed driver interfaces (.vhex.drivers)
The driver information is required to start and configure the
driver, even if the symbols are not referenced */
.vhex.drivers : {
_vhex_drivers_start = . ;
KEEP(*(SORT_BY_NAME(.vhex.drivers.*)));
_vhex_drivers_end = . ;
} > userram
/* Exposed module interfaces (.vhex.modules) */
.vhex.modules : {
_vhex_modules_start = . ;
KEEP(*(SORT_BY_NAME(.vhex.modules.*)));
_vhex_modules_end = . ;
} > userram
/* Exposed device interfaces (.vhex.device) */
.vhex.device : {
_vhex_devices_start = . ;
KEEP(*(.vhex.device));
_vhex_devices_end = . ;
} > userram
/* Read-only sections */
.rodata : {
/* Read-Only data */
*(.rodata);
*(.rodata.*);
} > userram
/* The GOT section is a bit exotic.
When I generate the PIE executable, all global variables are stored
is the *(.got) section and the __GLOBAL_OFFSET_TABLE__ object is
generated and inserted in the *(.got.plt) section.
But, neither of *(.plt*) or *(rela*) section are generated to help
me out for the "relocation" of each address stored in the GOT. The
result is that the content of the GOT is always absolute addresses,
which makes the machine crash.
So, the idea to bypass this limitation (probably due to the custom
GCC which not provide critical information for the relocation) is to
isolate the GOT in a standalone section and, after the loading of
the image, walk thought the relocalised table and manually patch
each address.
Moreover, the __GLOBAL_OFFSET_TABLE__ is used like a table, so, the
section ".got.plt" SHOULD be before the .got section. */
.got.plt : { *(.got.plt) *(.igot.plt) *(.igot) }
.got : { *(.got) }
.dynamic : { *(.dynamic) }
/* readable / writable data */
.data ALIGN(4) : {
/* Data sections */
*(.data);
*(.data.*);
*(COMMON);
}
.bss ALIGN(4) : {
/* bss section included to avoid missaligned segment */
*(.bss);
*(.bss.*);
/* dynamic BSS information (move me ?) */
*(.dynbss)
/* Video RAM symbols
The Video RAM contains a full pixel sized frame for the
screen. Its size is 396x224 and each pixel depth is 16bits,
so (3996 * 224) * 2 = 177408
Thus, we use a double buffering methods to speed-up the
render, this is why we define two VRAM */
_vhex_vram0 = ALIGN(32);
. = _vhex_vram0 + 177408;
_vhex_vram1 = ALIGN(32);
. = _vhex_vram1 + 177408;
} > userram
/* Vhex VBR management geometry
Due to the SH3/SH4 VBR system, we have some restrictions:
The handlers should reside at VBR relative position, in P1 or P2
protected space (0x8* or 0xA*). For our case, the bootloader will
relocalise the kernel at the P1 area using the Casio's UTLB
information. So, we don't have to wories about this here.
There are 3 vectors offset called by the processor : VBR + 0x100,
0x400 and 0x600. The first offset is involved when an exception
occur, the second when a memory (TLB) error is detected and the last
when an interruptions is detected.
All exception and memory interuption will be handled "normaly" : a
simple handler will be involved which will try to resolve/fix the
problem, if nothing can be fixed, a panic screen will be displayed.
However, regarding interuptions, we will use a power-full and
complexe technique to compact as most as possible the VBR size. To
explain the technique we need to know that each interruption start at
offset VBR + 0x600 and each interrupt source is "gapped" by 0x20 (
for exemple the 0x400 is for TMU0, 0x420 is for TMU1 and 0x440 is for
TMU2) Moreover, we have some "hole" in the "interrup map".
So, the idea is to write all interrupt handler in "blocks" of 32
bytes, and, for those that 32 bytes is to short, can use hole of the
interrupt map. */
.vhex.vbr : ALIGN(4) {
_vhex_vbr = . - 0x100;
*(.vhex.exch.pretext) ;
*(.vhex.exch)
. = _vhex_vbr + 0x400;
*(.vhex.tlbh.pretext) ;
*(.vhex.tlbh) ;
. = _vhex_vbr + 0x600;
*(.vhex.inth.pretext) ;
*(.vhex.inth) ;
/* interrupt block entry */
__vhex_interrupth_start = ALIGN(4);
/* static ram start (for kmalloc) */
PROVIDE(___sram_start = ALIGN(4) + 4096);
} > userram
/* On-chip memory sections: IL and Y (X is reserved) */
. = ORIGIN(xram);
.xram ALIGN(4) : ALIGN(4) {
*(.vhex.xram)
. = ALIGN(16);
} > xram AT> userram
/* unwanted section */
/DISCARD/ : {
*(.rela.debug*)
*(.gnu.*)
*(.debug_info)
*(.debug_abbrev)
*(.debug_loc)
*(.debug_aranges)
*(.debug_ranges)
*(.debug_line)
*(.debug_str)
*(.debug_*)
*(.jcr)
*(.eh_frame_hdr)
*(.eh_frame)
*(.comment)
*(.interp)
}
}

View File

@ -1,37 +0,0 @@
//---
// vhex:defs:attributes - Macros for compiler-specific attributes
//---
#ifndef __VHEX_DEFS_ATTRIBUTES__
# define __VHEX_DEFS_ATTRIBUTES__
/* Objects from specific sections */
#define VSECTION(x) __attribute__((section(x)))
/* Unused parameters or variables */
#define VUNUSED __attribute__((unused))
/* Functions that *must* be inlined */
#define VINLINE __attribute__((always_inline)) inline
/* Aligned variables */
#define VALIGNED(x) __attribute__((aligned(x)))
/* Packed structures. I require explicit alignment because if it's unspecified,
GCC cannot optimize access size, and reads to memory-mapped I/O with invalid
access sizes silently fail - honestly you don't want this to happen */
#define VPACKED(x) __attribute__((packed, aligned(x)))
/* Packed enumerations */
#define VPACKEDENUM __attribute__((packed))
/* Transparent unions */
#define VTRANSPARENT __attribute__((transparent_union))
/* Weak symbols */
#define VWEAK __attribute__((weak))
/* Constructors */
#define VCONSTRUCTOR __attribute__((constructor))
#define VDESTRUCTOR __attribute__((destructor))
/* Functions that do not return */
#define VNORETURN __attribute__((noreturn))
#endif /*__VHEX_DEFS_ATTRIBUTES__*/

View File

@ -1,184 +0,0 @@
#include <vhex/keyboard/interface.h>
#include <vhex/driver.h>
#include <vhex/keyboard.h>
#include <SDL2/SDL.h>
//---
// fake interrupt handler
//---
#define __SDL2_KEYCODE_SUPPORTED 16
static struct {
int sdl2_id;
char const * const sdl2_name;
int vhex_id;
char const * const vhex_name;
} key_translation[__SDL2_KEYCODE_SUPPORTED] = {
{
.sdl2_id = 49, .sdl2_name = "1",
.vhex_id = VKEY_F1, .vhex_name = "F1"
},
{
.sdl2_id = 50, .sdl2_name = "2",
.vhex_id = VKEY_F2, .vhex_name = "F2"
},
{
.sdl2_id = 51, .sdl2_name = "3",
.vhex_id = VKEY_F3, .vhex_name = "F3"
},
{
.sdl2_id = 52, .sdl2_name = "4",
.vhex_id = VKEY_F4, .vhex_name = "F4"
},
{
.sdl2_id = 53, .sdl2_name = "5",
.vhex_id = VKEY_F5, .vhex_name = "F5"
},
{
.sdl2_id = 54, .sdl2_name = "6",
.vhex_id = VKEY_F6, .vhex_name = "F6"
},
{
.sdl2_id = 0x4000004f, .sdl2_name = "right arrow",
.vhex_id = VKEY_RIGHT, .vhex_name = "right arrow"
},
{
.sdl2_id = 0x40000050, .sdl2_name = "left arrow",
.vhex_id = VKEY_LEFT, .vhex_name = "left arrow"
},
{
.sdl2_id = 0x40000051, .sdl2_name = "down arrow",
.vhex_id = VKEY_DOWN, .vhex_name = "down arrow"
},
{
.sdl2_id = 0x40000052, .sdl2_name = "up arrow",
.vhex_id = VKEY_UP, .vhex_name = "up arrow"
},
{
.sdl2_id = 13, .sdl2_name = "enter",
.vhex_id = VKEY_EXE, .vhex_name = "EXE"
},
{
.sdl2_id = 0x400000e0, .sdl2_name = "shift",
.vhex_id = VKEY_ALPHA, .vhex_name = "ALPHA"
},
{
.sdl2_id = 0x400000e1, .sdl2_name = "ctrl",
.vhex_id = VKEY_SHIFT, .vhex_name = "SHIFT"
},
{
.sdl2_id = 27, .sdl2_name = "echap",
.vhex_id = VKEY_EXIT, .vhex_name = "EXIT"
},
{
.sdl2_id = 8, .sdl2_name = "backspace",
.vhex_id = VKEY_DEL, .vhex_name = "DEL"
},
{
.sdl2_id = 9, .sdl2_name = "tab",
.vhex_id = VKEY_MENU, .vhex_name = "MENU"
},
};
//---
// keycache primitives
//---
static int sdl_keycache_event_pop(vkey_event_t *event)
{
SDL_Event evt;
int ret;
while (1)
{
ret = SDL_PollEvent(&evt);
if (ret == 0) {
if (event != NULL) {
event->key = VKEY_NONE;
event->type = VKEYEV_NONE;
}
return 0;
}
if (evt.type == SDL_QUIT)
exit(0);
if (evt.type != SDL_KEYDOWN && evt.type != SDL_KEYUP)
continue;
for (int i = 0; i < __SDL2_KEYCODE_SUPPORTED ; ++i) {
if (evt.key.keysym.sym != key_translation[i].sdl2_id)
continue;
if (event != NULL) {
event->time = 0;
event->key = key_translation[i].vhex_id;
event->type = VKEYEV_DOWN;
if (evt.type == SDL_KEYDOWN)
event->type = VKEYEV_UP;
}
return 1;
}
}
}
//---
// Internal fake driver definition
//---
/* __keysc_configure() : configure the keyboard module */
static void __keysc_configure(void)
{
printf("[drv] keyboard: keymap notes:\n");
printf(" +-------------+-------------+\n");
printf(" | OS | Vhex |\n");
printf(" +-------------+-------------+\n");
for (int i = 0; i < __SDL2_KEYCODE_SUPPORTED; ++i) {
printf(
" | %-11s | %-11s |\n",
key_translation[i].sdl2_name,
key_translation[i].vhex_name
);
}
printf(" +-------------+-------------+\n");
}
/* __keysc_hsave() : save hardware information */
static void __keysc_hsave(void)
{
// Nothing to do, this is a fake driver
;
}
/* __keysc_hrestore() : restore hadware information */
static void __keysc_hrestore(void)
{
// Nothing to do, this is a fake driver
;
}
struct vhex_driver drv_keysc = {
.name = "SDL2 Keyboard",
.hsave = (void*)&__keysc_hsave,
.hrestore = (void*)&__keysc_hrestore,
.configure = (void*)&__keysc_configure,
.state_size = 4,
.flags = {
.KEYBOARD = 1,
.SHARED = 0,
.UNUSED = 0,
},
.module_data = &(struct keyboard_drv_interface){
.keycache_init = NULL,
.keycache_keydown = NULL,
.keycache_event_wait = NULL,
.keycache_event_pop = &sdl_keycache_event_pop,
.keycache_event_push = NULL,
.keycache_quit = NULL,
}
};
VHEX_DECLARE_DRIVER(03, drv_keysc);

0
scripts/bootstrap.sh Normal file
View File

View File

@ -0,0 +1,49 @@
"""
pylint - checker for vxnorm
This file does not expose an explicite VxChecker object declaration to avoid
dependencies handling, you just need to provide:
======================= ===============================================
parse_file() Parse the source file
======================= ===============================================
"""
import subprocess
#---
# Public
#---
def parse_file(checker, _, pathinfo):
""" parse the mapped file
The file is mapped using mmap() and seeked through offset 0 to avoid too
many I/O operations with classical file primitive.
@args
> checker (VxChecker) - current checker instance for this file
> mfile (mmap) - mmap instance of the file, seeked at 0
> pathname (str) - file pathname
@return
> Nothing
"""
status = subprocess.run(
['pylint', pathinfo['filepath']], capture_output=True, check=False
)
if status.returncode == 0:
return
for line in status.stdout.decode('utf8').split('\n'):
if not line:
continue
if line[0] == '*':
continue
if line[0] == '-':
break
if not (line := line.split(' ', 2)):
continue
checker.notify(
line[0].split(':', 1)[1][:-1],
f"[{line[1][:-1]}] {line[2]}",
'pylint'
)

View File

@ -0,0 +1,58 @@
" Force-display trailing whitespaces
highlight ExtraWhitespace ctermbg=red guibg=red
match ExtraWhitespace /\s\+$/
autocmd BufWinEnter * match ExtraWhitespace /\s\+$/
autocmd InsertEnter * match ExtraWhitespace /\s\+\%#\@<!$/
autocmd InsertLeave * match ExtraWhitespace /\s\+$/
autocmd BufWinLeave * call clearmatches()
" indicate the compiler to use
let g:ale_asm_gcc_executable = 'sh-elf-vhex-gcc'
let g:ale_c_gcc_executable = 'sh-elf-vhex-gcc'
let g:ale_c_cc_executable = 'sh-elf-vhex-gcc'
let g:ale_cpp_gcc_executable = 'sh-elf-vhex-gcc'
" Find GiteaPC include directory information
let s:_hdrflags = [
\ '-I./vxgos/bootloader/include',
\ '-I./vxgos/bootloader/boards/fxcg50/include/',
\ ]
let s:_cflags = [
\ '-DFXCG50',
\ '-std=c11',
\ '-Wall',
\ '-Wextra',
\ ]
let s:_asmflags = [
\ '-DFXCG50',
\ '-x assembler-with-cpp',
\ '-Wall',
\ '-Wextra',
\ '-m4-nofpu',
\ '-mb',
\ '-Wa,--dsp',
\ ]
" convert table into string
let s:_hdrflags = join(s:_hdrflags)
let s:_asmflags = join(s:_asmflags)
let s:_cflags = join(s:_cflags)
" Patch option
" @note
" - we setup cpp because header file is considered ad C++ file
" - we force sh-elf-vhex for assembly file only
let g:ale_asm_gcc_options = s:_asmflags.' '.s:_hdrflags
let g:ale_c_gcc_options = s:_cflags.' '.s:_hdrflags
let g:ale_c_cc_options = s:_cflags.' '.s:_hdrflags
let g:ale_cpp_cc_options = s:_cflags.' '.s:_hdrflags
let g:ale_cpp_gcc_options = s:_cflags.' '.s:_hdrflags
" Enable completion
let g:ale_completion_enabled = 1
" Enable linter when enter is pressed
let g:ale_lint_on_enter = 1
" Force display non-printable chars
set list

View File

@ -0,0 +1,30 @@
"""
patch kernel image for the Raspberry Pi Imager
"""
import os
import sys
#---
# Public
#---
if len(sys.argv) != 3:
print("./script <kernel_original> <output>")
if os.path.exists(sys.argv[2]):
yes = input("output file already exists, overwrite? [Yn]")
if yes and yes not in ['y', 'Y', 'yes']:
print("output file already exists, abord", file=sys.stderr)
sys.exit(84)
os.remove(sys.argv[2])
with open(sys.argv[2], "xb") as patch:
with open(sys.argv[1], "rb") as kernel:
content = kernel.read()
patch.write(content)
kernel_size = len(content)
if kernel_size % 512:
for _ in range(0, 512 - (kernel_size % 512)):
patch.write(0x00.to_bytes(1))
print(f"{sys.argv[2]}: patched and generated")

2
scripts/preflight.sh Normal file
View File

@ -0,0 +1,2 @@
#TODO : check vxdev virtual env
#TODO : check if vxSDK is installed

View File

@ -1,15 +1,19 @@
#! /usr/bin/env python3
"""
Vhex kernel developement script
"""
import sys
import cli
from core.logger import log
#---
# Internals
#---
__HELP__ = """ usage: vxdev [ACTION] ...
Actions:
configure perform configuration step
doctor display information about the vxOS and supported feature
build perfrom build step
install perform installation step
uninstall perform uninstallation step
@ -20,27 +24,33 @@ Options:
You can try `vxdev <action> --help` for more information about each action
"""
#---
# Public
#---
def _main(argv):
""" main entry of the script """
if '-h' in argv or '--help' in argv:
print(__HELP__)
log.user(__HELP__)
sys.exit(0)
if not argv:
print(__HELP__)
log.user(__HELP__)
sys.exit(84)
if argv[0] == 'configure':
sys.exit(cli.configure(argv[1:]))
if argv[0] in ['-v', '-vv', '-vvv']:
log.level += len(argv[0]) - 1
argv = argv[1:]
if not argv:
log.error(__HELP__)
sys.exit(84)
if argv[0] == 'build':
sys.exit(cli.build(argv[1:]))
if argv[0] == 'install':
sys.exit(cli.install(argv[1:]))
if argv[0] == 'uninstall':
sys.exit(cli.uninstall(argv[1:]))
if argv[0] == 'unitests':
sys.exit(cli.unitests(argv[1:]))
print(f"unrecognized argument '{argv[0]}'", file=sys.stderr)
sys.exit(84)
#---
# Public
#---
sys.exit(_main(sys.argv[1:]))

View File

@ -1,34 +1,22 @@
"""
Expose configure script part
"""
from cli.configure import configure_entry
from cli.build import build_entry
from cli.install import install_entry
from cli.uninstall import uninstall_entry
from cli.unitests import unitests_entry
__all__ = [
'configure',
'build',
'install',
'uninstall',
'unitests',
]
#---
# Public
#---
def configure(argv):
""" invoke configuration script """
return configure_entry(argv)
def build(argv):
""" invoke build script """
return build_entry(argv)
def install(argv):
""" invoke install script """
return install_entry(argv)
def uninstall(argv):
""" invoke uninstall script """
return uninstall_entry(argv)
def unitests(argv):
""" invoke unitests script """
return unitests_entry(argv)

View File

@ -4,12 +4,17 @@ Vhex kernel build script
import os
import sys
from core.cmake import cmake_build
from core.logger import log
from core.build import VxOSBuild
__all__ = [
'build_entry'
]
#---
# Internal
#---
__HELP__ = """ usage: vxdev build [OPTIONS] ...
Build script for the Vhex kernel
@ -26,22 +31,22 @@ Options:
def build_entry(argv):
""" Build entry """
if '-h' in argv or '--help' in argv:
print(__HELP__)
log.user(__HELP__)
sys.exit(0)
if 'VXSDK_PKG_TARGET' not in os.environ \
or 'VXSDK_PREFIX_BUILD' not in os.environ \
or 'VXSDK_PREFIX_INSTALL' not in os.environ:
log.error("unable to build the vxGOS without using the vxSDK")
enable_verbose = False
for arg in argv:
if arg in ['-v', '--verbose']:
enable_verbose = True
continue
print(f"unrecognized argument '{arg}'", file=sys.stderr)
sys.exit(84)
log.error(f"unrecognized argument '{arg}'")
if 'VXSDK_PREFIX_BUILD' not in os.environ:
print(
"unable to generate the configuration file, you should use the "
"vxSDK",
file=sys.stderr
)
sys.exit(84)
return cmake_build(os.environ['VXSDK_PREFIX_BUILD'], enable_verbose)
build = VxOSBuild(enable_verbose)
build.configure()
build.build()
return 0

View File

@ -1,95 +0,0 @@
"""
Vhex kernel configuration script
"""
import os
import sys
from core.board import board_command, board_select
from core.format import format_command, format_select
from core.config_file import config_file_generate
from core.cmake import cmake_configure
__all__ = [
'configure_entry'
]
__HELP__ = """ usage: vxdev configure [OTPIONS ...] [ACTIONS ...]
Configuration script for the Vhex unikernel.
Actions:
board: Board hadling
<board-name> select the board as compilation target
--list,list display board list information
--info,info display a particular board information
vdso: "Fake" kernel compilation like a vDSO
--info,info display all infromation that the vdso will exposes
--generate,gen generate the fake build source (debug)
Options:
-h,--help display this message
--board=<board> select the board to target
--format=<target>
select the format of the library generation. You can use two format:
<> static - generate a static library
<> vdso - generate a "fake" dynamic library (default)
"""
#---
# Public
#---
def configure_entry(argv):
""" main entry of the script """
# early check
if '-h' in argv or '--help' in argv:
print(__HELP__)
sys.exit(0)
# handle vxSDK configuration
if 'VXSDK_PKG_TARGET' in os.environ:
argv.append(f"--board={os.environ['VXSDK_PKG_TARGET']}")
# default behaviour
if not argv:
print(__HELP__)
sys.exit(84)
# configure default value
kernconf = {
'VXKERNEL_ENABLE_VERBOSE' : False,
'VXKERNEL_ENABLE_MODULES' : [],
'VXKERNEL_ENABLE_DRIVERS' : [],
'VXKERNEL_TARGET_FORMAT' : 'vdso',
}
# check subcommand
if argv[0] == 'board':
sys.exit(board_command(argv[1:]))
if argv[0] == 'format':
sys.exit(format_command(argv[1:]))
# check user arguments
for arg in argv:
if arg.find('--board=') == 0:
board_select(kernconf, arg[8:])
elif arg.find('--format=') == 0:
format_select(kernconf, arg[9:])
else:
print(f"argument '{arg}' not recognized", file=sys.stderr)
sys.exit(84)
# check vxSDK validity
try:
prefix_src = os.environ['VXSDK_CURRENT_SOURCE_DIR']
prefix_build = os.environ['VXSDK_PREFIX_BUILD']
if config_file_generate(kernconf, prefix_build) == 0:
sys.exit(cmake_configure(prefix_build, prefix_src))
except KeyError:
print(
"unable to generate the configuration file, you should use the "
"vxSDK",
file=sys.stderr
)
sys.exit(84)

View File

@ -1,50 +0,0 @@
"""
Vhex kernel install script
"""
import os
import sys
from core.cmake import cmake_install
__all__ = [
'install_entry'
]
__HELP__ = """ usage: vxdev install [OPTIONS] ...
Install script for the Vhex kernel
Options:
-h, --help display this message
-v, --verbose display more information during the building process
"""
#---
# Public
#---
def install_entry(argv):
""" Install entry """
if '-h' in argv or '--help' in argv:
print(__HELP__)
sys.exit(0)
enable_verbose = False
for arg in argv:
if arg in ['-v', '--verbose']:
enable_verbose = True
continue
print(f"unrecognized argument '{arg}'", file=sys.stderr)
sys.exit(84)
try:
_ = os.environ['VXSDK_PREFIX_BUILD']
__ = os.environ['VXSDK_PREFIX_INSTALL']
except KeyError:
print(
"unable to generate the configuration file, you should use the "
"vxSDK",
file=sys.stderr
)
sys.exit(84)
return cmake_install(os.environ['VXSDK_PREFIX_BUILD'], enable_verbose)

View File

@ -1,50 +0,0 @@
"""
Vhex kernel uninstall script
"""
import os
import sys
from core.cmake import cmake_uninstall
__all__ = [
'uninstall_entry'
]
__HELP__ = """ usage: vxdev uninstall [OPTIONS] ...
uninstall script for the Vhex kernel
Options:
-h, --help display this message
-v, --verbose display more information during the building process
"""
#---
# Public
#---
def uninstall_entry(argv):
""" uninstall entry """
if '-h' in argv or '--help' in argv:
print(__HELP__)
sys.exit(0)
enable_verbose = False
for arg in argv:
if arg in ['-v', '--verbose']:
enable_verbose = True
continue
print(f"unrecognized argument '{arg}'", file=sys.stderr)
sys.exit(84)
try:
_ = os.environ['VXSDK_PREFIX_BUILD']
__ = os.environ['VXSDK_PREFIX_INSTALL']
except KeyError:
print(
"unable to generate the configuration file, you should use the "
"vxSDK",
file=sys.stderr
)
sys.exit(84)
return cmake_uninstall(os.environ['VXSDK_PREFIX_BUILD'], enable_verbose)

View File

@ -0,0 +1,46 @@
"""
cli.unitests - unit tests interface
"""
import sys
from core.logger import log
from core.unitests import VxOSUnitest
__all__ = [
'unitests_entry'
]
#---
# Internal
#---
__HELP__ = """ usage: vxdev unitest [OPTIONS] ...
Build script for the Vhex kernel
Options:
-h, --help display this message
-v, --verbose display more information during the building process
"""
#---
# Public
#---
def unitests_entry(argv):
""" Build entry """
if '-h' in argv or '--help' in argv:
log.user(__HELP__)
sys.exit(0)
enable_verbose = False
for arg in argv:
if arg in ['-v', '--verbose']:
enable_verbose = True
continue
log.error(f"unrecognized argument '{arg}'")
unitest = VxOSUnitest(enable_verbose)
unitest.configure()
unitest.execute()
return 0

View File

@ -1,242 +0,0 @@
"""
Board handling
"""
import os
import sys
import toml
__all__ = [
'board_select',
'board_command',
]
#---
# Internal functions
#---
__PROJ_PREFIX__ = f"{os.path.dirname(__file__)}/../../.."
def _warning(text):
print(text, file=sys.stderr)
def _board_fetch_info_meta(board_info, board_desc, board):
""" Fetch and check board meta information
@args
> board_info (dict) - board information
> board_desc (dict) - board TOML parsed file
> board (str) - board name
@return
> 0 if success, negative value otherwise
"""
if 'meta' not in board_desc:
_warning(f"board '{board}' : missing meta section")
return -1
if 'author' not in board_desc['meta']:
_warning(f"board '{board}' : missing author names")
return -2
if 'description' not in board_desc['meta']:
_warning(f"board '{board}' : missing description")
return -3
board_info['author'] = board_desc['meta']['author']
board_info['desc'] = board_desc['meta']['description']
return 0
def _board_fetch_info_config(board_info, board_desc, board):
""" Fetch and check board configuration information
@args
> board_info (dict) - board information
> board_desc (dict) - board TOML parsed file
> board (str) - board name
@return
> 0 if success, negative value otherwise
"""
if 'config' not in board_desc:
_warning(f"board '{board}' : missing config section")
return -1
board_conf = board_desc['config']
if 'modules' not in board_conf:
_warning(f"board '{board}' : missing modules declaration")
return -2
if 'drivers' not in board_conf:
_warning(f"board '{board}' : missing drivers declaration")
return -3
mod_prefix = f"{__PROJ_PREFIX__}/kernel/src/modules"
for mod in board_conf['modules']:
if not os.path.exists(f"{mod_prefix}/{mod}"):
_warning(f"{board} : module '{mod}' does not exists, skipped")
continue
board_info['config']['modules'].append(mod)
drv_prefix = f"{__PROJ_PREFIX__}/kernel/src/drivers"
for drv in board_conf['drivers']:
drv_path = drv.replace(':', '/')
if not os.path.exists(f"{drv_prefix}/{drv_path}"):
_warning(f"{board} : driver '{drv}' does not exists, skipped")
continue
board_info['config']['drivers'].append(drv_path)
if 'install_files' in board_conf:
for file in board_conf['install_files']:
file_pathname = f"{__PROJ_PREFIX__}/boards/{board}/{file}"
if not os.path.exists(file_pathname):
_warning("{board}: unable to find the file '{file}', skipped")
continue
board_info['config']['install_files'].append(file_pathname)
return 0
def _board_fetch_info(board):
""" Generate all information about a particular board
@return
> a dictionary with all board information : {
'name' : <board name>,
'path' : <board absolute path>,
'config' : {
'drivers' : <list of driver pseudo-pathname>,
'modules' : <list of module pseudo-pathname>
},
}
"""
board_info = {
'name' : board,
'path' : f"{__PROJ_PREFIX__}/boards/{board}",
'author' : 'unknown',
'desc' : 'unknown',
'config' : {
'drivers' : [],
'modules' : [],
'install_files' : []
}
}
# Check board existance
if not os.path.exists(board_info['path']):
_warning(f"board '{board}' does not exists, skipped")
return {}
board_desc_path = f"{board_info['path']}/board.toml"
if not os.path.exists(board_desc_path):
_warning(f"board '{board}' : missing board description, skipped")
return {}
# Try to dump (and tranforms) board information
with open(board_desc_path, "r", encoding='utf8') as file:
board_desc = toml.loads(file.read())
# check meta board information
if _board_fetch_info_meta(board_info, board_desc, board) != 0:
return {}
# check board configuration
if _board_fetch_info_config(board_info, board_desc, board) != 0:
return {}
# return the board information
return board_info
def _board_fetch_available_board():
""" Fetch all available board
@return
> a list of all available board name
"""
# fetch folder information
board_prefix = f"{__PROJ_PREFIX__}/boards"
if not (available_list := list(os.walk(board_prefix))):
return []
# fetch folder content only
if not (available_list := available_list[0]):
return 0
# fetch directory only
return available_list[1]
def _board_generate_conf(kernconf, board_info):
""" generate exportable variable information
@args
> kernconf (dict) - kernel configuration
> board_info (dict) - board information
@return
> nothing
"""
bconf = board_info['config']
kernconf['VXKERNEL_ENABLE_BOARD'] = board_info['name']
kernconf['VXKERNEL_ENABLE_MODULES'] = bconf['modules']
kernconf['VXKERNEL_ENABLE_DRIVERS'] = bconf['drivers']
kernconf['VXKERNEL_INSTALL_EXTRA_FILES'] = bconf['install_files']
def _board_display_list(verbose, board_target):
""" Display board information
@args
> verbose (bool) - display more information
> board_target (str) - targeted board
@return
> 0 if success, negative value othervise
"""
board_list = [board_target]
if not board_target:
board_list = _board_fetch_available_board()
print(f"\033[1m{'board':<16}{'Authors':<16}Description\033[0m")
for board in board_list:
if not (board_info := _board_fetch_info(board)):
continue
content = f"{board:<16}{board_info['author']:<16}{board_info['desc']}"
if verbose:
fake_kernconf = {}
_board_generate_conf(fake_kernconf, board_info)
content += ":\n"
for item in fake_kernconf.items():
content += f" {item[0]:<16} = {item[1]}\n"
print(content)
return 0
#---
# Public
#---
def board_select(kernconf, board):
""" Select a board and generate output information
@args
> kernconf (dict) - kernel configuration information
> board (str) - argument for the selection
@return
> Nothing, hangout if error. (return are only here for syntax sugar)
"""
if not (board_info := _board_fetch_info(board)):
sys.exit(84)
return _board_generate_conf(kernconf, board_info)
def board_command(argv):
""" Board-specific handler
@arg
> argv (list,str) - subcommand arguments
@return
> Never
"""
if not argv:
_warning('missing board argument')
sys.exit(84)
board = ''
verbose = False
for arg in argv:
if arg in ['--list', 'list']:
verbose = False
elif arg in ['--info', 'info']:
verbose = True
else:
if board:
_warning(f"{board} already selected, change for '{arg}'")
board = arg
sys.exit(_board_display_list(verbose, board))

View File

@ -0,0 +1,140 @@
"""
core.build - build abstraction
"""
import os
import sys
from core.logger import log
from core.build.bootloader import bootloader_configure
from core.build.kernel import kernel_configure
from core.build.cmake import cmake_build_compile
from core.build.aslr import aslr_generate
from version import (
# VXGOS_OS_VERSION,
VXGOS_KERNEL_VERSION,
VXGOS_BOOTLOADER_VERSION,
)
__all__ = [
'VxOSBuild',
]
#---
# Public
#---
class VxOSBuild():
""" performs build abstraction
The build system of vxGOS is particular
TODO
"""
def __new__(cls, *_, **__):
""" try to acquire boards required file and load external post-scripts
"""
obj = super().__new__(cls)
obj._target = os.environ['VXSDK_PKG_TARGET']
obj._prefix_base = f"{os.path.dirname(__file__)}/../../../../vxgos/"
obj._prefix_base = os.path.normpath(obj._prefix_base)
try:
sys.path.append(f"{obj._prefix_base}/boards/{obj._target}/")
mod = __import__(
'generate',
fromlist=[
'generate_aslr_blob',
'generate_image'
]
)
error_base_str = f"{obj._target} : `generate.py` do not exposes"
if not hasattr(mod, 'generate_aslr_blob'):
log.emergency(f"{error_base_str} : `generate_aslr_blob()`")
if not hasattr(mod, 'generate_image'):
log.emergency(f"{error_base_str} : `generate_image()`")
obj._postscript = {
'alsr' : mod.generate_aslr_blob,
'image' : mod.generate_image
}
sys.path.pop()
except ImportError as _:
log.error(f"unable to aquire '{obj._target}' post-script")
return obj
def __init__(self, verbose=False):
""" create build abstraction object """
self._verbose = verbose
self._prefix = {
'build' : os.environ['VXSDK_PREFIX_BUILD'],
'bootloader' : f"{self._prefix_base}/bootloader",
#'kernel' : f"{self._prefix_base}/kernel",
}
self._binlist = {
'bootloader' : '',
#'kernel' : '',
#'os' : ''
}
#---
# Attributes
#---
@property
def prefix(self):
""" return the prefix dictionary """
return self._prefix
@property
def target(self):
""" return the selected target """
return self._target
@property
def verbose(self):
""" return if the verbose mode is selected """
return self._verbose
@property
def postscript(self):
""" return if the verbose mode is selected """
return self._postscript
#---
# Public
#---
def configure(self):
""" compile OS/kernel, bootloader and perform post-build scripts """
self._binlist = {
'bootloader': bootloader_configure(self, VXGOS_BOOTLOADER_VERSION),
#'kernel' : kernel_configure(self, VXGOS_KERNEL_VERSION),
#'os' : '',
}
def build(self):
""" compile OS/kernel, bootloader and perform post-build scripts
TODO
"""
binpathlist = {}
log.user("[+] compiling each OS part...")
for partname in self._binlist:
log.user(f"- {partname}...")
binpathlist[partname] = cmake_build_compile(
partname,
f"{self.prefix['build']}/{partname}/",
self.verbose
)
blobpathlist = {}
log.user("[+] generate ASLR information...")
for bininfo in binpathlist.items():
log.user(f"- {bininfo[0]}...")
blobpathlist[bininfo[0]] = aslr_generate(self, bininfo[1])
return self.postscript['image'](
self.prefix['build'],
blobpathlist['bootloader'],
'',
#blobpathlist['kernel'],
#blobpathlist['os']
)

View File

@ -0,0 +1,64 @@
"""
core.aslr - generate ASLR symbols table
"""
import subprocess
from core.logger import log
__all__ = [
'aslr_generate'
]
#---
# Internals
#---
def _fetch_symtab(binpath):
"""
"""
ret = subprocess.run(
f"readelf -r {binpath}".split(),
capture_output=True,
check=False
)
if ret.returncode != 0:
log.error(ret.stderr.decode('utf8'))
symtab = []
for i, line in enumerate(ret.stdout.decode('utf8').splitlines()):
if i < 3:
continue
if len(sinfo := line.split()) != 7:
continue
log.debug(f"[{i}] {sinfo}")
symtab.append(sinfo)
return symtab
def _fetch_sectab(binpath):
"""
"""
ret = subprocess.run(
f"readelf --sections --wide {binpath}".split(),
capture_output=True,
check=False
)
if ret.returncode != 0:
log.error(ret.stderr.decode('utf8'))
sectab = []
for i, line in enumerate(ret.stdout.decode('utf8').splitlines()):
if line.find(' [') != 0:
continue
secinfo = line[7:].split()
log.debug(f"[{i}] {secinfo[0]}")
sectab.append(secinfo)
return sectab
#---
# Public
#---
def aslr_generate(build, binpath):
""" generate ASLR symbols table
"""
symtab = _fetch_symtab(binpath)
sectab = _fetch_sectab(binpath)
return build.postscript['alsr'](binpath, symtab, sectab)

View File

@ -0,0 +1,89 @@
"""
core.build.bootloader - bootloader build abstraction
"""
import subprocess
import glob
from core.logger import log
from core.build.cmake import cmake_build_configure
from core.build.compiles import compiles_fetch
__all__ = [
'bootloader_configure',
]
#---
# Public
#---
def bootloader_configure(build, version):
""" Configure the bootloader
The configuration step is special. It will first fetch all common source
files (stored at <vxgos/bootloader/src/>) then, fetch board-specific files
like extra source file and the linker script.
After theses operation, a CMake file will be generated in the build prefix
(<env['VXSDK_BUILD_PREFIX']/bootloader/>)
"""
prefix = build.prefix['bootloader']
log.debug('fetching bootloader files...')
log.debug(f"> prefix = {prefix}")
log.debug('- fetching compiles informations...')
compiles = compiles_fetch(f"{prefix}/boards/{build.target}/compiles.toml")
if not compiles:
log.emergency(
"unable to find bootloader's compilation information "
"(compiles.toml)"
)
assets_src = []
if compiles.assets:
log.debug('- generate assets sources files...')
cmd = '/usr/bin/env python3 '
cmd += f"{prefix}/../../sdk/converter conv-assets "
cmd += f"{prefix}/assets "
cmd += f"{prefix}/../../.vxsdk/{build.target}/converter/bootloader "
cmd += f"--filter={','.join(compiles.assets)} "
cmd += '--bootloader '
if compiles.toolchain_endian == 'little':
cmd += '--little-endian'
else:
cmd += '--big-endian'
proc = subprocess.run(cmd.split(), capture_output=True, check=False)
if proc.returncode != 0:
log.emergency(
f"unable to convert bootloader assets : {proc.stderr}"
)
assets_src = proc.stdout.decode('utf8').split()
log.debug(f"> assets src = {assets_src}")
common_src = f"{prefix}/src/**/*.[csS]"
common_src = glob.glob(common_src, recursive=True)
log.debug(f"> common src = {common_src}")
board_src = f"{prefix}/boards/{build.target}/src/**/*.[csS]"
board_src = glob.glob(board_src, recursive=True)
log.debug(f"> board src = {board_src}")
prefix = f"{build.prefix['build']}/bootloader/"
include_list = [
f"{build.prefix['bootloader']}/include/",
f"{build.prefix['bootloader']}/boards/{build.target}/include/"
]
return cmake_build_configure(
prefix,
{
'name' : 'bootloader',
'version' : version,
'linker' : f"{build.prefix['bootloader']}/bootloader.ld",
'ldflags' : compiles.ldflags,
'cflags' : compiles.cflags,
'include' : include_list,
'src' : assets_src + common_src + board_src,
'libs' : compiles.libs,
'toolchain_prefix' : compiles.toolchain_prefix,
'toolchain_proc' : compiles.toolchain_processor,
}
)

View File

@ -0,0 +1,186 @@
"""
CMake abstraction
"""
import os
import subprocess
import re
from core.logger import log
__all__ = [
'cmake_build_configure',
'cmake_build_compile',
]
#---
# Internals
#---
_CMAKE_TOOLCHAIN_TEMPLATE = """
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR {VXDEV_TOOLCHAIN_PROCESSOR})
set(CMAKE_C_COMPILER {VXDEV_TOOLCHAIN_PREFIX}gcc)
set(CMAKE_CXX_COMPILER {VXDEV_TOOLCHAIN_PREFIX}g++)
set(CMAKE_C_FLAGS_INIT "")
set(CMAKE_CXX_FLAGS_INIT "")
# required to avoid CMake compiler check fails
add_compile_options(-nostdlib)
add_link_options(-nostdlib)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
""".strip()
_CMAKE_TEMPLATE = """
cmake_minimum_required(VERSION 3.15)
#---
# project-specific information
#---
project({VXDEV_PROJ_NAME} VERSION {VXDEV_PROJ_VERSION} LANGUAGES C ASM)
include_directories(
{VXDEV_BUILD_INCLUDES}
)
add_compile_options(
{VXDEV_BUILD_CFLAGS}
)
add_link_options(
{VXDEV_BUILD_LDFLAGS}
)
set(
CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -T {VXDEV_BUILD_LINKER}"
)
#---
# source files listing
#---
set(
VXDEV_PROJ_SOURCES
{VXDEV_SOURCE_FILES}
)
#---
# commit projet
#---
add_executable({VXDEV_PROJ_NAME} ${VXDEV_PROJ_SOURCES})
target_link_libraries({VXDEV_PROJ_NAME} {VXDEV_BUILD_LIBS})
""".strip()
def _cmakefile_need_update(cmakefile_pathname, content):
""" check if the current CMakeLists.txt need to be recreated
"""
if not os.path.exists(cmakefile_pathname):
return True
with open(cmakefile_pathname, 'r', encoding='ascii') as cmakefile:
if cmakefile.read() != content:
os.remove(cmakefile_pathname)
return True
return False
def _cmake_build_generate_template(prefix, proj):
""" Generate a common CMake file
@args
> proj (dict) - project information
> prefix (str) - build prefix
"""
cmakefile_prefix = f"{prefix}"
cmakefile_pathname = f"{cmakefile_prefix}/CMakeLists.txt"
proj['src'] = '\n '.join(proj['src'])
proj['include'] = '\n '.join(proj['include'])
proj['ldflags'] = '\n '.join(proj['ldflags'])
proj['cflags'] = '\n '.join(proj['cflags'])
proj['libs'] = '\n '.join(proj['libs'])
content = _CMAKE_TEMPLATE
content = re.sub('{VXDEV_PROJ_NAME}', proj['name'], content)
content = re.sub('{VXDEV_PROJ_VERSION}', proj['version'], content)
content = re.sub('{VXDEV_BUILD_CFLAGS}', proj['cflags'], content)
content = re.sub('{VXDEV_BUILD_LDFLAGS}', proj['ldflags'], content)
content = re.sub('{VXDEV_BUILD_LINKER}', proj['linker'], content)
content = re.sub('{VXDEV_SOURCE_FILES}', proj['src'], content)
content = re.sub('{VXDEV_BUILD_INCLUDES}', proj['include'], content)
content = re.sub('{VXDEV_BUILD_LIBS}', proj['libs'], content)
if not _cmakefile_need_update(cmakefile_pathname, content):
return False
if not os.path.exists(cmakefile_prefix):
os.makedirs(cmakefile_prefix)
log.debug(f"generate cmakefile at '{cmakefile_prefix}'...")
with open(cmakefile_pathname, 'x', encoding='ascii') as cmakefile:
cmakefile.write(content)
return True
def _cmake_build_generate_toolchain(prefix, proj):
""" generate the toolchain file
"""
toolchain_proc = proj['toolchain_proc']
toolchain_prefix = proj['toolchain_prefix']
cmakefile_pathname = f"{prefix}/toolchain.cmake"
content = _CMAKE_TOOLCHAIN_TEMPLATE
content = re.sub('{VXDEV_TOOLCHAIN_PROCESSOR}', toolchain_proc, content)
content = re.sub('{VXDEV_TOOLCHAIN_PREFIX}', toolchain_prefix, content)
if not _cmakefile_need_update(cmakefile_pathname, content):
return False
if not os.path.exists(prefix):
os.makedirs(prefix)
log.debug(f"generate cmakefile at '{prefix}'...")
with open(cmakefile_pathname, 'x', encoding='ascii') as cmakefile:
cmakefile.write(content)
return True
#---
# Pulbic
#---
def cmake_build_configure(prefix, proj):
""" Abstract cmake configuration
@args
> name (str) - project name
> prefix (str) - build prefix
"""
toolchain_flag = ''
check = _cmake_build_generate_toolchain(prefix, proj)
if proj['toolchain_prefix']:
check += _cmake_build_generate_template(prefix, proj)
toolchain_flag = f"-DCMAKE_TOOLCHAIN_FILE={prefix}/toolchain.cmake"
if check == 0:
return
shell_cmd = f"cmake {toolchain_flag} -B {prefix} -S {prefix}"
if subprocess.run(shell_cmd.split(), check=False).returncode != 0:
log.emergency(f"{proj['name']}: unable to configure the projet, abord")
def cmake_build_compile(name, prefix, verbose):
""" Abstract cmake configuration
@args
> name (str) - project name
> prefix (str) - build prefix
> verbose (bool) - build verbose
@return
> the generated binary pathname
"""
shell_cmd = f"cmake --build {prefix}"
if verbose:
shell_cmd += ' --verbose'
if subprocess.run(shell_cmd.split(), check=False).returncode != 0:
log.emergency(f"{name}: unable to configure the projet, abord")
return f"{prefix}/{name}"

View File

@ -0,0 +1,56 @@
"""
core.build.compiles - handle "compiles.toml" files
"""
import dataclasses
import toml
from core.logger import log
__all__ = [
'compiles_fetch',
]
#---
# Internals
#---
@dataclasses.dataclass
class CompilesInfo():
""" board compilation information """
toolchain_prefix = ''
toolchain_processor = ''
toolchain_endian = ''
cflags = []
ldflags = []
libs = []
assets = []
#---
# Public
#---
def compiles_fetch(filename):
""" try to load the compiles.toml file
"""
board_info = toml.load(filename)
info = CompilesInfo()
check = 'VXDEV_TOOLCHAIN_PREFIX' in board_info
check += 'VXDEV_TOOLCHAIN_PROCESSOR' in board_info
check += 'VXDEV_TOOLCHAIN_ENDIANNESS' in board_info
if check != 3:
log.emergency(f"{filename}: missing toolchain information, abord")
info.toolchain_prefix = board_info['VXDEV_TOOLCHAIN_PREFIX']
info.toolchain_processor = board_info['VXDEV_TOOLCHAIN_PROCESSOR']
info.toolchain_endian = board_info['VXDEV_TOOLCHAIN_ENDIANNESS']
if 'VXDEV_CFLAGS' in board_info:
info.cflags = board_info['VXDEV_CFLAGS']
if 'VXDEV_LDFLAGS' in board_info:
info.ldflags = board_info['VXDEV_LDFLAGS']
if 'VXDEV_LIBS' in board_info:
info.libs = board_info['VXDEV_LIBS']
if 'VXDEV_ASSETS' in board_info:
info.assets = board_info['VXDEV_ASSETS']
return info

View File

@ -0,0 +1,139 @@
"""
vxdev.core.build.kernel - kernel build abstraction
"""
import os
import glob
import toml
from core.logger import log
from core.build.cmake import cmake_build_configure
from core.build.compiles import compiles_fetch
__all__ = [
'kernel_configure',
]
#---
# Internals
#---
def _fetch_board_config(build, board_cfg_path):
""" try to find and load the board configuratio file
This routine will check the TOML boards configuration validity and return
only the 'config' section which contain most of the information needed to
fetch all kernel source files
@return
> a dictionary with all configuration information
> give up if an error is detected
"""
if not os.path.exists(board_cfg_path):
log.emergency("unable to find the kernel '{build.target}' board file")
log.debug(f"> board config = {board_cfg_path}")
board_cfg = toml.load(board_cfg_path)
if 'meta' not in board_cfg:
log.emergency(f"{build.target}: missing board 'meta' information")
if 'config' not in board_cfg:
log.emergency(f"{build.target}: missing board 'config' information")
if 'modules' not in board_cfg['config']:
log.emergency(f"{build.target}: missing board 'modules' information")
if 'drivers' not in board_cfg['config']:
log.emergency(f"{build.target}: missing board 'drivers' information")
return board_cfg['config']
def _fetch_board_sources(build, prefix):
""" fetch board specific sources files
Scan the target board source directory (<prefix>/src/) and fetch all C
and assembly file.
@return
> a list of all source file found
"""
prefix = f"{prefix}/boards/{build.target}/src"
if not os.path.exists(prefix):
return glob.glob(f"{prefix}/**/*.[csS]", recursive=True)
return []
def _fetch_common_sources(_, prefix):
""" fetch common source file
"""
return glob.glob(f"{prefix}/src/*.[csS]", recursive=False)
def _fetch_drivers_sources(build, prefix, drvlist):
""" fetch selected drivers
"""
sources = []
for drv in drvlist:
if not os.path.exists(f"{prefix}/src/drivers/{drv}"):
log.emergency(f"{build.target}: unable to find driver '{drv}'")
sources += glob.glob(
f"{prefix}/src/drivers/{drv}/**/*.[csS]",
recursive=True
)
return sources
def _fetch_modules_sources(build, prefix, modlist):
""" fetch selected kernel module
"""
sources = []
for mod in modlist:
if not os.path.exists(f"{prefix}/src/modules/{mod}"):
log.emergency(f"{build.target}: unable to find module '{mod}'")
sources += glob.glob(
f"{prefix}/src/modules/{mod}/**/*.[csS]",
recursive=True
)
return sources
#---
# Public
#---
def kernel_configure(build, version):
""" configure the kernel
TODO
"""
prefix = build.prefix['kernel']
log.debug('fetching kernel files...')
log.debug(f"> prefix = {prefix}")
log.debug('- fetching compiles informations...')
compiles = compiles_fetch(f"{prefix}/boards/{build.target}/compiles.toml")
if not compiles:
log.emergency(
"unable to find kernel's compilation information (compiles.toml)"
)
board_cfg = _fetch_board_config(
build,
f"{prefix}/boards/{build.target}/board.toml"
)
sources = _fetch_board_sources(build, prefix)
sources += _fetch_common_sources(build, prefix)
sources += _fetch_drivers_sources(build, prefix, board_cfg['drivers'])
sources += _fetch_modules_sources(build, prefix, board_cfg['modules'])
log.debug("> fetched sources = {sources}")
prefix = f"{build.prefix['build']}/kernel/"
include_list = [
f"{build.prefix['kernel']}/include/",
f"{build.prefix['kernel']}/boards/{build.target}/include/"
]
return cmake_build_configure(
prefix,
{
'name' : 'kernel',
'version' : version,
'linker' : f"{build.prefix['kernel']}/kernel.ld",
'ldflags' : compiles.ldflags,
'cflags' : compiles.cflags,
'include' : include_list,
'src' : sources,
'libs' : compiles.libs,
'toolchain_prefix' : compiles.toolchain_prefix,
'toolchain_proc' : compiles.toolchain_processor,
}
)

View File

@ -1,65 +0,0 @@
"""
CMake abstraction
"""
import os
import sys
import subprocess
__all__ = [
'cmake_configure',
'cmake_build',
'cmake_install',
'cmake_uninstall',
]
#---
# Pulbic
#---
def cmake_configure(prefix_build, prefix_src):
""" Abstract cmake configuration """
toolchain_flag = ''
if toolchain_path := os.environ.get('VXSDK_HOOK_CMAKE_TOOLCHAIN'):
toolchain_flag = f"-DCMAKE_TOOLCHAIN_FILE={toolchain_path}"
shell_cmd = f"cmake {toolchain_flag} -B {prefix_build} -S {prefix_src}"
return subprocess.run(shell_cmd.split(), check=False).returncode
def cmake_build(prefix_build, verbose):
""" Abstract cmake configuration """
shell_cmd = f"cmake --build {prefix_build}"
if verbose:
shell_cmd += ' --verbose'
return subprocess.run(shell_cmd.split(), check=False).returncode
def cmake_install(prefix_build, verbose):
""" Abstract cmake installation """
shell_cmd = f"cmake --install {prefix_build}"
if verbose:
shell_cmd += ' --verbose'
return subprocess.run(shell_cmd.split(), check=False).returncode
def cmake_uninstall(prefix_build, verbose):
""" Abstract cmake uninstall
Note that CMake does not offert a easy way to uninstall project, but it
generate a file which contains all installed pathname
"""
manifile = f"{prefix_build}/install_manifest.txt"
if not os.path.exists(manifile):
print('project not installed')
return -1
retcode = 0
with open(manifile, 'r', encoding='utf8') as manifest:
for pathname in manifest.readlines():
pathname = pathname.strip()
if not os.path.exists(pathname):
continue
if verbose:
print("-- Removing {pathname}")
ret = subprocess.run(f"rm {pathname}".split(), check=False)
if ret.returncode == 0:
continue
print("warning : error during removing file", file=sys.stderr)
retcode -= 1
return retcode

View File

@ -1,38 +0,0 @@
"""
Configuration file handling
"""
import os
__all__ = [
'config_file_generate'
]
#---
# Public
#---
def config_file_generate(kernconf, confile_prefix):
""" Generate the kernel configuration file for CMake build system
@args
> kernconf (dict) - kernel configuration information
> confile_prefix (str) - build prefix
@return
> 0 on success, negative value otherwise
"""
if not os.path.exists(confile_prefix):
os.makedirs(confile_prefix)
content = "# file generated by the vxSDK\n\n"
for item in kernconf.items():
if not (data := item[1]):
continue
if isinstance(item[1], list):
data = ' '.join(item[1])
content += f"set({item[0]} {data})\n"
confile_path = f"{confile_prefix}/vxkernel_config.cmake"
with open(confile_path, 'w+', encoding='utf8') as file:
file.write(content)
return 0

View File

@ -1,34 +0,0 @@
"""
Format handling
"""
__all__ = [
'format_select',
'format_command',
]
#---
# Public
#---
def format_select(_, __):
""" Select a kernel format and generate output information
@args
> kernconf (dict) - kernel configuration information
> format (str) - targeted format
@return
> Nothing, hangout if error. (return are only here for syntax sugar)
"""
def format_command(_):
""" format-specific handler
@arg
> argv (list,str) - subcommand arguments
@return
> Never
"""

View File

@ -0,0 +1,127 @@
"""
core.log - logger object
"""
import sys
__all__ = [
'log'
]
#---
# Internals
#---
_LOG_DEBUG = 7
_LOG_INFO = 6
_LOG_NOTICE = 5
_LOG_USER = 4
_LOG_WARN = 3
_LOG_ERR = 2
_LOG_CRIT = 1
_LOG_EMERG = 0
class _VxLogger():
def __init__(self, logfile=None):
self._logfile = logfile
self._level = _LOG_USER
self._indent = 0
#---
# Internals
#---
def _print(self, level, text, skip_indent, fileno):
if self._level < level:
return 0
if not skip_indent and self._level == _LOG_DEBUG and self._indent > 0:
text = ('>>> ' * self._indent) + text
print(text, file=fileno, end='', flush=True)
return len(text) + 1
#---
# Public properties
#---
@property
def level(self):
""" <property> handle print level """
return self._level
@level.setter
def level(self, level):
""" <property> handle print level """
if level < _LOG_EMERG or level > _LOG_DEBUG:
print(f"[log] level update to {level} is not possible, ignored")
return
self._level = level
@property
def indent(self):
""" <property> handle indentation level for _LOG_DEBUG """
return self._indent
@indent.setter
def indent(self, indent):
""" <property> handle indentation level for _LOG_DEBUG """
if indent < 0:
print(f"[log] indent update to {indent} is not possible, ignored")
return
self._indent = indent
#---
# Public methods
#---
def debug(self, text, end='\n', skip_indent=False):
""" print debug log """
return self._print(
_LOG_DEBUG, f"[DEBUG] {text}{end}", skip_indent, sys.stdout
)
def info(self, text, end='\n', skip_indent=False):
""" print info log """
return self._print(
_LOG_INFO, f"[INFO] {text}{end}", skip_indent, sys.stdout
)
def notice(self, text, end='\n', skip_indent=False):
""" print notice log """
return self._print(
_LOG_NOTICE, f"[NOTICE] {text}{end}", skip_indent, sys.stdout
)
def user(self, text, end='\n', skip_indent=False):
""" print user log """
return self._print(_LOG_USER, f"{text}{end}", skip_indent, sys.stdout)
def warn(self, text, end='\n', skip_indent=False):
""" print warning log """
return self._print(
_LOG_WARN, f"[WARN] {text}{end}", skip_indent, sys.stderr
)
def error(self, text, end='\n', skip_indent=False):
""" print error log """
return self._print(
_LOG_ERR, f"[ERROR] {text}{end}", skip_indent, sys.stderr
)
def critical(self, text, end='\n', skip_indent=False):
""" print critical log """
return self._print(
_LOG_CRIT, f"[CRITICAL] {text}{end}", skip_indent, sys.stderr
)
def emergency(self, text, end='\n', skip_indent=False):
""" print emergency log """
self._print(
_LOG_EMERG, f"[EMERGENCY] {text}{end}", skip_indent, sys.stderr
)
sys.exit(84)
#---
# Public functions
#---
log = _VxLogger()

View File

@ -0,0 +1,78 @@
"""
core.unitests - unit tests abstraction
"""
import os
import subprocess
from core.logger import log
from core.unitests.bootloader import bootloader_unitest_config
from core.unitests.cmake import cmake_unitest_compile
from core.unitests.criterion import criterion_unitests_run
from core.unitests.gcovr import gcovr_unitests_run
__all__ = [
'VxOSUnitest'
]
#---
# Public
#---
class VxOSUnitest():
""" Unit tests abstraction
"""
def __init__(self, verbose):
self._verbose = verbose
prefix_base = f"{os.path.dirname(__file__)}/../../../../"
prefix_base = os.path.normpath(prefix_base)
self._prefix = {
'base' : prefix_base,
'build' : f"{prefix_base}/.vxsdk/unitests",
'src' : f"{prefix_base}/vxgos",
}
self._unitpath = {
'bootloader' : [],
'kernel' : [],
'os' : [],
}
#---
# Properties
#---
@property
def prefix(self):
""" return the prefix dictionary """
return self._prefix
#---
# Methods
#---
def configure(self):
""" generate all unitests env """
self._unitpath['bootloader'] = bootloader_unitest_config(self)
def execute(self):
""" execute all unitests
"""
bininfo_list = []
log.user('- compile unitests...')
for item in self._unitpath.items():
for unitest in item[1]:
log.user(f" > {item[0]}:{unitest['name']}...")
bininfo_list.append((
f"{item[0]}:{unitest['name']}",
cmake_unitest_compile(
unitest['name'],
unitest['prefix'],
self._verbose
),
unitest['requirements']
))
log.user('- execute unitests...')
for bininfo in bininfo_list:
log.user(f" > {bininfo[0]}...")
if criterion_unitests_run(bininfo) != 0:
continue
gcovr_unitests_run(bininfo)

View File

@ -0,0 +1,60 @@
"""
core.unitests.bootloader - bootloader unit tests abstraction
"""
import os
import glob
from core.logger import log
from core.unitests.requirements import requirements_unitests_parse
from core.unitests.cmake import cmake_unitest_configure
__all__ = [
'bootloader_unitest_config'
]
#---
# Internals
#---
def _bootloader_unitest_new(unitest, prefix, test):
""" add a new bootloader unitest
"""
log.debug(f"- new unitest '{test}'...")
req = requirements_unitests_parse(prefix, 'bootloader', test)
log.debug(f"> deplist = {req}")
srclist = f"{prefix}/unitests/{test}/**/*.[csS]"
srclist = glob.glob(srclist, recursive=True)
log.debug(f"> srclist = {srclist}")
include_list = [
f"{prefix}/include/",
f"{prefix}/unitests/{test}/"
]
return {
'name' : test,
'prefix' : cmake_unitest_configure(
f"{unitest.prefix['build']}/bootloader/{test}",
{
'name' : test,
'src' : req['covered'] + req['dependencies'] + srclist,
'include' : req['includes'] + include_list
}
),
'requirements' : req
}
#---
# Public
#---
def bootloader_unitest_config(unitest):
""" bootloader unitests abstraction entry
"""
unitpath = []
prefix = f"{unitest.prefix['src']}/bootloader"
for test in os.listdir(f"{prefix}/unitests/"):
unitpath.append(_bootloader_unitest_new(unitest, prefix, test))
return unitpath

View File

@ -0,0 +1,135 @@
"""
core.unitests.cmake - CMake abstraction for unitests
"""
import os
import subprocess
import re
from core.logger import log
__all__ = [
'cmake_unitest_configure',
'cmake_unitest_compile',
]
#---
# Internals
#---
_CMAKE_TEMPLATE = """
cmake_minimum_required(VERSION 3.15)
#---
# project-specific information
#---
project({VXDEV_PROJ_NAME} VERSION 0.1.0 LANGUAGES C ASM)
include_directories(
{VXDEV_BUILD_INCLUDES}
)
add_compile_options(
-Wall
-Wextra
-std=c2x
-g3
--coverage
-fsanitize=address
)
add_link_options(
--coverage
-lasan
-lcriterion
)
#---
# source files listing
#---
set(
VXDEV_PROJ_SOURCES
{VXDEV_SOURCE_FILES}
)
#---
# commit projet
#---
add_executable({VXDEV_PROJ_NAME} ${VXDEV_PROJ_SOURCES})
target_link_libraries({VXDEV_PROJ_NAME})
""".strip()
def _cmakefile_need_update(cmakefile_pathname, content):
""" check if the current CMakeLists.txt need to be recreated
"""
if not os.path.exists(cmakefile_pathname):
return True
with open(cmakefile_pathname, 'r', encoding='ascii') as cmakefile:
if cmakefile.read() != content:
os.remove(cmakefile_pathname)
return True
return False
def _cmake_unitest_generate_template(prefix, proj):
""" Generate a common CMake file
@args
> proj (dict) - project information
> prefix (str) - build prefix
"""
cmakefile_prefix = f"{prefix}"
cmakefile_pathname = f"{cmakefile_prefix}/CMakeLists.txt"
proj['src'] = '\n '.join(proj['src'])
proj['include'] = '\n '.join(proj['include'])
content = _CMAKE_TEMPLATE
content = re.sub('{VXDEV_PROJ_NAME}', proj['name'], content)
content = re.sub('{VXDEV_SOURCE_FILES}', proj['src'], content)
content = re.sub('{VXDEV_BUILD_INCLUDES}', proj['include'], content)
if not _cmakefile_need_update(cmakefile_pathname, content):
return False
if not os.path.exists(cmakefile_prefix):
os.makedirs(cmakefile_prefix)
log.debug(f"generate cmakefile at '{cmakefile_prefix}'...")
with open(cmakefile_pathname, 'x', encoding='ascii') as cmakefile:
cmakefile.write(content)
return True
#---
# Pulbic
#---
def cmake_unitest_configure(prefix, proj):
""" Abstract cmake configuration
@args
> name (str) - project name
> prefix (str) - build prefix
"""
if not _cmake_unitest_generate_template(prefix, proj):
return prefix
shell_cmd = f"cmake -B {prefix} -S {prefix}"
if subprocess.run(shell_cmd.split(), check=False).returncode != 0:
log.emergency(f"{proj['name']}: unable to configure the projet, abord")
return prefix
def cmake_unitest_compile(name, prefix, verbose):
""" Abstract cmake configuration
@args
> name (str) - project name
> prefix (str) - build prefix
> verbose (bool) - build verbose
@return
> the generated binary pathname
"""
shell_cmd = f"cmake --build {prefix}"
if verbose:
shell_cmd += ' --verbose'
if subprocess.run(shell_cmd.split(), check=False).returncode != 0:
log.emergency(f"{name}: unable to configure the projet, abord")
return f"{prefix}/{name}"

View File

@ -0,0 +1,29 @@
"""
unitests.criterion - criterion abstraction
"""
import os
import glob
import subprocess
from core.logger import log
__all__ = [
'criterion_unitests_run'
]
#---
# Public
#---
def criterion_unitests_run(bininfo):
""" run criterion unitests (purge converage informaiton)
"""
log.debug(f"{bininfo[0]}: purge GCDA file...")
query = f"{os.path.dirname(bininfo[1])}/**/*.gcd[ao]"
for gcda in glob.glob(query, recursive=True):
log.debug(f"- {gcda}...")
os.remove(gcda)
if subprocess.run([bininfo[1]], check=False).returncode != 0:
log.error(f"{bininfo[0]}: unitest error, skipped code coverage")
return -2
return 0

View File

@ -0,0 +1,73 @@
"""
core.unitests.gcovr - gcovr abstraction
"""
import os
import subprocess
from core.logger import log
#---
# Internals
#---
def _gcovr_parse_output(output, covered_list):
""" fetch only line information
"""
lineinfo = []
workaround_line = None
for line in output.decode('ascii').split('\n'):
if not (line := line.split()):
continue
if workaround_line:
lineinfo.append(workaround_line + line)
workaround_line = None
continue
find = False
for filename in covered_list:
if filename.find(line[0]) >= 0:
find = True
break
if not find:
continue
if len(line) < 2:
workaround_line = line
continue
lineinfo.append(line)
return lineinfo
def _gcovr_display_output(lineinfo):
""" proper display line information
"""
maxwidth = 0
for line in lineinfo:
maxwidth = max(len(line[0]), maxwidth)
for line in lineinfo:
percent = int(line[3][:-1])
color = '\033[31m'
if 50 <= percent <= 90:
color = '\033[33m'
if percent >= 90:
color = '\033[32m'
if percent == 100:
log.user(f"[\033[92m{line[3]: >4}\033[0m] {line[0]: <{maxwidth}}")
continue
log.user(
f"[{color}{line[3]: >4}\033[0m] {line[0]: <{maxwidth}} {line[4]}"
)
#---
# Public
#---
def gcovr_unitests_run(bininfo):
""" run code coverage
"""
pwd = os.path.dirname(bininfo[1])
cmd = f"gcovr {pwd}"
proc = subprocess.run(cmd.split(), capture_output=True, check=False)
if proc.returncode != 0:
log.error(f"{bininfo[0]}: code coverage error, skipped")
log.error(proc.stderr.decode('utf8'))
return -1
lineinfo = _gcovr_parse_output(proc.stdout, bininfo[2]['covered'])
_gcovr_display_output(lineinfo)
return 0

View File

@ -0,0 +1,68 @@
"""
core.unitests.requirements - requirements.txt file parser
"""
import os
from core.logger import log
__all__ = [
'requirements_unitests_parse'
]
#---
# Public
#---
def requirements_unitests_parse(prefix, suite, test):
""" parse requirements.txt file for unitests
@args
> prefix (str) - unitests root dir
> suite (str) - unitests suite (bootloader, kernel, ...)
> test (str) - unitests name
@return
> a dictionary with parsed information
"""
testname = f"{suite}:{test}"
pathname = f"{prefix}/unitests/{test}/requirements.txt"
if not os.path.exists(prefix):
log.emergency(f"{pathname}: file doesn't exists, skipped")
line_id = 0
requirement = {
'covered' : [],
'dependencies' : [],
'includes' : []
}
mapping = {
'c' : requirement['covered'],
'd' : requirement['dependencies'],
'i' : requirement['includes'],
}
with open(pathname, 'r', encoding='ascii') as reqfile:
while True:
line_id += 1
if not (line := reqfile.readline()):
break
if not (line := line.strip()):
continue
if line.strip()[0] == '#':
continue
line = line.split()
if line[0][0] != '[' or line[0][2] != ']':
log.warn(f"{testname}:requirements.txt:{line_id}: line error")
continue
if line[0][1] not in mapping:
log.warn(f"{testname}: unsupported '{line[0][1]}' flag")
continue
real_filename = f"{prefix}/{line[1]}"
if not os.path.exists(real_filename):
log.emergency(
f"{testname}: dependency '{real_filename}'"
"not exists"
)
mapping[line[0][1]].append(real_filename)
log.debug(f"- requirements.txt : {requirement}")
return requirement

17
scripts/vxdev/version.py Normal file
View File

@ -0,0 +1,17 @@
"""
version - centralize all version information
"""
__all__ = [
'VXGOS_OS_VERSION',
'VXGOS_KERNEL_VERSION',
'VXGOS_BOOTLOADER_VERSION',
]
#---
# Public
#---
VXGOS_OS_VERSION = '0.0.0'
VXGOS_KERNEL_VERSION = '0.7.0'
VXGOS_BOOTLOADER_VERSION = '0.0.1'

23
sdk/converter/__main__.py Normal file
View File

@ -0,0 +1,23 @@
"""
sdk - vxGOS SDK entry
"""
import sys
from cli import cli_parse
#---
# Internals
#---
def _main(argv):
""" real entry """
if argv[0].find('conv') == 0:
return cli_parse(argv)
print(f"not supported {argv[0]}", file=sys.stderr)
return -1
#---
# Public
#---
sys.exit(_main(sys.argv[1:]))

View File

@ -0,0 +1,63 @@
"""
cli.conv - assset conversion abstraction
"""
from core.logger import log
from cli.doctor import doctor_conv_cli
from cli.assets import assets_conv_cli
from cli.addin import addin_conv_cli
__all__ = [
'__VXSDK_PLUGIN_META__',
'cli_validate',
'cli_parse',
]
#---
# Public
#---
__VXSDK_PLUGIN_META__ = (
['conv'],
'vxGOS assets converter',
"""vxsdk-conv
Project assets converter
USAGE:
vxsdk vxgos conv(-<ACTION>) [OPTIONS] ...
DESCRIPTION:
Convert vxGOS project assets (or binary) into various form. By default, if
no action is specified, the "assets" conversion is selected.
ACTIONS:
asset convert asset into source file or binary file
addin convert binary into addin file for vxOS
doctor try to display assets and addin information (debug)
See `vxsdk vxgos conv-<action> --help` for more information on a specific
action
"""
)
def cli_validate(name):
""" validate the module name """
return name.find('conv') == 0
def cli_parse(argv):
""" Build subcommand entry """
if '--help' in argv or '-h' in argv:
log.user(__VXSDK_PLUGIN_META__[2])
return 0
if argv[0].find('conv-') != 0:
argv[0] = 'conv-asset'
action = argv[0][5:]
if action == 'doctor':
return doctor_conv_cli(argv[1:])
if action == 'assets':
return assets_conv_cli(argv[1:])
if action == 'addin':
return addin_conv_cli(argv[1:])
log.error(f"unable to find action '{action}'")
return 84

View File

@ -0,0 +1,69 @@
"""
cli.conv.addin - vxSDK addin conversion
"""
import sys
#from core.conv.addin import generate_addin
from core.logger import log
__all__ = [
'addin_conv_cli'
]
#---
# Internals
#---
__HELP__ = """vxsdk-converter-addin
Converte binary file into Vhex OS addin.
USAGE:
vxsdk conv addin -b BINARY ...
DESCRIPTION:
Convert a binary file into an application for the Vhex operating system.
OPTIONS:
-b <binary path> ELF file (no check is performed in this file)
-i <icon path> 92x62 pixel image path
-o <output path> output path for the generated addin
-n <internal name> internal addin name
-v <internal version> internal addin version
"""
#---
# Public
#---
def addin_conv_cli(argv):
"""Process CLI arguments"""
if '-h' in argv or '--help' in argv:
log.user(__HELP__)
sys.exit(0)
action = None
info = [None, None, None, None, None]
for arg in argv:
if action == '-b':
info[0] = arg
if action == '-i':
info[1] = arg
if action == '-n':
info[2] = arg
if action == '-o':
info[3] = arg
if action == '-v':
info[4] = arg
if action:
action = None
continue
if arg in ['-b', '-i', '-n', '-o', '-v']:
action = arg
continue
if not info[0]:
log.error('converter: need binary path !')
sys.exit(84)
log.error('not supported addin convertion')
#return generate_addin(info[0], info[1], info[2], info[3], info[4])

101
sdk/converter/cli/assets.py Normal file
View File

@ -0,0 +1,101 @@
"""
cli.conv.assets - Vhex asset converter user interface
"""
import os
import dataclasses
from core.logger import log
from core import assets_generate
__all__ = [
'assets_conv_cli'
]
#---
# Internals
#---
__HELP__ = """vxsdk-converter-asset
Convert all assets file in the project directory.
USAGE:
vxsdk vxgos conv-asset <assets prefix> <output prefix>
DESCRIPTION:
Convert all assets file in the asset directory. This part of the converter
module will scan the provided folder (or the current working directory) and
will try to find all `vxconv.txt` file, which describe all assets that
should be converted.
If no argument is provided, then the current working directory is used as
asset prefix and a storag for all generated source file. You can modify
this behaviour using OPTIONS.
For more information about the `vxconv.txt` in the wiki.
OPTIONS:
-f, --force Force assets generation even if they exist
--bootloader Generate bootloader font encoding
--kernel Generate kernel font encoding
--os Generate OS font encoding (default)
--little-endian Encode assets information in little endian format
--big-endian Encode assets information in big endian format
-h, --help Display this help
"""
@dataclasses.dataclass
class _ConvertAssetInfo():
""" Assets converter information """
force = False
endianness = 'little'
generator = 'os'
prefix_output = ''
prefix_assets = ''
assets_filter = []
#---
# Public
#---
def assets_conv_cli(argv):
"""Process CLI arguments"""
# check obvious flags
if '-h' in argv or '--help' in argv:
log.user(__HELP__)
return 0
# fetch user indication
info = _ConvertAssetInfo()
for arg in argv:
if arg in ['-f', '--force']:
info.force = True
continue
if arg == '--bootloader':
info.generator = 'bootloader'
continue
if arg == '--little-endian':
info.endianness = 'little'
continue
if arg == '--big-endian':
info.endianness = 'big'
continue
if not info.prefix_assets:
info.prefix_assets = arg
continue
if arg.find('--filter=') == 0:
info.assets_filter = arg[9:].split(',')
continue
if info.prefix_output:
log.warn(f"previous output path ({info.prefix_output}) dropped")
info.prefix_output = arg
# check indication
if not info.prefix_assets:
log.emergency('missing assets prefix')
if not info.prefix_assets:
log.emergency('missing output prefix')
info.prefix_assets = os.path.abspath(info.prefix_assets)
info.prefix_output = os.path.abspath(info.prefix_output)
# generate assets information
return assets_generate(info)

View File

@ -0,0 +1,24 @@
"""
cli.conv.doctor - vxSDK converter doctor.
"""
import sys
from core.logger import log
__all__ = [
'doctor_conv_cli'
]
#---
# Public
#---
def doctor_conv_cli(_):
"""Process CLI handling
TODO:
> give asset file description to check error
> try to display asset and addin information based on the project type
"""
log.warn('conv: doctor action not implemented yet')
sys.exit(84)

View File

@ -0,0 +1,4 @@
"""
core.conv - Vhex converter module
"""
from core.assets import assets_generate

View File

@ -0,0 +1,67 @@
#from core.conv.pixel import rgba8conv
#
#from PIL import Image
#import os
#
#__all__ = [
# 'generate_addin'
#]
#
#
#def generate_addin(binary, icon=None, name=None, output=None, version=None):
# r"""Generate an addin for the Vhex Operating System.
#
# The addin name (passed through the `name` argument) is optional here. In
# this case, the name will use the internal name...which can be guessed using
# the binary name (e.i '/path/to/the/super_addin.elf' -> internal name =
# 'super_addin' -> output name = '/path/to/the/super_addin').
#
# The output path for the generated file is, by defautl, the same path that
# the binary but the suffix '.vxos' will be added.
#
# if the icon is not specified, a default blank icon will be used.
#
# Args:
# > binary (str) - binary path
# > icon (str) - addin icon path (optional)
# > name (str) - addin name (displayed in the menu) (optional)
# > output (str) - output path for the generated addin (optional)
#
# _fixme:
# > generate default internal name
# > change 8-bits icon into rgb565
# > add internal addin version in the header
# """
# if not os.path.exists(binary):
# logger(LOG_ERR, 'binary path is invalid')
# sys.exit(84)
# if icon and not os.path.exists(icon):
# logger(LOG_WARN, f'{icon}: icon does not exists, ignored')
# icon = None
# if not name:
# name = ''
# if not output:
# output = binary + '.vxos'
#
# if icon:
# bitmap = Image.open(icon)
# if bitmap.size != (92, 64):
# logger(
# LOG_ERR,
# f'{icon}:icon size does not match {bitmap.size} != (92, 64)',
# exit=84
# )
#
# with open(binary, 'rb') as b:
# with open(output, 'wb') as a:
# a.write(b'VHEX')
# a.write(name.encode('utf8'))
# a.write(b'\x00')
# if icon:
# for pixel in bitmap.getdata():
# a.write(rgba8conv(pixel).to_bytes(1, 'big'))
# else:
# a.write(bytes(92*64))
# a.write(b.read())
#
# return 0

View File

@ -0,0 +1,150 @@
"""
core.conv.assets - Vhex assets converter
"""
import os
import toml
from core.logger import log
from core.font import font_generate
from core.image import image_generate
__all__ = [
'assets_generate'
]
#---
# Internals
#---
class _VxAssetException(Exception):
""" simple exception wrapper """
class _VxAsset():
"""Represent a asset object
This is an internal class which represents assets information with some
methods to abstract conversion and file type manipulation (for asset type
font and asset type bitmap).
Also note that this class is private because we use a tricky optimization
to parse the `vxconv.txt` file, this is why we have no "private" property
with setter and getter, and why this class is "hidden".
Some important methods to note:
================================== =======================================
Name Description
================================== =======================================
generate() Generate the source file (C)
================================== =======================================
"""
def __init__(self, prefix, name, meta):
if 'path' not in meta:
raise _VxAssetException(f"[{name}] missing path information")
if 'type' not in meta:
raise _VxAssetException(f"[{name}] missing type information")
if meta['type'] not in ['font', 'image']:
raise _VxAssetException(f"asset type '{meta[type]}' is not known")
self._name = name
self._meta = meta
self._type = meta['type']
self._path = prefix + '/' + meta['path']
if not os.path.exists(self.path):
raise _VxAssetException(
"asset path '{self._path}' cannot be openned"
)
def __repr__(self):
return f'<_VxAssetObj, {self.name}>'
def __str__(self):
content = f"[{self.name}]\n"
content += f" - type: {self.type}\n"
content += f" - path: {self.path}\n"
return content
#---
# Getter
#---
@property
def path(self):
"""<property> path"""
return self._path
@property
def name(self):
"""<property> name"""
return self._name
@property
def type(self):
"""<property> type"""
return self._type
@property
def meta(self):
"""<property> meta"""
return self._meta
#---
# Public method
#---
def generate_source_file(
self,
prefix_output,
generator,
endianness,
force_generate
):
"""generate source file """
function = font_generate if self.type == 'font' else image_generate
return function(
self,
prefix_output,
generator,
endianness,
force_generate
)
#---
# Public
#---
def assets_generate(info):
""" Walk through the assets prefix and generate all source file
@args
> info (dataclass) - contains all information about the conversion
@return
> a list of all generated sources pathname
"""
if not os.path.exists(info.prefix_output):
os.makedirs(info.prefix_output)
for root, _, files in os.walk(info.prefix_assets):
if 'vxconv.toml' not in files:
continue
with open(f"{root}/vxconv.toml", 'r', encoding='utf-8') as inf:
content = toml.loads(inf.read())
for asset_name in content:
if info.assets_filter:
if asset_name not in info.assets_filter:
continue
log.debug(f"converting {asset_name}...")
log.user(
_VxAsset(
root,
asset_name,
content[asset_name]
).generate_source_file(
info.prefix_output,
info.generator,
info.endianness,
info.force
)
)
return 0

View File

@ -0,0 +1,49 @@
"""
core.conv.type.font - Vhex font converter
"""
import os
from core.logger import log
from core.font.meta import font_meta_fetch
from core.font.convert import font_convert
from core.font.generator import font_generate_source_file
__all__ = [
'font_generate'
]
#---
# Public
#---
def font_generate(asset, prefix_output, generator, endian, force_generate):
""" Convert an image asset to a C source file
@args
> asset (_VxAsset) - minimal asset information
> prefix_output (str) - prefix for source file generation
> generator (str) - selected generator for C file content
> endian (str) - selected endianness encoding
> force_generate (bool) - force generate the source file
@return
> pathname of the generated file
"""
# check if the asset already exists
asset_src = f'{prefix_output}/{asset.name}_vxfont.c'
if not force_generate and os.path.exists(asset_src):
log.debug(f"{asset.name}: not generated, cached")
return asset_src
# generate font information
if not (font_info := font_meta_fetch(asset)):
return ''
if font_convert(asset, font_info) != 0:
return ''
# create the source file
content = font_generate_source_file(asset, font_info, generator, endian)
with open(asset_src, "w", encoding='utf8') as file:
file.write(content)
log.debug(f"source file generated at {asset_src}")
return asset_src

View File

@ -0,0 +1,200 @@
"""
core.font.convert - common conversion handling
"""
from PIL import Image
from core.logger import log
from core.font.glyph import glyph_get_wgeometry, glyph_encode
__all__ = [
'font_convert',
]
#---
# Internals
#---
def _font_convert_proportional(packed_info):
""" Generate proportional font
Proportional font means that each character have its own width size (but
have a common height). We need to performs more complexe handling than the
monospaced one.
@args
> asset (VxAsset) - asset information
> font_information (dict) - font indication
@return
> 0 if success, negative value otherwise
"""
# unpack information
font_info = packed_info[0]
img_info = packed_info[1]
glyph_info = packed_info[2]
data_info = packed_info[4]
geometry_info = packed_info[5]
# isolate needed information
img = img_info['obj']
img_raw = img_info['raw']
nb_col = packed_info[3][0]
nb_row = packed_info[3][1]
gwidth = glyph_info[0]
gheight = glyph_info[1]
# main loop, walk glyph per glyph
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
for _ in range(0, nb_row):
_px = font_info['grid_border'] + font_info['grid_padding']
for _ in range(0, nb_col):
# generate width geometry information
glyph_get_wgeometry(
geometry_info,
img_raw,
img.size,
(_px, _py),
(font_info['grid_size_x'], font_info['grid_size_y'])
)
# save critical glyph geometry information that will be encoded in
# the final C source file
font_info['glyph_props'].append((
geometry_info['wend'] - geometry_info['wstart'],
data_info['idx'],
data_info['shift']
))
# encode glyph information
glyph_encode(data_info, img_info, geometry_info, _px, _py)
# update loop information
font_info['glyph_count'] += 1
_px += gwidth
_py += gheight * img.size[0]
return 0
def _font_convert_monospaced(packed_info):
""" Generate proportional font
Proportional font means that each character have its own width size (but
have a common height). We need to performs more complexe handling than the
monospaced one.
@args
> asset (VxAsset) - asset information
> font_information (dict) - font indication
@return
> 0 if success, negative value otherwise
"""
# unpack information
font_info = packed_info[0]
img_info = packed_info[1]
glyph_info = packed_info[2]
grid_info = packed_info[3]
data_info = packed_info[4]
geometry_info = packed_info[5]
# isolate needed information
img = img_info['obj']
nb_row = grid_info[1]
nb_col = grid_info[0]
gwidth = glyph_info[0]
gheight = glyph_info[1]
# main loop, walk glyph per glyph
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
for _ in range(0, nb_row):
_px = font_info['grid_border'] + font_info['grid_padding']
for _ in range(0, nb_col):
glyph_encode(data_info, img_info, geometry_info, _px, _py)
font_info['glyph_count'] += 1
_px += gwidth
_py += gheight * img.size[0]
return 0
#---
# Public
#---
def font_convert(asset, font_info):
""" Generate font information
@args
> asset (VxAsset) - asset information
> font_info (dict) - font information
@return
> 0 if success, negative value otherwise
"""
# generate image information
img = Image.open(asset.path)
img_raw = img.getdata()
img_info = {
'obj' : img,
'raw' : img_raw
}
# pre-calculate the "real" glyph width and height using padding information
glyph_info = [0, 0]
glyph_info[0] = font_info['grid_size_x'] + font_info['grid_padding']
glyph_info[1] = font_info['grid_size_y'] + font_info['grid_padding']
gheight = glyph_info[1]
gwidth = glyph_info[0]
log.debug(f"gwidth = {gwidth} && gheight = {gheight}")
# pre-calculate the number of row and column of the font
grid_info = [0, 0]
grid_info[0] = int((img.size[0] - (font_info['grid_border'] * 2)) / gwidth)
grid_info[1] = int((img.size[1] - (font_info['grid_border'] * 2)) /gheight)
nb_col = grid_info[0]
nb_row = grid_info[1]
log.debug(f"nb_row = {nb_row} && nb_col = {nb_col}")
# pre-calculate and prepare per-glyph information
# @note
# The generated data is designed for 4-alignement padding. This to have
# speed-up on drawing function.
font_info['glyph_size'] = font_info['grid_size_x']
font_info['glyph_size'] *= font_info['grid_size_y']
font_info['font_size'] = font_info['glyph_size'] * nb_row * nb_col
font_info['glyph_count'] = 0
font_info['glyph_props'] = []
font_info['data'] = [0] * int((font_info['font_size'] + 31) / 32)
log.debug(f"data original = {id(font_info['data'])}")
# generate data information
data_info = {
'table' : font_info['data'],
'idx' : 0,
'shift' : 0
}
log.debug(f"data packed = {id(data_info['table'])}")
# generate geometry information
geometry_info = {
'hstart' : 0,
'hend' : font_info['grid_size_y'],
'wstart' : 0,
'wend' : font_info['grid_size_x'],
}
# select the converter
converter = _font_convert_monospaced
if font_info['is_proportional']:
converter = _font_convert_proportional
# convert font
converter((
font_info,
img_info,
glyph_info,
grid_info,
data_info,
geometry_info
))
log.debug(f"data packed end = {id(data_info['table'])}")
return 0

View File

@ -0,0 +1,35 @@
"""
core.font.generator - generator abstraction
"""
from core.logger import log
from core.font.generator.bootloader import font_generate_bootloader_source_file
#---
# Public
#---
def font_generate_source_file(asset, font_info, generator, endianness):
""" return the C source file based on the selected generator
@args
> asset (_VxAsset) - minimal asset information
> font_info (dict) - font information
> generator (str) - selected generator for C file content
> endianness (str) - selected endianness encoding
@return
> file content (str)
"""
if generator not in ['bootloader', 'kernel', 'os']:
log.emergency(f"not supported generator '{generator}'")
if generator == 'bootloader':
return font_generate_bootloader_source_file(
asset,
font_info,
endianness
)
log.emergency(f"generator '{generator}' not implemented yet o(x_x)o")
return ''

View File

@ -0,0 +1,102 @@
"""
core.font.generator.bootloader - bootloader font generator
"""
from core.logger import log
__all__ = [
'font_generate_bootloader_source_file'
]
#---
# Internals
#---
def _u32_conv_little_endian(x32):
# return ((x32 & 0xff000000) >> 24) \
# | ((x32 & 0x00ff0000) >> 8) \
# | ((x32 & 0x0000ff00) << 8) \
# | ((x32 & 0x000000ff) << 24)
return x32
def _u32_conv_big_endian(x32):
return x32
def _font_generate_normal_source(asset, font_info, endianness):
""" Print chaset is a image file
Generate a C font file content based on bootloader font internal
structure and header declaration.
@args
> asset (VxAsset) - asset information
> info (dict) - hold font information
> endianness (str) - selected endianness encoding
@return
> complet font file content
"""
# generate basic header
content = '#include "bootloader/display.h"\n'
content += '\n'
content += f"/* {asset.name} - Vhex asset\n"
content += ' This object has been converted by using the vxSDK '
content += 'converter */\n'
content += f"struct font const {asset.name} = " + "{\n"
# handle endianness
u32 = _u32_conv_little_endian
if endianness != 'little':
u32 = _u32_conv_big_endian
# encode font bitmap
line = 0
log.debug(f"data = {font_info['data']}")
content += " .data = (uint32_t[]){\n"
for pixel in font_info['data']:
if line == 0:
content += ' '
if line >= 1:
content += ' '
content += f"{u32(pixel):#010x},"
if (line := line + 1) == 6:
content += '\n'
line = 0
if line != 0:
content += '\n'
content += ' },\n'
# indicate other font information
content += f" .count = {font_info['glyph_count']},\n"
content += f" .height = {font_info['glyph_height']},\n"
content += f" .width = {font_info['grid_size_x']},\n"
content += f" .line_height = {font_info['line_height']},\n"
content += f" .char_block_size = {font_info['glyph_size']},\n"
# closure and return
content += '};\n'
return content
#---
# Public
#---
def font_generate_bootloader_source_file(asset, font_info, endianness):
""" Generate font source file content
@args
> asset (VxAsset) - asset information
> info (dict) - hold font information
> endianness (str) - selected endianness encoding
@return
> file C content string
"""
# check font validity (common vxconv.toml parser)
if font_info['is_proportional']:
log.emergency(f"{asset.name}: unsupported proportional font")
if font_info['charset'] == 'unicode':
log.emergency(f"{asset.name}: unsupported unicode font")
# generate file content
return _font_generate_normal_source(asset, font_info, endianness)

View File

@ -0,0 +1,105 @@
"""
core.font.generator.kernel - kernel font generator
"""
from core.logger import log
#---
# Public
#---
def _font_generate_unicode_source(_):
""" Unicode special chaset directory """
log.error("unicode conversion not implemented yet o(x_x)o")
return ''
def _font_generate_normal_source(font_info):
""" Print chaset is a image file
"""
content = "\t.glyph = {\n"
content += f"\t\t.height = {font_info['glyph_height']},\n"
content += f"\t\t.line_height = {font_info['line_height']},\n"
# encode font bitmap
line = 0
log.debug(f"data = {font_info['data']}")
content += "\t\t.data = (uint32_t[]){\n"
for pixel in font_info['data']:
if line == 0:
content += '\t\t\t'
if line >= 1:
content += ' '
content += f"{pixel:#010x},"
if (line := line + 1) == 4:
content += '\n'
line = 0
if line != 0:
content += '\n'
content += '\t\t},\n'
# indicate the number of glyph in the bitmap
content += f"\t\t.count = {font_info['glyph_count']},\n"
# encode proportional information if needed
if font_info['is_proportional']:
content += '\t\t.prop = (struct __workaround[]){\n'
for prop in font_info['glyph_props']:
content += "\t\t\t{\n"
content += f"\t\t\t\t.width = {prop[0]},\n"
content += f"\t\t\t\t.index = {prop[1]},\n"
content += f"\t\t\t\t.shift = {prop[2]},\n"
content += "\t\t\t},\n"
else:
content += "\t\t.mono = {,\n"
content += f"\t\t\t.width = {font_info['glyph_width']},\n"
content += f"\t\t\t.size = {font_info['glyph_size']},\n"
content += "\t\t},\n"
content += "\t},\n"
# skip unicode struct
content += "\t.unicode = {\n"
content += "\t\t.blocks = NULL,\n"
content += "\t\t.block_count = 0,\n"
content += "\t}\n"
return content
def _font_generate_source_file(asset, font_info):
""" Generate font source file content
@args
> asset (VxAsset) - asset information
> info (dict) - hold font information
@return
> file C content string
"""
# generate basic header
content = "#include <vhex/display/font.h>\n"
content += "\n"
content += f"/* {asset.name} - Vhex asset\n"
content += " This object has been converted by using the vxSDK "
content += "converter */\n"
content += f"struct font const {asset.name} = " + "{\n"
content += f"\t.name = \"{asset.name}\",\n"
# shape information
content += "\t.shape = {\n"
content += "\t\t.bold = 0,\n"
content += "\t\t.italic = 0,\n"
content += "\t\t.serif = 0,\n"
content += "\t\t.mono = 0,\n"
content += f"\t\t.prop = {int(font_info['is_proportional'])},\n"
content += "\t},\n"
# manage display indication
content += f"\t.char_spacing = {font_info['char_spacing']},\n"
# handle special charset behaviour
if font_info['charset'] == 'unicode':
content += _font_generate_unicode_source(font_info)
else:
content += _font_generate_normal_source(font_info)
# closure and return
content += '};\n'
return content

View File

@ -0,0 +1,89 @@
"""
core.font.glyph - glyph abstraction
"""
from core.logger import log
__all__ = [
'glyph_get_wgeometry',
'glyph_encode',
]
#---
# Public
#---
def glyph_get_wgeometry(geometry_info, img_raw, img_size, pos, grid_size):
""" Generate glyph width geometry information
@args
> geometry_info (dict) - geometry information
> img_raw (list) - list of all pixel of the image
> img_size (tuple) - image width and image height
> pos (tuple) - glyph position information (X and Y in pixel)
> grid_size (tuple) - glyph grid size information (width and height)
@return
> Nothing
"""
geometry_info['wstart'] = -1
geometry_info['wend'] = -1
_px = pos[0]
_py = pos[1]
log.debug(f'[geometry] X:{pos[0]} Y:{int(pos[1]/img_size[0])}')
log.debug(f' - grid_size = {grid_size}')
for _ in range(0, grid_size[1]):
for offx in range(0, grid_size[0]):
if img_raw[_py + (_px + offx)][:3] == (255, 255, 255):
continue
if geometry_info['wstart'] < 0 or offx < geometry_info['wstart']:
geometry_info['wstart'] = offx
if geometry_info['wstart'] < 0 or offx > geometry_info['wend']:
geometry_info['wend'] = offx
_py += img_size[0]
geometry_info['wend'] += 1
log.debug(f' - geometry = {geometry_info}')
def glyph_encode(data_info, img_info, geometry, posx, posy):
""" Encode glyph bitmap
@args
> data_info (dict) - internal data information (list, index and shift)
> img_info (dict) - image-related information (object and raw content)
> geometry (dict) - geometry information
> posx (int) - X-axis position in pixel
> posy (int) - Y-axis position in pixel
@return
> Nothing
"""
# fetch information
img = img_info['obj']
img_raw = img_info['raw']
data = data_info['table']
data_idx = data_info['idx']
data_shift = data_info['shift']
wstart = geometry['wstart']
wend = geometry['wend']
# encode the glyph
yoff = 0
log.debug(f'[encode] X:{posx} Y:{int(posy/img.size[0])}')
for _h in range(geometry['hstart'], geometry['hend']):
for _w in range(wstart, wend):
if img_raw[(posy + yoff) + (posx + _w)][:3] == (0, 0, 0):
log.debug('#', end='')
data[data_idx] |= 0x80000000 >> data_shift
else:
log.debug('.', end='')
data[data_idx] &= ~(0x80000000 >> data_shift)
if (data_shift := data_shift + 1) >= 32:
data_shift = 0
data_idx += 1
log.debug('')
yoff += img.size[0]
# commit modification
data_info['idx'] = data_idx
data_info['shift'] = data_shift

View File

@ -0,0 +1,72 @@
"""
core.font.meta - TOML meta information file abstraction
"""
from core.logger import log
__all__ = [
'font_meta_fetch',
]
#---
# Public
#---
def font_meta_fetch(asset):
""" Check and fetch font information
@arg
> asset (VxAsset) - asset information
@return
> dictionary with font information
"""
# generate font default information
font_info = {
# user can customise
'charset' : 'normal',
'grid_size_x' : 0,
'grid_size_y' : 0,
'grid_padding' : 1,
'grid_border' : 0,
'is_proportional' : False,
'line_height' : 0,
'char_spacing' : 1,
# generated "on-the-fly" by the conversion step
# @notes
# This is mainly to provide cache for the Vhex operating system to
# speed-up render calculation by avoiding recurent caculation.
'glyph_size' : 0,
'glyph_height' : 0,
'font_size' : 0,
'data' : []
}
# handle user meta-indication
if 'charset' in asset.meta:
if asset.meta['charset'] not in ['default', 'unicode']:
log.error(f"Unknown charset '{asset.meta['charset']}', abord")
return None
font_info['charset'] = asset.meta['charset']
if 'grid_size' not in asset.meta:
log.error("Missing critical grid size information, abord")
return None
grid_size = asset.meta['grid_size'].split('x')
font_info['grid_size_x'] = int(grid_size[0])
font_info['grid_size_y'] = int(grid_size[1])
if 'grid_padding' in asset.meta:
font_info['grid_padding'] = int(asset.meta['grid_padding'])
if 'grid_border' in asset.meta:
font_info['grid_border'] = int(asset.meta['grid_border'])
if 'proportional' in asset.meta:
font_info['is_proportional'] = asset.meta['proportional']
font_info['line_height'] = font_info['grid_size_y']
if 'line_height' in asset.meta:
font_info['line_height'] = asset.meta['line_height']
if 'char_spacing' in asset.meta:
font_info['char_spacing'] = asset.meta['char_spacing']
font_info['glyph_height'] = font_info['grid_size_y']
# return font information
return font_info

View File

@ -0,0 +1,403 @@
"""
core.conv.type.image - Vhex image converter
"""
import os
from PIL import Image
from core.logger import log
from core.pixel import pixel_rgb24to16
__all__ = [
'image_generate'
]
#---
# Internals
#---
## Profile handling
def _profile_gen(profile, name, palette=None, alpha=None):
r""" Internal image profile class
================================== ========================================
Property Description
================================== ========================================
id (int) profile ID
names (array of str) list all profile names
format (str) profile format name (vhex API)
has_alpha (bool) indicate if the profil has alpha
alpha (int) alpha index in the palette (or mask)
is_indexed (bool) indicate if it should be indexed
palette_base (int) indicate base index for color inserting
palette_color_count (int) indicate the number of color (palette)
palette_trim (bool) indicate if the palette should be trimed
================================== ========================================
"""
profile = {
'profile' : profile,
'name' : name,
'has_alpha' : (alpha is not None),
'alpha' : alpha,
'is_indexed': (palette is not None),
'palette' : None
}
if palette is not None:
profile['palette_base'] = palette[0]
profile['palette_color_count'] = palette[1]
profile['palette_trim'] = palette[2]
return profile
# all supported profile information
VX_PROFILES = [
_profile_gen('IMAGE_RGB565', "p16"),
_profile_gen('IMAGE_RGB565A', "p16a", alpha=0x0001),
_profile_gen('IMAGE_P8_RGB565', "p8", palette=(0,256,True)),
_profile_gen('IMAGE_P8_RGB565A', "p8a", palette=(1,256,True), alpha=0),
_profile_gen('IMAGE_P4_RGB565', "p4", palette=(0,16,False)),
_profile_gen('IMAGE_P4_RGB565A', "p4a", palette=(1,16,False), alpha=0),
]
def _profile_find(name):
"""Find a profile by name."""
for profile in VX_PROFILES:
if name == profile['name']:
return profile
return None
## Image manipulation
def _image_isolate_alpha(info):
""" Isolate alpha corlor of the image
Vhex use a particular handling for alpha color and this information should
use a strict encoding way. Things that Pillow don't do properly. So, lets
manually setup our alpha isolation and patch Pillow alpha palette handling.
@args
> info (dict) - contains all needed information (image, data, ...)
@return
> Nothing
"""
# fetch needed information
img = info['img']
profile = info['profile']
# Save the alpha channel and make it 1-bit. We need to do this because
# the alpha value is handled specialy in Vhex and the image conversion
# to palette-oriented image is weird : the alpha colors is also converted
# in the palette
if profile['has_alpha']:
alpha_channel = img.getchannel("A").convert("1", dither=Image.NONE)
else:
alpha_channel = Image.new("1", img.size, 1)
alpha_pixels = alpha_channel.load()
img = img.convert("RGB")
# Transparent pixels have random values on the RGB channels, causing
# them to use up palette entries during quantization. To avoid that, set
# their RGB data to a color used somewhere else in the image.
pixels = img.load()
bg_color = next(
(
pixels[x,y]
for x in range(img.width)
for y in range(img.height)
if alpha_pixels[x,y] > 0
),
(0,0,0)
)
for _y in range(img.height):
for _x in range(img.width):
if alpha_pixels[_x, _y] == 0:
pixels[_x, _y] = bg_color
# update external information
info['img'] = img
info['img_pixel_list_alpha'] = alpha_pixels
info['img_pixel_list_clean'] = pixels
def _image_encode_palette(info):
""" Generate palette information
This routine is involved only if the targeted profile is indexed. We need
to generate (and isolate) color palette.
@args
> info (dict) - contains all needed information (image, data, ...)
@return
> Nothing
"""
# fetch needed information
img = info['img']
profile = info['profile']
# convert image into palette format
# note: we remove one color slot in the palette for the alpha one
color_count = profile['palette_color_count'] - int(profile['has_alpha'])
img = img.convert(
'P',
dither=Image.NONE,
palette=Image.ADAPTIVE,
colors=color_count
)
# The palette format is a list of N triplets ([r, g, b, ...]). But,
# sometimes, colors after img.convert() are not numbered 0 to
# `color_count`, because the palette don't need to be that big. So,
# we calculate the "palette size" by walking throuth the bitmap and
# by saving the biggest index used.
pixels = img.load()
nb_triplet = 1 + max(
pixels[x,y]
for y in range(img.height)
for x in range(img.width)
)
palette = img.getpalette()[:3 * nb_triplet]
palette = list(zip(palette[::3], palette[1::3], palette[2::3]))
# For formats with transparency, add an "unused" palette slot which
# will used has pink/purple in case of a bad application try to use
# this value anyway
if profile['has_alpha']:
palette = [(255, 0, 255)] + palette
nb_triplet += 1
# Also keep track of how to remap indices from the values generated
# by img.convert() into the palette, which is shifted by 1 due to
# alpha and also starts at profile.palette_base.
#
# Note: profile.palette_base already starts 1 value later for
# formats with alpha.
palette_map = [
(profile['palette_base'] + i) % profile['palette_color_count']
for i in range(nb_triplet)
]
# Encode the palette
palette_color_count = nb_triplet
if not profile['palette_trim']:
palette_color_count = profile['palette_color_count']
palette_data = [0] * palette_color_count
for i, rgb24 in enumerate(palette):
palette_data[i] = pixel_rgb24to16(rgb24)
# update internal information
info['palette_map'] = palette_map
info['palette_data'] = palette_data
info['palette_color_count'] = palette_color_count
info['nb_triplet'] = nb_triplet
info['img_pixel_list_clean'] = pixels
def _image_encode_bitmap(info):
""" Encode the bitmap
This routine will generate the main data list which will contains the
bitmap using Vhex-specific encoding.
@args
> info (dict) - contains all needed information (image, data, ...)
@return
> Nothing
"""
# fetch needed information
img = info['img']
profile = info['profile']
alpha_pixels = info['img_pixel_list_alpha']
pixels = info['img_pixel_list_clean']
palette_map = info['palette_map']
# generate profile-specific geometry information
if profile['name'] in ['p16', 'p16a']:
# Preserve alignment between rows by padding to 4 bytes
nb_stride = ((img.width + 1) // 2) * 4
data_size = (nb_stride * img.height) * 2
elif profile['name'] in ['p8', 'p8a']:
nb_stride = img.width
data_size = img.width * img.height
else:
# Pad whole bytes
nb_stride = (img.width + 1) // 2
data_size = nb_stride * img.height
# Generate the real data map
data = [0] * data_size
# encode the bitmap
for _y in range(img.height):
for _x in range(img.width):
# get alpha information about this pixel
_a = alpha_pixels[_x, _y]
if profile['name'] in ['p16', 'p16a']:
# If c lands on the alpha value, flip its lowest bit to avoid
# ambiguity with alpha
_c = profile['alpha']
if not _a:
_c = pixel_rgb24to16(pixels[_x, _y]) & ~1
data[(img.width * _y) + _x] = _c
elif profile['name'] in ['p8', 'p8a']:
_c = palette_map[pixels[_x,_y]] if _a > 0 else profile['alpha']
data[(img.width * _y) + _x] = _c
else:
_c = palette_map[pixels[_x,_y]] if _a > 0 else profile['alpha']
offset = (nb_stride * _y) + (_x // 2)
if _x % 2 == 0:
data[offset] |= (_c << 4)
else:
data[offset] |= _c
# update external information
info['data'] = data
info['data_size'] = data_size
info['nb_stride'] = nb_stride
info['data_size'] = data_size
def _image_convert(asset, profile_name):
""" Image asset convertion
@args
> asset (_VxAsset) - asset information
> profile_name (str) - profile name information
@return
> a dictionary with all image information
"""
# generate critical information and check posible error
img_info = {
'img' : Image.open(asset.path),
'profile' : _profile_find(profile_name)
}
if not img_info['img']:
log.error(f"unable to open the asset '{asset.path}', abord")
return None
if not img_info['profile']:
log.error(f"unable to find the color profile '{profile_name}', abord")
return None
# convert the bitmap and generate critical information
_image_isolate_alpha(img_info)
if img_info['profile']['is_indexed']:
_image_encode_palette(img_info)
_image_encode_bitmap(img_info)
# return generated information
return img_info
## source file content generation
def _display_array(array, prefix='\t\t'):
""" Display array information (only for p16* profile) """
line = 0
content = ''
for pixels in array:
if line == 0:
content += prefix
if line >= 1:
content += ' '
content += f'{pixels:#06x},'
if (line := line + 1) >= 8:
content += '\n'
line = 0
if line != 0:
content += '\n'
return content
def _image_generate_source_file(asset, info):
"""Generate image source file
@args
> asset (VxAsset) - asset information
> info (dict) - hold image information
@return
> file C content string
"""
img = info['img']
profile = info['profile']
# generate basic header
content = "#include <vhex/display/image/types.h>\n"
content += "\n"
content += f"/* {asset.name} - Vhex asset\n"
content += " This object has been converted by using the vxSDK "
content += "converter */\n"
content += "const image_t " + f"{asset.name} = " + "{\n"
content += f"\t.format = {profile['profile']},\n"
content += "\t.flags = IMAGE_FLAGS_RO | IMAGE_FLAGS_OWN,\n"
content += f"\t.color_count = {profile['palette_color_count']},\n"
content += f"\t.width = {img.width},\n"
content += f"\t.height = {img.height},\n"
content += f"\t.stride = {info['nb_stride']},\n"
# encode bitmap table
encode = 16 if profile['profile'] in ['p16', 'p16a'] else 8
content += f"\t.data = (void*)(const uint{encode}_t [])" + "{\n"
for _y in range(img.height):
content += '\t\t'
for _x in range(info['nb_stride']):
pixel = info['data'][(_y * info['nb_stride']) + _x]
if profile['profile'] in ['p16', 'p16a']:
content += f'{pixel:#06x},'
elif profile['profile'] in ['p8', 'p8a']:
content += f'{pixel:#04x},'
else:
content += f'{pixel:3},'
content += '\n'
content += '\t},\n'
# add palette information
if 'palette_data' in info:
content += "\t.palette = (void*)(const uint16_t []){\n"
content += _display_array(info['palette_data'])
content += "\t},\n"
else:
content += "\t.palette = NULL,\n"
# closure and return
content += '};'
return content
#---
# Public
#---
def image_generate(asset, prefix_output, force_generate):
""" Convert an image asset to a C source file
@args
> asset (_VxAsset) - minimal asset information
> prefix_output (str) - prefix for source file generation
> force_generate (bool) - force generate the source file
@return
> pathname of the generated file
"""
# check critical requirement
if 'profile' not in asset.meta:
log.error(f"[{asset.name}] missing profile information!")
return ''
# check if the file already exists
asset_src = f'{prefix_output}/{asset.name}_vximage.c'
if not force_generate and os.path.exists(asset_src):
return asset_src
#generate the source file content
if not (img_info := _image_convert(asset, asset.meta['profile'])):
return ''
content = _image_generate_source_file(asset, img_info)
# generate the source file
with open(asset_src, "w", encoding='utf8') as file:
file.write(content)
return asset_src

View File

@ -0,0 +1,126 @@
"""
core.logger - Log wrapper
"""
import sys
__all__ = [
'log'
]
#---
# Internals
#---
_LOG_DEBUG = 7
_LOG_INFO = 6
_LOG_NOTICE = 5
_LOG_USER = 4
_LOG_WARN = 3
_LOG_ERR = 2
_LOG_CRIT = 1
_LOG_EMERG = 0
class _VxLogger():
def __init__(self, logfile=None):
self._logfile = logfile
self._level = _LOG_USER
self._indent = 0
#---
# Internals
#---
def _print(self, level, text, skip_indent, fileno):
if self._level < level:
return 0
if not skip_indent and self._level == _LOG_DEBUG and self._indent > 0:
text = ('>>> ' * self._indent) + text
print(text, file=fileno, end='', flush=True)
return len(text) + 1
#---
# Public properties
#---
@property
def level(self):
""" <property> handle print level """
return self._level
@level.setter
def level(self, level):
""" <property> handle print level """
if level < _LOG_EMERG or level > _LOG_DEBUG:
print(f"[log] level update to {level} is not possible, ignored")
return
self._level = level
@property
def indent(self):
""" <property> handle indentation level for __LOG_DEBUG """
return self._indent
@indent.setter
def indent(self, indent):
""" <property> handle indentation level for __LOG_DEBUG """
if indent < 0:
print(f"[log] indent update to {indent} is not possible, ignored")
return
self._indent = indent
#---
# Public methods
#---
def debug(self, text, end='\n', skip_indent=False):
""" print debug log """
return self._print(
_LOG_DEBUG, f"[DEBUG] {text}{end}", skip_indent, sys.stdout
)
def info(self, text, end='\n', skip_indent=False):
""" print info log """
return self._print(
_LOG_INFO, f"[INFO] {text}{end}", skip_indent, sys.stdout
)
def notice(self, text, end='\n', skip_indent=False):
""" print notice log """
return self._print(
_LOG_NOTICE, f"[NOTICE] {text}{end}", skip_indent, sys.stdout
)
def user(self, text, end='\n', skip_indent=False):
""" print user log """
return self._print(_LOG_USER, f"{text}{end}", skip_indent, sys.stdout)
def warn(self, text, end='\n', skip_indent=False):
""" print warning log """
return self._print(
_LOG_WARN, f"[WARN] {text}{end}", skip_indent, sys.stderr
)
def error(self, text, end='\n', skip_indent=False):
""" print error log """
return self._print(
_LOG_ERR, f"[ERROR] {text}{end}", skip_indent, sys.stderr
)
def critical(self, text, end='\n', skip_indent=False):
""" print critical log """
return self._print(
_LOG_CRIT, f"[CRITICAL] {text}{end}", skip_indent, sys.stderr
)
def emergency(self, text, end='\n', skip_indent=False):
""" print emergency log """
self._print(
_LOG_EMERG, f"[EMERGENCY] {text}{end}", skip_indent, sys.stderr
)
sys.exit(84)
#---
# Public functions
#---
log = _VxLogger()

View File

@ -0,0 +1,45 @@
"""
core.conv.pixel - Pixel converter utilities
"""
__all__ = [
'pixel_rgb24to16'
]
#---
# Public
#---
def pixel_rgb24to16(rgb):
""" convert RGB24 -> RGB16-565"""
_r = (rgb[0] & 0xff) >> 3
_g = (rgb[1] & 0xff) >> 2
_b = (rgb[2] & 0xff) >> 3
return (_r << 11) | (_g << 5) | _b
#def rgb1conv(pixel):
# """ Convert RGB<BLACK> -> 1-bit color """
# return pixel == (0, 0, 0)
#def rgb8conv(pixel):
# """ Convert RGB8 -> """
# return int((pixel[0] * 7) / 255) << 5 \
# | int((pixel[1] * 4) / 255) << 3 \
# | int((pixel[2] * 7) / 255) << 0
#def rgba8conv(pixel):
# return int((pixel[0] * 4) / 256) << 6 \
# | int((pixel[1] * 8) / 256) << 3 \
# | int((pixel[2] * 4) / 256) << 1 \
# | (len(pixel) >= 4 and pixel[3] == 0)
#def rgb16conv(pixel):
# return int((pixel[0] * 31) / 255) << 11 \
# | int((pixel[1] * 63) / 255) << 5 \
# | int((pixel[2] * 31) / 255) << 0
#def rgba16conv(pixel):
# return int((pixel[0] * 31) / 255) << 11 \
# | int((pixel[1] * 63) / 255) << 6 \
# | int((pixel[2] * 31) / 255) << 1 \
# | (pixel[3] != 0)

54
sdk/project/__init__.py Normal file
View File

@ -0,0 +1,54 @@
"""
cli.project - handles project creation and more
"""
import sys
from core.project import new_project
from core.logger import log
__all__ = [
'__VXSDK_MODULE_META__',
'cli_validate',
'cli_parse'
]
#---
# Public
#---
__VXSDK_MODULE_META__ = (
['p', 'project'],
"project abstraction",
r"""vxsdk-project
Abstract project manipulation
USAGE:
vxsdk project <COMMAND> [OPTIONS]
OPTIONS:
-h, --help Print helps information
Common used commands:
n, new Create a new project
See `vxsdk project <action> --help` for more information on a specific command
"""
)
def cli_validate(name):
""" validate the module name """
return name in __VXSDK_MODULE_META__[0]
def cli_parse(argv):
""" Project subcommand entry """
if len(argv) > 1:
if '-h' in argv or '--help' in argv:
log.user(__VXSDK_MODULE_META__[2])
sys.exit(0)
if len(argv) > 3:
if argv[0] in ['n', 'new']:
for path in argv[1:]:
new_project(path)
sys.exit(0)
log.error(__VXSDK_MODULE_META__[2])
sys.exit(84)

View File

@ -0,0 +1,17 @@
"""
core.project - project abstraction
"""
from core.project.new import new_project
__all__ = [
'new'
]
#---
# Public
#---
def new(projpath):
""" create a new project from default template """
return new_project(projpath)

29
sdk/project/core/new.py Normal file
View File

@ -0,0 +1,29 @@
"""
core.project.new - create a new project from default template
"""
import os
import shutil
from core.logger import log
__all__ = [
'new_project'
]
#---
# Public
#---
# (todo/CDE6) : change internal project name
def new_project(project_path):
""" create a new project """
if os.path.exists(project_path):
log.warn(f"The path {project_path} already exists !")
return True
origin_path = os.path.dirname(__file__)
shutil.copytree(
origin_path + '/../../assets/project/',
project_path
)
log.user(f"project '{project_path}' successfully created !")
return False

3
vxdev
View File

@ -1,3 +0,0 @@
#!/usr/bin/env bash
python3 scripts/vxdev $@

View File

@ -0,0 +1,131 @@
"""
g3a_generator - Casio G1A file generator
"""
import os
import sys
from datetime import datetime
from PIL import Image
#---
# Internals
#---
def _u32(ptr, idx, data):
""" unsigned 32bits wrapper """
ptr[idx + 0] = (data & 0xff000000) >> 24
ptr[idx + 1] = (data & 0x00ff0000) >> 16
ptr[idx + 2] = (data & 0x0000ff00) >> 8
ptr[idx + 3] = (data & 0x000000ff) >> 0
def _u16(ptr, idx, data):
""" unsigned 16bits injection """
ptr[idx + 0] = (data & 0xff00) >> 8
ptr[idx + 1] = (data & 0x00ff) >> 0
def _u08(ptr, idx, data):
""" unsigned 8bits injection """
ptr[idx + 0] = (data & 0xff) >> 0
def _str(ptr, idx, data):
""" string copy """
for i, j in enumerate(data):
_u08(ptr, idx + i, ord(j))
def _generate_standar_header(addin):
""" generate Casio file standard header """
_str(addin, 0x000, "USBPower") # watermark
_u08(addin, 0x008, 0xf3) # addin file type
_u08(addin, 0x009, 0x00) # watermark
_u08(addin, 0x00a, 0x01) # watermark
_u08(addin, 0x00b, 0x00) # watermark
_u08(addin, 0x00c, 0x01) # watermark
_u08(addin, 0x00d, 0x00) # watermark
_u08(addin, 0x00e, 0x00) # LSB of file size + 0x41 (post)
_u08(addin, 0x00f, 0x01) # watermark
_u32(addin, 0x010, 0x00000000) # file size (MSB) (post)
_u08(addin, 0x014, 0x00) # LSB of file size + 0xb8 (post)
_u16(addin, 0x016, 0x0000) # sum of 8 words starting at 0x7100
def _generate_addin_subheader(addin):
""" generate the g1a addin subheader """
_str(addin, 0x020, "@VXGOS ") # checksum (post)
_u08(addin, 0x02b, 0x00) # number of estrips
_str(addin, 0x030, "01.00.0000") # addin version
_str(addin, 0x03c, datetime.now().strftime("%Y.%m%d.%M%S")) # addin date
for i in range(0, 44):
_u08(addin, 0x04c + i, 0x00) # icon
_str(addin, 0x1d4, "VXGOS ") # add name
_u32(addin, 0x002e, 0x00000000) # filesize (post)
def _generate_addin_icon(addin, icon_path):
""" encode mono icon in addin blob """
with Image.open(icon_path) as icon:
if icon.width != 30 and icon.height != 19:
print(f"{icon_path}: image size should be 30x19 pixels, ignored")
return
pixels = icon.getdata()
for _y in range(1, 18):
for _x in range(0, 30):
if pixels[(_y * 30) + _x] != (0, 0, 0, 255):
continue
_idx = 0x4c + ((_y * 4) + int((_x / 8)))
_u08(addin, _idx, addin[_idx] | (0x80 >> (_x & 7)))
def _generate_checksums(addin):
""" generate all checksums requested for the g3a format """
filesize = len(addin)
# standard header checksum
_u08(addin, 0x010, (filesize & 0xff000000) >> 24)
_u08(addin, 0x011, (filesize & 0x00ff0000) >> 16)
_u08(addin, 0x012, (filesize & 0x0000ff00) >> 8)
_u08(addin, 0x013, (filesize & 0x000000ff) >> 0)
_u08(addin, 0x00e, addin[0x013] + 0x41)
_u08(addin, 0x014, addin[0x013] + 0xb8)
checksum = 0x00000
for i in range(0, 8):
checksum += int(
addin[0x300 + (i * 2) : 0x302 + (i * 2)].hex(),
base=16
)
_u16(addin, 0x016, checksum)
# addin-specific header checksum
_u08(addin, 0x1f0, (filesize & 0xff000000) >> 24)
_u08(addin, 0x1f1, (filesize & 0x00ff0000) >> 16)
_u08(addin, 0x1f2, (filesize & 0x0000ff00) >> 8)
_u08(addin, 0x1f3, (filesize & 0x000000ff) >> 0)
def _main(argv):
""" main entry of the project """
if len(argv) != 3:
print('missing argument (./g1a_generator <output> <rawbin> <icon>)')
sys.exit(84)
rawbin = b''
with open(argv[1], 'rb') as rawfile:
rawbin = rawfile.read()
addin = bytearray(0x200)
_generate_standar_header(addin)
_generate_addin_subheader(addin)
_generate_addin_icon(addin, argv[2])
addin += rawbin
_generate_checksums(addin)
if os.path.exists(argv[0]):
os.remove(argv[0])
with open(argv[0], 'xb') as addinfile:
addinfile.write(addin)
return 0
#---
# Public
#---
if __name__ == '__main__':
sys.exit(_main(sys.argv[1:]))

View File

@ -0,0 +1,138 @@
"""
generate - post-build script used to generate bootlaoder blob with ASLR
"""
import os
import sys
import subprocess
__all__ = [
'generate_aslr_blob',
'generate_image'
]
#---
# Internals
#---
def _patch_got_section(symfile, section):
""" fetch all address of the GOT table
"""
for sec in section:
if sec[0] != '.got':
continue
got_base_addr = int(sec[2], base=16)
for i in range(0, int(sec[4], base=16), 4):
print(f" > reloc GOT entry {got_base_addr+i:08x}...")
symfile.write((got_base_addr + i).to_bytes(4, 'big'))
break
#---
# Public
#---
def generate_aslr_blob(binpath, symtab, sectab):
""" generate bootloader final blob with ASLR
The objectif of this script is to generate the final bootloader blob with
ASLR. To performs this, we will performs 3 steps:
* generate the raw binary file of the bootloader (objcpy)
* generate the raw ALSR symbols table
* generate the complet blootloader image
@args
> binpath (str) - binary file
> symtab (list) - list of all reloc information (readelf -r binpath)
> sectab (list) - list of all sections information (readelf -S binpath)
@return
> Nothings
"""
print('- generate raw binary...')
if os.path.exists(f"{binpath}.raw"):
os.remove(f"{binpath}.raw")
cmd = f"sh-elf-vhex-objcopy -O binary -R .bss {binpath} {binpath}.raw"
subprocess.run(
cmd.split(),
capture_output=False,
check=False
)
print('- generate raw symtab...')
if os.path.exists(f"{binpath}.symtab"):
os.remove(f"{binpath}.symtab")
need_patch_got = False
with open(f"{binpath}.symtab", 'xb') as symfile:
for i, sym in enumerate(symtab):
# direct 32bits address
if sym[2] == 'R_SH_DIR32':
print(f" > reloc 'R_SH_DIR32' sym at {sym[0]}")
symfile.write(int(sym[0], base=16).to_bytes(4, 'big'))
continue
# GOT related jump
if sym[2] == 'R_SH_GOT32':
need_patch_got = True
continue
# Other
if sym[2] not in [
'R_SH_PLT32',
'R_SH_GOT32',
'R_SH_GOTPC',
'R_SH_GOTOFF'
]:
print(f" > [{i}] reloc {sym[2]} not supported")
sys.exit(84)
if need_patch_got:
_patch_got_section(symfile, sectab)
symfile.write(int('00000000', base=16).to_bytes(4, 'big'))
print('- generate the full bootloader...')
if os.path.exists(f"{binpath}.bzImage"):
os.remove(f"{binpath}.bzImage")
with open(f"{binpath}.bzImage", 'xb') as bzimgfile:
with open(f"{binpath}.raw", 'rb') as rawbinfile:
bzimgfile.write(rawbinfile.read())
with open(f"{binpath}.symtab", 'rb') as symtabfile:
bzimgfile.write(symtabfile.read())
return f"{binpath}.bzImage"
def generate_image(prefix_build, bootloader_path, _):
""" generate complet image (g3a) file
TODO
"""
image = bytearray(0)
with open(bootloader_path, 'rb') as bootloaderfile:
image += bootloaderfile.read()
# (todo) os/kernel
image_size = len(image)
# patch first two instruction of the image (see <vxgos/bootloader>)
image[0] = 0b00000000 # (MSB) nop
image[1] = 0b00001001 # (LSB) nop
image[2] = 0b11010000 # (MSB) mov.l @(1*, PC), r0
image[3] = 0b00000001 # (LSB) mov.l @(1*, PC), r0
image[8] = (image_size & 0xff000000) >> 24
image[9] = (image_size & 0x00ff0000) >> 16
image[10] = (image_size & 0x0000ff00) >> 8
image[11] = (image_size & 0x000000ff) >> 0
bzimage = f"{prefix_build}/vxgos.bzImage"
if os.path.exists(bzimage):
os.remove(bzimage)
with open(bzimage, 'xb') as bzimage:
bzimage.write(image)
prefix = os.path.dirname(__file__)
subprocess.run(
[
'/usr/bin/env',
'python3',
f"{prefix}/g1a_generator.py",
f"{prefix_build}/vxgos.g1a",
f"{prefix_build}/vxgos.bzImage",
f"{prefix}/icon.bmp",
],
capture_output=False,
check=False
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,151 @@
"""
g3a_generator - Casio G3A file generator
"""
import os
import sys
from datetime import datetime
from PIL import Image
#---
# Internals
#---
def _u32(ptr, idx, data):
""" unsigned 32bits wrapper """
ptr[idx + 0] = (data & 0xff000000) >> 24
ptr[idx + 1] = (data & 0x00ff0000) >> 16
ptr[idx + 2] = (data & 0x0000ff00) >> 8
ptr[idx + 3] = (data & 0x000000ff) >> 0
def _u16(ptr, idx, data):
""" unsigned 16bits injection """
ptr[idx + 0] = (data & 0xff00) >> 8
ptr[idx + 1] = (data & 0x00ff) >> 0
def _u08(ptr, idx, data):
""" unsigned 8bits injection """
ptr[idx + 0] = (data & 0xff) >> 0
def _str(ptr, idx, data):
""" string copy """
for i, j in enumerate(data):
_u08(ptr, idx + i, ord(j))
def _pxl(ptr, idx, pixel):
""" convert Image pixel tuple to RGB565 data """
_r = (pixel[0] & 0xff) >> 3
_g = (pixel[1] & 0xff) >> 2
_b = (pixel[2] & 0xff) >> 3
_c = (_r << 11) | (_g << 5) | _b
ptr[idx + 0] = (_c & 0xff00) >> 8
ptr[idx + 1] = (_c & 0x00ff) >> 0
def _generate_standar_header(addin):
""" generate Casio file standard header """
_str(addin, 0x0000, "USBPower") # watermark
_u08(addin, 0x0008, 0x2c) # addin file type
_u08(addin, 0x0009, 0x00) # watermark
_u08(addin, 0x000a, 0x01) # watermark
_u08(addin, 0x000b, 0x00) # watermark
_u08(addin, 0x000c, 0x01) # watermark
_u08(addin, 0x000d, 0x00) # watermark
_u08(addin, 0x000e, 0x00) # LSB of file size + 0x41 (post)
_u08(addin, 0x000f, 0x01) # watermark
_u32(addin, 0x0010, 0x00000000) # file size (MSB) (post)
_u08(addin, 0x0014, 0x00) # LSB of file size + 0xb8 (post)
_u16(addin, 0x0016, 0x0000) # 8 words sum starting at 0x7100
def _generate_addin_subheader(addin):
""" generate the g3a addin subheader """
_u32(addin, 0x0020, 0x00000000) # checksum (post)
_u16(addin, 0x0024, 0x0101) # watermark
_u32(addin, 0x002e, 0x00000000) # filesize - 0x7000 - 4 (post)
_str(addin, 0x0040, "VXOS") # title padded with zeros
_u32(addin, 0x005c, 0x00000000) # file size (post)
_str(addin, 0x0060, "@VXOS") # internal name zero padded
for i in range(0, 8):
_str(addin, 0x006b + (i * 24), "vxOS")
_u08(addin, 0x012b, 0x00) # not usable by EAct
_u32(addin, 0x012c, 0x00000000) # watermark
_str(addin, 0x0130, "01.00.0000") # addin version
_str(addin, 0x013c, datetime.now().strftime("%Y.%m%d.%M%S")) # addin date
_str(addin, 0x0ebc, "VxOS.g3a") # filename
def _generate_checksums(addin):
""" generate all checksums requested for the g3a format """
filesize = len(addin)
# standard header checksum
_u08(addin, 0x010, (filesize & 0xff000000) >> 24)
_u08(addin, 0x011, (filesize & 0x00ff0000) >> 16)
_u08(addin, 0x012, (filesize & 0x0000ff00) >> 8)
_u08(addin, 0x013, (filesize & 0x000000ff) >> 0)
_u08(addin, 0x00e, addin[0x013] + 0x41)
_u08(addin, 0x014, addin[0x013] + 0xb8)
checksum = 0x00000
for i in range(0, 8):
checksum += int(
addin[0x7100 + (i * 2) : 0x7102 + (i * 2)].hex(),
base=16
)
_u16(addin, 0x016, checksum)
# addin-specific header checksum
_u32(addin, 0x02e, filesize - 0x7000 - 4)
_u32(addin, 0x05c, filesize)
checksum = 0x00000000
for i in range(0x0000, 0x0020):
checksum += addin[i]
for i in range(0x0024, filesize - 4):
checksum += addin[i]
_u32(addin, 0x020, checksum)
_u32(addin, -4, checksum)
def _generate_addin_icon(addin, offset, icon_path):
""" generate the g3a header icon (selected or unselected) """
with Image.open(icon_path) as icon:
if icon.width != 92 and icon.height != 64:
print(f"{icon_path}: image size should be 64x24 pixels, ignored")
return
pixels = icon.getdata()
_idx = 0
for _y in range(0, 92):
for _x in range(0, 64):
_pxl(addin, offset + (_idx * 2), pixels[_idx])
_idx += 1
def _main(argv):
""" main entry of the project """
if len(argv) != 4:
print(
'missing argument '
'(./g3a_generator <output> <rawbin> <icon-sel> <icon-unsel>)'
)
sys.exit(84)
rawbin = b''
with open(argv[1], 'rb') as rawfile:
rawbin = rawfile.read()
addin = bytearray(0x7000)
_generate_standar_header(addin)
_generate_addin_subheader(addin)
_generate_addin_icon(addin, 0x1000, argv[3])
_generate_addin_icon(addin, 0x4000, argv[2])
addin += rawbin
addin += bytearray(4)
_generate_checksums(addin)
if os.path.exists(argv[0]):
os.remove(argv[0])
with open(argv[0], 'xb') as addinfile:
addinfile.write(addin)
return 0
#---
# Public
#---
if __name__ == '__main__':
sys.exit(_main(sys.argv[1:]))

View File

@ -0,0 +1,139 @@
"""
generate - post-build script used to generate bootlaoder blob with ASLR
"""
import os
import sys
import subprocess
__all__ = [
'generate_aslr_blob',
'generate_image'
]
#---
# Internals
#---
def _patch_got_section(symfile, section):
""" fetch all address of the GOT table
"""
for sec in section:
if sec[0] != '.got':
continue
got_base_addr = int(sec[2], base=16)
for i in range(0, int(sec[4], base=16), 4):
print(f" > reloc GOT entry {got_base_addr+i:08x}...")
symfile.write((got_base_addr + i).to_bytes(4, 'big'))
break
#---
# Public
#---
def generate_aslr_blob(binpath, symtab, sectab):
""" generate bootloader final blob with ASLR
The objectif of this script is to generate the final bootloader blob with
ASLR. To performs this, we will performs 3 steps:
* generate the raw binary file of the bootloader (objcpy)
* generate the raw ALSR symbols table
* generate the complet blootloader image
@args
> binpath (str) - binary file
> symtab (list) - list of all reloc information (readelf -r binpath)
> sectab (list) - list of all sections information (readelf -S binpath)
@return
> Nothings
"""
print('- generate raw binary...')
if os.path.exists(f"{binpath}.raw"):
os.remove(f"{binpath}.raw")
cmd = f"sh-elf-vhex-objcopy -O binary -R .bss {binpath} {binpath}.raw"
subprocess.run(
cmd.split(),
capture_output=False,
check=False
)
print('- generate raw symtab...')
if os.path.exists(f"{binpath}.symtab"):
os.remove(f"{binpath}.symtab")
need_patch_got = False
with open(f"{binpath}.symtab", 'xb') as symfile:
for i, sym in enumerate(symtab):
# direct 32bits address
if sym[2] == 'R_SH_DIR32':
print(f" > reloc 'R_SH_DIR32' sym at {sym[0]}")
symfile.write(int(sym[0], base=16).to_bytes(4, 'big'))
continue
# GOT related jump
if sym[2] == 'R_SH_GOT32':
need_patch_got = True
continue
# Other
if sym[2] not in [
'R_SH_PLT32',
'R_SH_GOT32',
'R_SH_GOTPC',
'R_SH_GOTOFF'
]:
print(f" > [{i}] reloc {sym[2]} not supported")
sys.exit(84)
if need_patch_got:
_patch_got_section(symfile, sectab)
symfile.write(int('00000000', base=16).to_bytes(4, 'big'))
print('- generate the full bootloader...')
if os.path.exists(f"{binpath}.bzImage"):
os.remove(f"{binpath}.bzImage")
with open(f"{binpath}.bzImage", 'xb') as bzimgfile:
with open(f"{binpath}.raw", 'rb') as rawbinfile:
bzimgfile.write(rawbinfile.read())
with open(f"{binpath}.symtab", 'rb') as symtabfile:
bzimgfile.write(symtabfile.read())
return f"{binpath}.bzImage"
def generate_image(prefix_build, bootloader_path, _):
""" generate complet image (g3a) file
TODO
"""
image = bytearray(0)
with open(bootloader_path, 'rb') as bootloaderfile:
image += bootloaderfile.read()
# (todo) os/kernel
image_size = len(image)
# patch first two instruction of the image (see <vxgos/bootloader>)
image[0] = 0b00000000 # (MSB) nop
image[1] = 0b00001001 # (LSB) nop
image[2] = 0b11010000 # (MSB) mov.l @(1*, PC), r0
image[3] = 0b00000001 # (LSB) mov.l @(1*, PC), r0
image[8] = (image_size & 0xff000000) >> 24
image[9] = (image_size & 0x00ff0000) >> 16
image[10] = (image_size & 0x0000ff00) >> 8
image[11] = (image_size & 0x000000ff) >> 0
bzimage = f"{prefix_build}/vxgos.bzImage"
if os.path.exists(bzimage):
os.remove(bzimage)
with open(bzimage, 'xb') as bzimage:
bzimage.write(image)
prefix = os.path.dirname(__file__)
subprocess.run(
[
'/usr/bin/env',
'python3',
f"{prefix}/g3a_generator.py",
f"{prefix_build}/vxgos.g3a",
f"{prefix_build}/vxgos.bzImage",
f"{prefix}/icon-sel.png",
f"{prefix}/icon-uns.png",
],
capture_output=False,
check=False
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,148 @@
"""
generate - post-build script used to generate bootlaoder blob with ASLR
"""
import os
import sys
import subprocess
__all__ = [
'generate_aslr_blob',
'generate_image'
]
#---
# Internals
#---
def _patch_got_section(symfile, section):
""" fetch all address of the GOT table
"""
for sec in section:
if sec[0] != '.got':
continue
got_base_addr = int(sec[2], base=16)
for i in range(0, int(sec[4], base=16), 8):
print(f" > reloc GOT entry {got_base_addr+i:016x}...")
symfile.write((got_base_addr + i).to_bytes(8, 'little'))
break
#---
# Public
#---
def generate_aslr_blob(binpath, symtab, sectab):
""" generate bootloader final blob with ASLR
The objectif of this script is to generate the final bootloader blob with
ASLR. To performs this, we will performs 3 steps:
* generate the raw binary file of the bootloader (objcpy)
* generate the raw ALSR symbols table
* generate the complet blootloader image
@args
> binpath (str) - binary file
> symtab (list) - list of all reloc information (readelf -r binpath)
> sectab (list) - list of all sections information (readelf -S binpath)
@return
> Nothings
"""
print('- generate raw binary...')
if os.path.exists(f"{binpath}.raw"):
os.remove(f"{binpath}.raw")
cmd = f"aarch64-linux-gnu-objcopy -O binary -R .bss {binpath} {binpath}.raw"
subprocess.run(
cmd.split(),
capture_output=False,
check=False
)
print('- generate raw symtab...')
if os.path.exists(f"{binpath}.symtab"):
os.remove(f"{binpath}.symtab")
need_patch_got = False
with open(f"{binpath}.symtab", 'xb') as symfile:
for i, sym in enumerate(symtab):
# direct 64bits address
if sym[2] == 'R_AARCH64_ABS64':
print(f" > reloc 'R_AARCH64_ABS64' sym at {sym[0]}")
symfile.write(int(sym[0], base=16).to_bytes(8, 'little'))
continue
# need GOT patch
if sym[2] == 'R_AARCH64_ADR_GOT':
need_patch_got = True
continue
if sym[2] == 'R_AARCH64_LD64_GO':
continue
# PC-related TAG
if sym[2] in [
'R_AARCH64_JUMP26',
'R_AARCH64_CALL26',
'R_AARCH64_ADR_PRE',
'R_AARCH64_ADD_ABS',
'R_AARCH64_LDST32_',
]:
continue
# unsupported TAG, aboard
print(f" > [{i}] reloc {sym[2]} not supported")
sys.exit(84)
# patch GOT if needed
if need_patch_got:
_patch_got_section(symfile, sectab)
symfile.write(int('0000000000000000', base=16).to_bytes(8, 'little'))
print('- generate the full bootloader...')
if os.path.exists(f"{binpath}.bzImage"):
os.remove(f"{binpath}.bzImage")
image_size = 0
with open(f"{binpath}.bzImage", 'xb') as bzimgfile:
with open(f"{binpath}.raw", 'rb') as rawbinfile:
content = rawbinfile.read()
bzimgfile.write(content)
image_size += len(content)
with open(f"{binpath}.symtab", 'rb') as symtabfile:
content = symtabfile.read()
bzimgfile.write(content)
image_size += len(content)
if image_size % 512:
for _ in range(0, 512 - (image_size % 512)):
bzimgfile.write(0x00.to_bytes(1))
return f"{binpath}.bzImage"
def generate_image(prefix_build, bootloader_path, _):
""" generate complet image file
"""
# mov x0, #0x0000 = 0xd2800000 - 0xd29fffe0
# mov x0, #0x0000, lsl 16 = 0xf2a00000 - 0xf2bfffe0
# mov x0, #0x0000, lsl 32 = 0xf2c00000 - 0xf2dfffe0
# mov x0, #0x0000, lsl 48 = 0xf2e00000 - 0xf2ffffe0
image = bytearray(0)
with open(bootloader_path, 'rb') as bootloaderfile:
image += bootloaderfile.read()
# (todo) os/kernel
image_size = len(image)
mov0 = 0xd2800000 | (((image_size & 0x000000000000ffff) >> 0) << 5)
mov1 = 0xf2a00000 | (((image_size & 0x00000000ffff0000) >> 16) << 5)
mov2 = 0xf2c00000 | (((image_size & 0x0000ffff00000000) >> 32) << 5)
mov3 = 0xf2e00000 | (((image_size & 0xffff000000000000) >> 48) << 5)
image[0x40:0x44] = mov0.to_bytes(4, 'little')
image[0x44:0x48] = mov1.to_bytes(4, 'little')
image[0x48:0x4c] = mov2.to_bytes(4, 'little')
image[0x4c:0x50] = mov3.to_bytes(4, 'little')
image[0x0c:0x14] = image_size.to_bytes(8, 'little')
bzimage = f"{prefix_build}/vxgos.bzImage"
if os.path.exists(bzimage):
os.remove(bzimage)
with open(bzimage, 'xb') as bzimage:
bzimage.write(image)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,16 @@
[font8x12]
path = 'font8x12.png'
type = 'font'
grid_size = '8x12'
grid_padding = 1
grid_border = 1
proportional = false
line_height = 9
[font3x5]
path = 'font3x5.png'
type = 'font'
grid_size = '3x5'
grid_padding = 1
proportional = false
line_height = 6

View File

@ -0,0 +1,26 @@
VXDEV_TOOLCHAIN_PREFIX = 'sh-elf-vhex-'
VXDEV_TOOLCHAIN_PROCESSOR = 'sh'
VXDEV_CFLAGS = [
'-Wall',
'-Wextra',
'-D__SUPPORT_FX9860__',
'-ffreestanding',
'-nostdlib',
'-fPIE',
'-O1',
'-mb',
'-m4-nofpu',
'-fstrict-volatile-bitfields',
]
VXDEV_LDFLAGS = [
'-static',
'-Wl,-Map=map',
'-Wl,--build-id=none',
'-Wl,--emit-relocs',
]
VXDEV_LIBS = [
'-lgcc',
]
VXDEV_ASSETS = [
'font3x5',
]

View File

@ -0,0 +1,30 @@
#ifndef FXCG50_BOOTLOADER_ASM_UTILS_H
#define FXCG50_BOOTLOADER_ASM_UTILS_H 1
/* function() : define a function using special indication
*
* - the function's name must start with a '_'
* - the function should start in a 4-aligned address in order to benefit from
* the ILP (Instruction Level Parallelism) */
#define function(name) \
.balign 4 ;\
.global _ ## name ;\
_ ## name
/* FX9860_SYSCALL_TRAMPOLINE - Casio's syscall trampoline address */
#define FX9860_SYSCALL_TRAMPOLINE 0x80010070
/* syscall() : helper used to quickly define Casio syscall invokation
*
* - the syscall trampoline code is common for all syscall
* - r0 contain the syscall ID */
#define syscall(id) \
mov.l 1f, r2 ;\
mov.l 2f, r0 ;\
jmp @r2 ;\
nop ;\
.balign 4 ;\
1: .long FX9860_SYSCALL_TRAMPOLINE ;\
2: .long id
#endif /* FXCG50_BOOTLOADER_ASM_UTILS_H */

View File

@ -0,0 +1,73 @@
#ifndef FXCG50_KEYCODE_H
#define FXCG50_KEYCODE_H
/* KEYSCODE_GEN() : generate keycode */
#define KEYCODE_GEN(row, column) \
(((row & 0x0f) << 4) | ((column & 0x0f) << 0))
/* Define all keycode */
typedef enum
{
KEY_F1 = 0x41,
KEY_F2 = 0x42,
KEY_F3 = 0x43,
KEY_F4 = 0x44,
KEY_F5 = 0x45,
KEY_F6 = 0x46,
KEY_SHIFT = 0x49,
KEY_OPTN = 0x4a,
KEY_VARS = 0x4b,
KEY_MENU = 0x4c,
KEY_LEFT = 0x4d,
KEY_UP = 0x4e,
KEY_ALPHA = 0x31,
KEY_SQUARE = 0x32,
KEY_POWER = 0x33,
KEY_EXIT = 0x34,
KEY_DOWN = 0x35,
KEY_RIGHT = 0x36,
KEY_XOT = 0x39,
KEY_LOG = 0x3a,
KEY_LN = 0x3b,
KEY_SIN = 0x3c,
KEY_COS = 0x3d,
KEY_TAN = 0x3e,
KEY_FRAC = 0x21,
KEY_FD = 0x22,
KEY_LEFTP = 0x23,
KEY_RIGHTP = 0x24,
KEY_COMMA = 0x25,
KEY_ARROW = 0x26,
KEY_7 = 0x29,
KEY_8 = 0x2a,
KEY_9 = 0x2b,
KEY_DEL = 0x2c,
KEY_4 = 0x11,
KEY_5 = 0x12,
KEY_6 = 0x13,
KEY_MUL = 0x14,
KEY_DIV = 0x15,
KEY_1 = 0x19,
KEY_2 = 0x1a,
KEY_3 = 0x1b,
KEY_PLUS = 0x1c,
KEY_MINUS = 0x1d,
KEY_0 = 0x01,
KEY_DOT = 0x02,
KEY_EXP = 0x03,
KEY_NEG = 0x04,
KEY_EXE = 0x05,
KEY_UNUSED = 0xff,
KEY_NONE = 0xfe,
} KEY_t;
#endif /* FXCG50_KEYCODE_H */

View File

@ -0,0 +1,17 @@
#include "bootloader/display.h"
#include "bootloader/bios.h"
//---
// Public
//---
/* _bios_dfont_get() : get font information */
int _bios_dfont_get(struct font **font)
{
extern struct font font3x5;
if (font == NULL)
return -1;
*font = &font3x5;
return 0;
}

View File

@ -0,0 +1,14 @@
#include "bootloader/bios.h"
//---
// Public
//---
/* _bios_dinfo() : get display information */
void _bios_dinfo(size_t *width, size_t *height)
{
if (width != NULL)
*width = 128;
if (height != NULL)
*height = 64;
}

View File

@ -0,0 +1,33 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "bootloader/bios.h"
#include "bootloader/display.h"
//---
// Public
//---
/* _bios_dpixel() : dpixel wrapper */
void _bios_dpixel(int x, int y, int color)
{
extern uint8_t vram[];
int mask;
int indx;
if ((unsigned)x >= 128 || (unsigned)y >= 64)
return;
indx = ((y * 128) + x) / 8;
mask = 0x80 >> (x & 7);
switch (color) {
case C_BLACK:
vram[indx] |= mask;
break;
case C_WHITE:
vram[indx] &= ~mask;
break;
}
}

View File

@ -0,0 +1,147 @@
#include <stdint.h>
//---
// Internals
//---
// Device specification sheet
/* This version number is 1 for the old T6K11 (T6K73A (2005-11-30)) everyone
* knows, and 2 for the newer one found in the Graph 35+E II (ML9801A
* (2018-07-05)).
* Documentation is available only for the T6K11 (version 1 is close).
* Dumps of Bdisp_PutDisp_DD() are used to drive version 2. */
static int t6k11_version = -1;
/* Screen registers on the original T6K11. Registers 8..11 and 13..15 are test
registers and must not be used! */
enum {
reg_display = 0,
reg_counter = 1,
reg_analog = 2,
reg_alternate = 3,
reg_yaddr = 4, /* These two use the same register */
reg_xaddr = 4, /* (interpretation depends on count mode) */
reg_zaddr = 5,
reg_contrast = 6,
reg_data = 7,
reg_daconv = 12,
};
/* Automatic counter increment during read/write */
enum {
cnt_ydown = 0,
cnt_yup = 1,
cnt_xdown = 2,
cnt_xup = 3,
};
// Device communication primitives
/* I/O may be performed either with RS = 0 or RS = 1.
* The memory-mapping of the device I/O maps bit 16 of the address to pin RS.
* There may be other mapped pins in the address. (?) */
/* RS = 0: Register selection */
static volatile uint8_t *sel = (void *)0xb4000000;
/* RS = 1: Command data or vram data */
static volatile uint8_t *cmd = (void *)0xb4010000;
/* command() - send a command to set the value of a register
@reg Register number
@data Value to set in reg */
static void command(uint8_t reg, uint8_t data)
{
*sel = reg;
*cmd = data;
}
/* write_row() - send 16 bytes to the display driver
@buf Buffer to take data from */
static void write_row(const uint8_t *buf)
{
/* Unroll the loop for more speed */
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
}
// Driver functions
/* t6k11_display() - send vram data to the LCD device */
static void t6k11_display_t6k73(const void *vram)
{
for(int y = 0; y < 64; y++)
{
/* Set the X-address register for this row */
command(reg_xaddr, y | 0xc0);
/* Use Y-Up mode */
command(reg_counter, cnt_yup);
/* Start counting Y from 0 */
command(reg_yaddr, 0);
/* Send the row's data to the device */
*sel = reg_data;
write_row(vram);
vram += 128 / 8;
}
}
/* t6k11_display() - send vram data to the LCD device */
static void t6k11_display_ml9801(const void *vram)
{
for(int y = 0; y < 64; y++)
{
command(8, y | 0x80);
command(8, 4);
*sel = 10;
write_row(vram);
vram += 128 / 8;
}
}
//---
// Public
//---
/* expose VRAM here */
uint8_t vram[(128 * 64) / 8];
/* _bios_dupdate() : small T6K73A and ML9801A driver */
void _bios_dupdate(void)
{
extern uint8_t vram[];
extern int t6k11_version;
/* Tell Graph 35+E II from OS version (this is accurate unless someone
* tweaks an OS file enough to de-correlate the version of the OS and
* the version of the display and storage memory drivers, which, let's
* be real, is enough for now. */
if (t6k11_version == -1) {
char *version = (void *)0x80010020;
t6k11_version = (version[1] == '3') ? 2 : 1;
}
/* branch to the real driver */
if(t6k11_version == 1) t6k11_display_t6k73(vram);
if(t6k11_version == 2) t6k11_display_ml9801(vram);
}

View File

@ -0,0 +1,11 @@
#include "fx9860/asm_utils.h"
.text
!---
! Public
!---
/* _bios_free() (free) : free allocated memory */
function(_bios_free):
syscall(0xacc)

View File

@ -0,0 +1,11 @@
#include "fx9860/asm_utils.h"
.text
!---
! Public
!---
/* _bios_malloc() (malloc) : allocate memory */
function(_bios_malloc):
syscall(0xacd)

View File

@ -0,0 +1,89 @@
#include "bootloader/console.h"
#include "fx9860/keycode.h"
//---
// Public
//---
/* console_key_handle_special() : handle special key handling */
// TODO
// - F_UP -> history
// - F_DOWN -> history
int console_key_handle_special(struct console *console, int key)
{
switch (key)
{
case KEY_SHIFT:
console->input.mode.shift ^= 1;
return 1;
case KEY_ALPHA:
console->input.mode.maj ^= 1;
return 1;
case KEY_OPTN:
console->input.mode.ctrl ^= 1;
return 1;
case KEY_DOT:
console_buffer_in_insert(console, ' ');
return 1;
case KEY_DEL:
console_buffer_in_remove(console);
return 1;
case KEY_EXE:
console->input.mode.exit = 1;
return 1;
case KEY_LEFT:
console_buffer_in_cursor_move(console, -1);
return 1;
case KEY_RIGHT:
console_buffer_in_cursor_move(console, 1);
return 1;
default:
return 0;
}
}
/* console_key_handle(): Update the internal buffer with the given key code */
int console_key_handle(struct console *console, int key)
{
static const uint8_t keylist_alpha[] = {
KEY_XOT, KEY_LOG, KEY_LN, KEY_SIN, KEY_COS, KEY_TAN,
KEY_FRAC, KEY_FD, KEY_LEFTP, KEY_RIGHTP, KEY_COMMA, KEY_ARROW,
KEY_7, KEY_8, KEY_9,
KEY_4, KEY_5, KEY_6, KEY_MUL, KEY_DIV,
KEY_1, KEY_2, KEY_3, KEY_PLUS, KEY_MINUS,
KEY_0, 0xff
};
static const uint8_t keylist_num[] = {
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
KEY_PLUS, KEY_MINUS, KEY_MUL, KEY_DIV,
KEY_LEFTP, KEY_RIGHTP, KEY_COMMA, KEY_POWER,
KEY_DOT, KEY_FD, KEY_ARROW, 0xff
};
static const char keylist_num_char[] = "0123456789+-x/(),^.|_";
const uint8_t *keycode_list;
char character;
int i;
/* Get the appropriate key list */
keycode_list = keylist_alpha;
if (console->input.mode.shift == 1)
keycode_list = keylist_num;
/* Try to find the pressed key. */
i = -1;
while (keycode_list[++i] != 0xff && keycode_list[i] != key);
if (keycode_list[i] != key)
return 0;
/* handle mode then update the buffer */
if (console->input.mode.shift == 0) {
character = 'a' + i;
if (console->input.mode.maj == 1)
character = 'A' + i;
} else {
character = keylist_num_char[i];
}
console_buffer_in_insert(console, character);
return 1;
}

View File

@ -0,0 +1,61 @@
#include "bootloader/console.h"
#include "fx9860/keycode.h"
//---
// Internlas
//---
/* keysc_fetch() : scan the KEYSC hardware module */
static int keysc_fetch(void)
{
uint16_t volatile *SH7305_KEYSC = (void*)0xa44b0000;
int column;
int key;
int row;
int registered;
uint16_t udata;
/* update the keycache */
row = 6;
key = 0x5f;
registered = 0x0000;
while (--row >= 0)
{
column = 16;
udata = SH7305_KEYSC[row];
if (registered != 0x0000)
continue;
while (--column >= 0)
{
if ((udata & (0x8000 >> column)) != 0) {
registered = KEYCODE_GEN(row, column);
break;
}
key -= 1;
}
}
return registered;
}
//---
// Public
//---
/* console_key_get() : small one-shot key waiter */
int console_key_get(void)
{
static uint16_t prev_key = 0xffff;
if (prev_key != 0xffff) {
while (1) {
if (prev_key != keysc_fetch())
break;
}
}
while (1) {
prev_key = keysc_fetch();
if (prev_key != 0x0000)
break;
}
return prev_key;
}

View File

@ -0,0 +1,297 @@
#include "fx9860/asm_utils.h"
.section .bootloader.pre_text, "ax"
/* ___fxcg50_initialize() : bootstrap routine
We are currently virtualized as a common program (addin) at 0x00300000, but the
code currently executed is physically fragmented through the ROM. So, we cannot
perform ASLR self-relocation here.
The first thing we need to do is find the "user" RAM allocated by Casio (by
analyzing the TLB) and then performs a "self-translation" into this place.
After that, we will be able to perform ASLR self-relocation and classic
"kernel" bootstrapping.. */
function(__fxcg50_primary_bootloader):
! ---
! *CRITICAL*
!
! The next two instructions will be patched *DURING COMPILE TIME* with the
! complete image size (bootloader, ASLR, kernel, OS,...) as follows:
! > mov.l complet_image_size, r0
! > nop
! If not patched, the code will just return and indicate to Casio not to
! restart the application.
! ---
rts
mov #1, r0
bra translation_entry
nop
.long 0x00000000
translation_entry:
! ---
! prepare alias
!
! We don't need to setup the stack because we are loaded from CasioABS
! (Casio OS) as a simple program (addin) with a given stack, which we will
! not use even if we use "special" registers such as r8, r9, ... as we will
! be careful to never return from here.
!
! @note
! - r0 is reused
! - r1 is reused
! - r2 is reused
! - r7 is reused
! - r14 is reused
! ---
#define counter r0
#define utlb_addr_val r1
#define utlb_data_val r2
#define uram_phys_addr r3
#define rom_base_addr r4
#define rom_image_size r5
#define utlb_data_ptr r6
#define utlb_addr_ptr r7
#define uram_virt_ptr r8
#define uram_phys_size r9
#define utlb_valid_bit r10
#define utlb_data_ptr_saved r11
#define utlb_magic_array r12
#define utlb_addr_ptr_saved r13
#define utlb_data_ppn_mask r14
! ---
! Save critical information
!
! Calculate the runtime loaded address of the addin and save the
! compile-time image size information
! ---
! save image size
mov r0, rom_image_size
! precalculate runtime loaded address
mova utlb_fetch_uram_info, r0
add #(___fxcg50_primary_bootloader - utlb_fetch_uram_info), r0
mov r0, rom_base_addr
! ---
! Configure cache
!
! This is a critical part because we will move to the URAM and perform
! self-patching symbols during our execution. So, we need to have a
! consistent cache behavior to help the MPU follow our instructions.
!
! The SH7305 has a special register for that called Cache Control Register
! (CCR), and it seems tobe the same as SH7724 (except that no official POC
! or information can invalidate the instruction Cache (IC) without causing
! the machine to crash):
!
! - Indicate that P1 area use the "Write-back" method
! - Indicate that P0/P3 areas use the "Write-through" method
! - Enable Operand Cache (OC)
! - Enable Instruction Cache (IC)
! - Invalidate all Operand Cache (OC) entries
! ---
mov.l ccr_register_addr, r0
mov.l ccr_register_data, r1
mov.l r1, @r0
synco
! ---
! UTLB scan to find URAM information
!
! As explained above, we first need to find the user RAM given by Casio for
! our application, as we know that is an unused and stable space for us.
!
! We will scan each TLB entry to find the user's RAM. The memory is
! virtually mapped by Casio using the same historical virtual address:
! 0x08100000. Also, all the given RAM is entierly (?) mapped by the
! operating system. Thus, we walk through the TLB until we don't find the
! next memory fragment; this will allow us to find the size of the RAM
! (which varies between models and emulators).
!
! We will also take advantage of the fact that Casio *MAP* the virtual
! address 0x00000000 (NULL) for no valid reason. So, if we find this
! mapping, we will invalidate it to be sure that a NULL manipulated pointer
! will cause a TLBmiss exception.
!
! TODO : invalidate NULL page
! ---
utlb_fetch_uram_info:
! fetch external information
mov.l data_00000100, utlb_valid_bit
mov.l data_08100000, uram_virt_ptr
mov.l data_f6000000, utlb_addr_ptr
mov.l data_14100c0a, utlb_magic_array
mov.l data_1ffffc00, utlb_data_ppn_mask
mov.l data_f7000000, utlb_data_ptr
! prepare internal vars
mov #0, counter
mov #-1, uram_phys_addr
mov #0, uram_phys_size
mov utlb_data_ptr, utlb_data_ptr_saved
mov utlb_addr_ptr, utlb_addr_ptr_saved
utlb_walk_loop:
! validity check
! @note
! - check the validity bit for each UTLB data and address entry
! - both data and address entry have the same Valid bit position
mov.l @utlb_addr_ptr, utlb_addr_val
tst utlb_valid_bit, utlb_addr_val
bt utlb_walk_cond_check
mov.l @utlb_data_ptr, utlb_data_val
tst utlb_valid_bit, utlb_data_val
bt.s utlb_walk_cond_check
! check VPN validity
! @note
! - "UTLB Address Array" (p239) - Figure 7.14
! - we need to clear the first 10 bits of the fetched UTLB data to get the
! the "real" virtual address (eliminate ASID, Dirty and Valid bits)
shlr8 utlb_addr_val
shlr2 utlb_addr_val
shll8 utlb_addr_val
shll2 utlb_addr_val
cmp/eq uram_virt_ptr, utlb_addr_val
bf.s utlb_walk_cond_check
! fetch the page size
! @note
! - "Unified TLB (UTLB) Configuration"(p221)
! - page size is weird to calculate for many hardware reasons, and this code
! is the literal translation of :
! > size = ((data->SZ1 << 1) | data->SZ2) << 3;
! > size = 1 << ((0x14100c0a >> size) & 0xff);
mov #-1, r1
mov utlb_data_val, r0
tst #128, r0
mov #-1, r7
negc r1, r1
tst #16, r0
add r1, r1
negc r7, r7
or r7, r1
mov utlb_magic_array,r7
shll2 r1
add r1, r1
neg r1, r1
shad r1, r7
extu.b r7, r1
mov #1, r7
shld r1, r7
! update counter / information
add r7, uram_phys_size
add r7, uram_virt_ptr
! check if the URAM physical address is already set
mov uram_phys_addr,r0
cmp/eq #-1,r0
bf utlb_page_found_restart
! calculate the physical address of the page (URAM)
! @note
! - "UTLB Data Array"(p240) - Figure 7.15
! - to fetch the Physical Address, we just need to isolate the PPN
and utlb_data_ppn_mask, utlb_data_val
shlr8 utlb_data_val
shlr2 utlb_data_val
mov utlb_data_val, uram_phys_addr
shll8 uram_phys_addr
shll2 uram_phys_addr
utlb_page_found_restart:
mov r13, utlb_addr_ptr
mov r11, utlb_data_ptr
bra utlb_walk_loop
mov #0, counter
utlb_walk_cond_check:
! update internal counter
! @notes
! - only 64 UTLB entry
! - UTLB entry (for data and address) gap is 0x100
mov.l data_00000100, r1
add #1, counter
cmp/eq #64, counter
add r1, utlb_addr_ptr
bf.s utlb_walk_loop
add r1, utlb_data_ptr
! ---
! Self-translation to URAM
!
! Now that we have the user RAM entry address (uram_phys_addr) and its size
! (uram_phys_size), we can self-translate to this location using a dummy
! byte-per-byte copy.
!
! Note that, for now, no random installation offset is performed since
! predicting uncertain behavior is complex to choose for now.
! ---
self_translation:
! fetch bootloader ROM geometry information
mov rom_base_addr, r0
mov rom_image_size, r2
! generate uncachable P2 URAM address
! TODO
! - random offset
! - check oversize
mov.l data_a0000000, r1
or uram_phys_addr, r1
! dump the complet image into URAM
self_dump_uram:
mov.b @r0, r14
mov.b r14, @r1
dt r2
add #1, r0
add #1, r1
bf.s self_dump_uram
nop
! Prepare the self-translation by calculating the new PC position using a
! P1 address to allow caching to be performed.
! @note
! - ___fxcg50_bootloader_start is a global symbol compiled with NULL as the
! base address. So, we don't have to calculate the gap between the start
! of the ROM and the symbol.
mov.l data_80000000, r1
or uram_phys_addr, r1
mov.l real_bootloader_start, r0
add r1, r0
! self-translation
mov rom_image_size, r6
mov uram_phys_addr, r5
mov rom_base_addr, r4
jmp @r0
nop
.balign 4
data_08100000: .long 0x08100000 ! Casio addin load virtual address
data_f6000000: .long 0xf6000000 ! SH7305 UTLB Address address
data_f7000000: .long 0xf7000000 ! SH7305 UTLB Data addresss
data_14100c0a: .long 0x14100c0a ! Magic UTLB page size table
data_1ffffc00: .long 0x1ffffc00 ! UTLB Address PPN mask
data_00000100: .long 0x00000100 ! UTLB entry gap and UTLB validity bit
data_a0000000: .long 0xa0000000 ! P2 base address
data_80000000: .long 0x80000000 ! P1 base address
ccr_register_addr: .long 0xff00001c ! SH7305.CACHE.CCR register address
ccr_register_data: .long 0x0000010f ! CCR configuration
real_bootloader_start:
.long ___fxcg50_bootloader_start

View File

@ -0,0 +1,130 @@
#include "fx9860/asm_utils.h"
.section .bootloader.text, "ax"
/* ___fxcg50_bootloader_start() : real bootstrap entry
Now we are in the URAM we can performs ASLR patching, setup stack and involve
the first high-level C code which will perform kernel setup.
The primary (fake) bootloader (previous operations) have setup some arg:
- r4 = ROM relocation base address
- r5 = RAM relocation base address (physical)
- r6 = image size */
function(__fxcg50_bootloader_start):
! ---
! prepare alias
! ---
#define rom_reloc_base r4
#define ram_reloc_base r5
#define image_size r6
! ---
! ASLR application
!
! perform ASLR patch by using the symbols table information injected
! during bootloader build steps at the end of the bootloader code marked
! with ___bootloader_code_end.
!
! The content of the table has been generated during post-compiling script
! ---
! The table symbol is not aready resolved (its our job), we must manually
! calculate the real address of the symbols table
mov.l bootloader_code_end, r0
mov.l p1_addr_base, r1
mov.l p2_addr_base, r2
or ram_reloc_base, r2
or ram_reloc_base, r1
add r2, r0
! walk trough the symbols table and patch all location
! @note
! - we MUST perform patching using P2 (uncachable) area to avoid
! inconsistancy behaviour with the cache.
! - symbols are relocalize through P1 (cachable) area
aslr_symbol_patch_loop:
mov.l @r0, r8
tst r8, r8
bt aslr_commit
add r2, r8
mov.l @r8, r9
add r1, r9
mov.l r9, @r8
mov.l r8, @r0
add #4, r0
bra aslr_symbol_patch_loop
nop
aslr_commit:
! Now that ASLR symbols has been updated using uncachable area (P2), we
! need to invalitate all Operands Cache entry that the MPU have possibly
! setup to avoid inconsistant `mov.x` behaviour
! @note
! - CCR.OCI = 1 -> Operand Cache Invalidation (self-cleared to 0)
mov.l ccr_reg_addr, r1
mov.l @r1, r0
or #0x08, r0
mov.l r0, @r1
synco
setup_stack:
! TODO : watermark stack area for statistics
! TODO : stack switch
! TODO : stack canary
bootloader_c_invokation:
mov.l p2_addr_base, r2
mov ram_reloc_base, r4
mov image_size, r5
mov.l bootloader_main, r0
jsr @r0
or r2, r4
! ---
! bootloader panic
!
! As we have probably wierdly restored hadware information, if the
! bootloader main routine return we simply display black screen. You can
! uncomment following instruction to allows getkey() to return to the menu
! (debug only)
! ---
bootloader_paniktamer:
mov.l syscall_trampoline, r8
mov #0x4a, r4
mov #3, r5
mov.l syscall_id, r0
jsr @r8
nop
test1:
! add #-4, r15
! mov r15, r4 ! column
! add #-4, r15
! mov r15, r5 ! row
! add #-4, r15
! mov r15, r1 ! keycode
! mov #0, r6 ! type of waiting (KEYWAIT_HALTON_TIMEOFF)
! mov #0, r7 ! timeout period
! mov.l r1, @-r15 ! keycode
! mov #0, r2
! mov.l r2, @-r15 ! [menu] key return to menu
! mov.l getkey_id, r0
! jsr @r8
! nop
bra test1
nop
.balign 4
bootloader_main: .long _bootloader_main
bootloader_code_end: .long ___bootloader_code_end
syscall_trampoline: .long 0x80020070
syscall_id: .long 0x0276
p2_addr_base: .long 0xa0000000
p1_addr_base: .long 0x80000000
getkey_id: .long 0x000012bf ! GetKeyWait_OS syscall ID
ccr_reg_addr: .long 0xff00001c ! SH7305.CACHE.CCR register address

View File

@ -0,0 +1,27 @@
VXDEV_TOOLCHAIN_PREFIX = 'sh-elf-vhex-'
VXDEV_TOOLCHAIN_PROCESSOR = 'sh'
VXDEV_TOOLCHAIN_ENDIANNESS = 'big'
VXDEV_CFLAGS = [
'-Wall',
'-Wextra',
'-D__SUPPORT_FXCG50__',
'-ffreestanding',
'-nostdlib',
'-fPIE',
'-O3',
'-mb',
'-m4-nofpu',
'-fstrict-volatile-bitfields',
]
VXDEV_LDFLAGS = [
'-static',
'-Wl,-Map=map',
'-Wl,--build-id=none',
'-Wl,--emit-relocs',
]
VXDEV_LIBS = [
'-lgcc',
]
VXDEV_ASSETS = [
'font8x12',
]

View File

@ -0,0 +1,30 @@
#ifndef FXCG50_BOOTLOADER_ASM_UTILS_H
#define FXCG50_BOOTLOADER_ASM_UTILS_H 1
/* function() : define a function using special indication
*
* - the function's name must start with a '_'
* - the function should start in a 4-aligned address in order to benefit from
* the ILP (Instruction Level Parallelism) */
#define function(name) \
.balign 4 ;\
.global _ ## name ;\
_ ## name
/* FXCG50_SYSCALL_TRAMPOLINE - Casio's syscall trampoline address */
#define FXCG50_SYSCALL_TRAMPOLINE 0x80020070
/* syscall() : helper used to quickly define Casio syscall invokation
*
* - the syscall trampoline code is common for all syscall
* - r0 contain the syscall ID */
#define syscall(id) \
mov.l 1f, r2 ;\
mov.l 2f, r0 ;\
jmp @r2 ;\
nop ;\
.balign 4 ;\
1: .long FXCG50_SYSCALL_TRAMPOLINE ;\
2: .long id
#endif /* FXCG50_BOOTLOADER_ASM_UTILS_H */

View File

@ -0,0 +1,73 @@
#ifndef FXCG50_KEYCODE_H
#define FXCG50_KEYCODE_H
/* KEYSCODE_GEN() : generate keycode */
#define KEYCODE_GEN(row, column) \
(((row & 0x0f) << 4) | ((column & 0x0f) << 0))
/* Define all keycode */
typedef enum
{
KEY_F1 = 0x41,
KEY_F2 = 0x42,
KEY_F3 = 0x43,
KEY_F4 = 0x44,
KEY_F5 = 0x45,
KEY_F6 = 0x46,
KEY_SHIFT = 0x49,
KEY_OPTN = 0x4a,
KEY_VARS = 0x4b,
KEY_MENU = 0x4c,
KEY_LEFT = 0x4d,
KEY_UP = 0x4e,
KEY_ALPHA = 0x31,
KEY_SQUARE = 0x32,
KEY_POWER = 0x33,
KEY_EXIT = 0x34,
KEY_DOWN = 0x35,
KEY_RIGHT = 0x36,
KEY_XOT = 0x39,
KEY_LOG = 0x3a,
KEY_LN = 0x3b,
KEY_SIN = 0x3c,
KEY_COS = 0x3d,
KEY_TAN = 0x3e,
KEY_FRAC = 0x21,
KEY_FD = 0x22,
KEY_LEFTP = 0x23,
KEY_RIGHTP = 0x24,
KEY_COMMA = 0x25,
KEY_ARROW = 0x26,
KEY_7 = 0x29,
KEY_8 = 0x2a,
KEY_9 = 0x2b,
KEY_DEL = 0x2c,
KEY_4 = 0x11,
KEY_5 = 0x12,
KEY_6 = 0x13,
KEY_MUL = 0x14,
KEY_DIV = 0x15,
KEY_1 = 0x19,
KEY_2 = 0x1a,
KEY_3 = 0x1b,
KEY_PLUS = 0x1c,
KEY_MINUS = 0x1d,
KEY_0 = 0x01,
KEY_DOT = 0x02,
KEY_EXP = 0x03,
KEY_NEG = 0x04,
KEY_EXE = 0x05,
KEY_UNUSED = 0xff,
KEY_NONE = 0xfe,
} KEY_t;
#endif /* FXCG50_KEYCODE_H */

View File

@ -0,0 +1,17 @@
#include "bootloader/display.h"
#include "bootloader/bios.h"
//---
// Public
//---
/* _bios_dfont_get() : get font information */
int _bios_dfont_get(struct font **font)
{
extern struct font font8x12;
if (font == NULL)
return -1;
*font = &font8x12;
return 0;
}

View File

@ -0,0 +1,14 @@
#include "bootloader/bios.h"
//---
// Public
//---
/* _bios_dinfo() : get display information */
void _bios_dinfo(size_t *width, size_t *height)
{
if (width != NULL)
*width = 396;
if (height != NULL)
*height = 224;
}

View File

@ -0,0 +1,19 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "bootloader/bios.h"
//---
// Public
//---
/* _bios_dpixel() : dpixel wrapper */
void _bios_dpixel(int x, int y, int color)
{
extern uint16_t vram[];
if ((unsigned)x >= 396 || (unsigned)y >= 224)
return;
vram[(y * 396) + x] = color;
}

View File

@ -0,0 +1,89 @@
#include <stdint.h>
#include <stddef.h>
//---
// Internals
//---
/* Registers and operations */
enum {
device_code_read = 0x000,
driver_output_control = 0x001,
entry_mode = 0x003,
display_control_2 = 0x008,
low_power_control = 0x00b,
ram_address_horizontal = 0x200,
ram_address_vertical = 0x201,
write_data = 0x202,
horizontal_ram_start = 0x210,
horizontal_ram_end = 0x211,
vertical_ram_start = 0x212,
vertical_ram_end = 0x213,
};
/* r61524_select() : selecte screen mode */
static inline void r61524_select(uint16_t reg)
{
/* Clear RS and write the register number */
*(volatile uint8_t *)0xa405013c &= ~0x10;
__asm__ volatile ("synco"::);
*(volatile uint16_t *)0xb4000000 = reg;
__asm__ volatile ("synco"::);
/* Set RS back. We don't do this in read()/write() because the display
driver is optimized for consecutive GRAM access. LCD-transfers will
be faster when executing select() followed by several calls to
write(). (Although most applications should use the DMA instead.) */
*(volatile uint8_t *)0xa405013c |= 0x10;
__asm__ volatile ("synco"::);
}
/* r61524_read() : read information from screen */
static inline uint16_t r61524_read(void)
{
return *(volatile uint16_t *)0xb4000000;
}
/* r61524_write() : write information */
static void r61524_write(uint16_t data)
{
*(volatile uint16_t *)0xb4000000 = data;
}
//---
// Public
//---
/* expose VRAM here */
uint16_t vram[396*224];
/* _bios_dupdate() : small R61524 driver */
void _bios_dupdate(void)
{
/* Set the windows size */
r61524_select(horizontal_ram_start);
r61524_write(0);
r61524_select(horizontal_ram_end);
r61524_write(395);
r61524_select(vertical_ram_start);
r61524_write(0);
r61524_select(vertical_ram_end);
r61524_write(223);
/* Set the RAM position */
r61524_select(ram_address_horizontal);
r61524_write(0);
r61524_select(ram_address_vertical);
r61524_write(0);
/* Bind address 0xb4000000 to the data write command */
r61524_select(write_data);
/* send VRAM */
for (int i = 0; i < 396*224; ++i) {
r61524_write(vram[i]);
}
}

View File

@ -0,0 +1,11 @@
#include "fxcg50/asm_utils.h"
.text
!---
! Public
!---
/* _bios_free() (free) : free allocated memory */
function(_bios_free):
syscall(0x1f42)

View File

@ -0,0 +1,11 @@
#include "fxcg50/asm_utils.h"
.text
!---
! Public
!---
/* _bios_malloc() (malloc) : allocate memory */
function(_bios_malloc):
syscall(0x1f44)

View File

@ -0,0 +1,89 @@
#include "bootloader/console.h"
#include "fxcg50/keycode.h"
//---
// Public
//---
/* console_key_handle_special() : handle special key handling */
// TODO
// - F_UP -> history
// - F_DOWN -> history
int console_key_handle_special(struct console *console, int key)
{
switch (key)
{
case KEY_SHIFT:
console->input.mode.shift ^= 1;
return 1;
case KEY_ALPHA:
console->input.mode.maj ^= 1;
return 1;
case KEY_OPTN:
console->input.mode.ctrl ^= 1;
return 1;
case KEY_DOT:
console_buffer_in_insert(console, ' ');
return 1;
case KEY_DEL:
console_buffer_in_remove(console);
return 1;
case KEY_EXE:
console->input.mode.exit = 1;
return 1;
case KEY_LEFT:
console_buffer_in_cursor_move(console, -1);
return 1;
case KEY_RIGHT:
console_buffer_in_cursor_move(console, 1);
return 1;
default:
return 0;
}
}
/* console_key_handle(): Update the internal buffer with the given key code */
int console_key_handle(struct console *console, int key)
{
static const uint8_t keylist_alpha[] = {
KEY_XOT, KEY_LOG, KEY_LN, KEY_SIN, KEY_COS, KEY_TAN,
KEY_FRAC, KEY_FD, KEY_LEFTP, KEY_RIGHTP, KEY_COMMA, KEY_ARROW,
KEY_7, KEY_8, KEY_9,
KEY_4, KEY_5, KEY_6, KEY_MUL, KEY_DIV,
KEY_1, KEY_2, KEY_3, KEY_PLUS, KEY_MINUS,
KEY_0, 0xff
};
static const uint8_t keylist_num[] = {
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
KEY_PLUS, KEY_MINUS, KEY_MUL, KEY_DIV,
KEY_LEFTP, KEY_RIGHTP, KEY_COMMA, KEY_POWER,
KEY_DOT, KEY_FD, KEY_ARROW, 0xff
};
static const char keylist_num_char[] = "0123456789+-x/(),^.|_";
const uint8_t *keycode_list;
char character;
int i;
/* Get the appropriate key list */
keycode_list = keylist_alpha;
if (console->input.mode.shift == 1)
keycode_list = keylist_num;
/* Try to find the pressed key. */
i = -1;
while (keycode_list[++i] != 0xff && keycode_list[i] != key);
if (keycode_list[i] != key)
return 0;
/* handle mode then update the buffer */
if (console->input.mode.shift == 0) {
character = 'a' + i;
if (console->input.mode.maj == 1)
character = 'A' + i;
} else {
character = keylist_num_char[i];
}
console_buffer_in_insert(console, character);
return 1;
}

View File

@ -0,0 +1,61 @@
#include "bootloader/console.h"
#include "fxcg50/keycode.h"
//---
// Internlas
//---
/* keysc_fetch() : scan the KEYSC hardware module */
static int keysc_fetch(void)
{
uint16_t volatile *SH7305_KEYSC = (void*)0xa44b0000;
int column;
int key;
int row;
int registered;
uint16_t udata;
/* update the keycache */
row = 6;
key = 0x5f;
registered = 0x0000;
while (--row >= 0)
{
column = 16;
udata = SH7305_KEYSC[row];
if (registered != 0x0000)
continue;
while (--column >= 0)
{
if ((udata & (0x8000 >> column)) != 0) {
registered = KEYCODE_GEN(row, column);
break;
}
key -= 1;
}
}
return registered;
}
//---
// Public
//---
/* console_key_get() : small one-shot key waiter */
int console_key_get(void)
{
static uint16_t prev_key = 0xffff;
if (prev_key != 0xffff) {
while (1) {
if (prev_key != keysc_fetch())
break;
}
}
while (1) {
prev_key = keysc_fetch();
if (prev_key != 0x0000)
break;
}
return prev_key;
}

View File

@ -0,0 +1,297 @@
#include "fxcg50/asm_utils.h"
.section .bootloader.pre_text, "ax"
/* ___fxcg50_initialize() : bootstrap routine
We are currently virtualized as a common program (addin) at 0x00300000, but the
code currently executed is physically fragmented through the ROM. So, we cannot
perform ASLR self-relocation here.
The first thing we need to do is find the "user" RAM allocated by Casio (by
analyzing the TLB) and then performs a "self-translation" into this place.
After that, we will be able to perform ASLR self-relocation and classic
"kernel" bootstrapping.. */
function(__fxcg50_primary_bootloader):
! ---
! *CRITICAL*
!
! The next two instructions will be patched *DURING COMPILE TIME* with the
! complete image size (bootloader, ASLR, kernel, OS,...) as follows:
! > mov.l complet_image_size, r0
! > nop
! If not patched, the code will just return and indicate to Casio not to
! restart the application.
! ---
rts
mov #1, r0
bra translation_entry
nop
.long 0x00000000
translation_entry:
! ---
! prepare alias
!
! We don't need to setup the stack because we are loaded from CasioABS
! (Casio OS) as a simple program (addin) with a given stack, which we will
! not use even if we use "special" registers such as r8, r9, ... as we will
! be careful to never return from here.
!
! @note
! - r0 is reused
! - r1 is reused
! - r2 is reused
! - r7 is reused
! - r14 is reused
! ---
#define counter r0
#define utlb_addr_val r1
#define utlb_data_val r2
#define uram_phys_addr r3
#define rom_base_addr r4
#define rom_image_size r5
#define utlb_data_ptr r6
#define utlb_addr_ptr r7
#define uram_virt_ptr r8
#define uram_phys_size r9
#define utlb_valid_bit r10
#define utlb_data_ptr_saved r11
#define utlb_magic_array r12
#define utlb_addr_ptr_saved r13
#define utlb_data_ppn_mask r14
! ---
! Save critical information
!
! Calculate the runtime loaded address of the addin and save the
! compile-time image size information
! ---
! save image size
mov r0, rom_image_size
! precalculate runtime loaded address
mova utlb_fetch_uram_info, r0
add #(___fxcg50_primary_bootloader - utlb_fetch_uram_info), r0
mov r0, rom_base_addr
! ---
! Configure cache
!
! This is a critical part because we will move to the URAM and perform
! self-patching symbols during our execution. So, we need to have a
! consistent cache behavior to help the MPU follow our instructions.
!
! The SH7305 has a special register for that called Cache Control Register
! (CCR), and it seems tobe the same as SH7724 (except that no official POC
! or information can invalidate the instruction Cache (IC) without causing
! the machine to crash):
!
! - Indicate that P1 area use the "Write-back" method
! - Indicate that P0/P3 areas use the "Write-through" method
! - Enable Operand Cache (OC)
! - Enable Instruction Cache (IC)
! - Invalidate all Operand Cache (OC) entries
! ---
mov.l ccr_register_addr, r0
mov.l ccr_register_data, r1
mov.l r1, @r0
synco
! ---
! UTLB scan to find URAM information
!
! As explained above, we first need to find the user RAM given by Casio for
! our application, as we know that is an unused and stable space for us.
!
! We will scan each TLB entry to find the user's RAM. The memory is
! virtually mapped by Casio using the same historical virtual address:
! 0x08100000. Also, all the given RAM is entierly (?) mapped by the
! operating system. Thus, we walk through the TLB until we don't find the
! next memory fragment; this will allow us to find the size of the RAM
! (which varies between models and emulators).
!
! We will also take advantage of the fact that Casio *MAP* the virtual
! address 0x00000000 (NULL) for no valid reason. So, if we find this
! mapping, we will invalidate it to be sure that a NULL manipulated pointer
! will cause a TLBmiss exception.
!
! TODO : invalidate NULL page
! ---
utlb_fetch_uram_info:
! fetch external information
mov.l data_00000100, utlb_valid_bit
mov.l data_08100000, uram_virt_ptr
mov.l data_f6000000, utlb_addr_ptr
mov.l data_14100c0a, utlb_magic_array
mov.l data_1ffffc00, utlb_data_ppn_mask
mov.l data_f7000000, utlb_data_ptr
! prepare internal vars
mov #0, counter
mov #-1, uram_phys_addr
mov #0, uram_phys_size
mov utlb_data_ptr, utlb_data_ptr_saved
mov utlb_addr_ptr, utlb_addr_ptr_saved
utlb_walk_loop:
! validity check
! @note
! - check the validity bit for each UTLB data and address entry
! - both data and address entry have the same Valid bit position
mov.l @utlb_addr_ptr, utlb_addr_val
tst utlb_valid_bit, utlb_addr_val
bt utlb_walk_cond_check
mov.l @utlb_data_ptr, utlb_data_val
tst utlb_valid_bit, utlb_data_val
bt.s utlb_walk_cond_check
! check VPN validity
! @note
! - "UTLB Address Array" (p239) - Figure 7.14
! - we need to clear the first 10 bits of the fetched UTLB data to get the
! the "real" virtual address (eliminate ASID, Dirty and Valid bits)
shlr8 utlb_addr_val
shlr2 utlb_addr_val
shll8 utlb_addr_val
shll2 utlb_addr_val
cmp/eq uram_virt_ptr, utlb_addr_val
bf.s utlb_walk_cond_check
! fetch the page size
! @note
! - "Unified TLB (UTLB) Configuration"(p221)
! - page size is weird to calculate for many hardware reasons, and this code
! is the literal translation of :
! > size = ((data->SZ1 << 1) | data->SZ2) << 3;
! > size = 1 << ((0x14100c0a >> size) & 0xff);
mov #-1, r1
mov utlb_data_val, r0
tst #128, r0
mov #-1, r7
negc r1, r1
tst #16, r0
add r1, r1
negc r7, r7
or r7, r1
mov utlb_magic_array,r7
shll2 r1
add r1, r1
neg r1, r1
shad r1, r7
extu.b r7, r1
mov #1, r7
shld r1, r7
! update counter / information
add r7, uram_phys_size
add r7, uram_virt_ptr
! check if the URAM physical address is already set
mov uram_phys_addr,r0
cmp/eq #-1,r0
bf utlb_page_found_restart
! calculate the physical address of the page (URAM)
! @note
! - "UTLB Data Array"(p240) - Figure 7.15
! - to fetch the Physical Address, we just need to isolate the PPN
and utlb_data_ppn_mask, utlb_data_val
shlr8 utlb_data_val
shlr2 utlb_data_val
mov utlb_data_val, uram_phys_addr
shll8 uram_phys_addr
shll2 uram_phys_addr
utlb_page_found_restart:
mov r13, utlb_addr_ptr
mov r11, utlb_data_ptr
bra utlb_walk_loop
mov #0, counter
utlb_walk_cond_check:
! update internal counter
! @notes
! - only 64 UTLB entry
! - UTLB entry (for data and address) gap is 0x100
mov.l data_00000100, r1
add #1, counter
cmp/eq #64, counter
add r1, utlb_addr_ptr
bf.s utlb_walk_loop
add r1, utlb_data_ptr
! ---
! Self-translation to URAM
!
! Now that we have the user RAM entry address (uram_phys_addr) and its size
! (uram_phys_size), we can self-translate to this location using a dummy
! byte-per-byte copy.
!
! Note that, for now, no random installation offset is performed since
! predicting uncertain behavior is complex to choose for now.
! ---
self_translation:
! fetch bootloader ROM geometry information
mov rom_base_addr, r0
mov rom_image_size, r2
! generate uncachable P2 URAM address
! TODO
! - random offset
! - check oversize
mov.l data_a0000000, r1
or uram_phys_addr, r1
! dump the complet image into URAM
self_dump_uram:
mov.b @r0, r14
mov.b r14, @r1
dt r2
add #1, r0
add #1, r1
bf.s self_dump_uram
nop
! Prepare the self-translation by calculating the new PC position using a
! P1 address to allow caching to be performed.
! @note
! - ___fxcg50_bootloader_start is a global symbol compiled with NULL as the
! base address. So, we don't have to calculate the gap between the start
! of the ROM and the symbol.
mov.l data_80000000, r1
or uram_phys_addr, r1
mov.l real_bootloader_start, r0
add r1, r0
! self-translation
mov rom_image_size, r6
mov uram_phys_addr, r5
mov rom_base_addr, r4
jmp @r0
nop
.balign 4
data_08100000: .long 0x08100000 ! Casio addin load virtual address
data_f6000000: .long 0xf6000000 ! SH7305 UTLB Address address
data_f7000000: .long 0xf7000000 ! SH7305 UTLB Data addresss
data_14100c0a: .long 0x14100c0a ! Magic UTLB page size table
data_1ffffc00: .long 0x1ffffc00 ! UTLB Address PPN mask
data_00000100: .long 0x00000100 ! UTLB entry gap and UTLB validity bit
data_a0000000: .long 0xa0000000 ! P2 base address
data_80000000: .long 0x80000000 ! P1 base address
ccr_register_addr: .long 0xff00001c ! SH7305.CACHE.CCR register address
ccr_register_data: .long 0x0000010f ! CCR configuration
real_bootloader_start:
.long ___fxcg50_bootloader_start

View File

@ -0,0 +1,130 @@
#include "fxcg50/asm_utils.h"
.section .bootloader.text, "ax"
/* ___fxcg50_bootloader_start() : real bootstrap entry
Now we are in the URAM we can performs ASLR patching, setup stack and involve
the first high-level C code which will perform kernel setup.
The primary (fake) bootloader (previous operations) have setup some arg:
- r4 = ROM relocation base address
- r5 = RAM relocation base address (physical)
- r6 = image size */
function(__fxcg50_bootloader_start):
! ---
! prepare alias
! ---
#define rom_reloc_base r4
#define ram_reloc_base r5
#define image_size r6
! ---
! ASLR application
!
! perform ASLR patch by using the symbols table information injected
! during bootloader build steps at the end of the bootloader code marked
! with ___bootloader_code_end.
!
! The content of the table has been generated during post-compiling script
! ---
! The table symbol is not aready resolved (its our job), we must manually
! calculate the real address of the symbols table
mov.l bootloader_code_end, r0
mov.l p1_addr_base, r1
mov.l p2_addr_base, r2
or ram_reloc_base, r2
or ram_reloc_base, r1
add r2, r0
! walk trough the symbols table and patch all location
! @note
! - we MUST perform patching using P2 (uncachable) area to avoid
! inconsistancy behaviour with the cache.
! - symbols are relocalize through P1 (cachable) area
aslr_symbol_patch_loop:
mov.l @r0, r8
tst r8, r8
bt aslr_commit
add r2, r8
mov.l @r8, r9
add r1, r9
mov.l r9, @r8
mov.l r8, @r0
add #4, r0
bra aslr_symbol_patch_loop
nop
aslr_commit:
! Now that ASLR symbols has been updated using uncachable area (P2), we
! need to invalitate all Operands Cache entry that the MPU have possibly
! setup to avoid inconsistant `mov.x` behaviour
! @note
! - CCR.OCI = 1 -> Operand Cache Invalidation (self-cleared to 0)
mov.l ccr_reg_addr, r1
mov.l @r1, r0
or #0x08, r0
mov.l r0, @r1
synco
setup_stack:
! TODO : watermark stack area for statistics
! TODO : stack switch
! TODO : stack canary
bootloader_c_invokation:
mov.l p2_addr_base, r2
mov ram_reloc_base, r4
mov image_size, r5
mov.l bootloader_main, r0
jsr @r0
or r2, r4
! ---
! bootloader panic
!
! As we have probably wierdly restored hadware information, if the
! bootloader main routine return we simply display black screen. You can
! uncomment following instruction to allows getkey() to return to the menu
! (debug only)
! ---
bootloader_paniktamer:
mov.l syscall_trampoline, r8
mov #0x4a, r4
mov #3, r5
mov.l syscall_id, r0
jsr @r8
nop
test1:
! add #-4, r15
! mov r15, r4 ! column
! add #-4, r15
! mov r15, r5 ! row
! add #-4, r15
! mov r15, r1 ! keycode
! mov #0, r6 ! type of waiting (KEYWAIT_HALTON_TIMEOFF)
! mov #0, r7 ! timeout period
! mov.l r1, @-r15 ! keycode
! mov #0, r2
! mov.l r2, @-r15 ! [menu] key return to menu
! mov.l getkey_id, r0
! jsr @r8
! nop
bra test1
nop
.balign 4
bootloader_main: .long _bootloader_main
bootloader_code_end: .long ___bootloader_code_end
syscall_trampoline: .long 0x80020070
syscall_id: .long 0x0276
p2_addr_base: .long 0xa0000000
p1_addr_base: .long 0x80000000
getkey_id: .long 0x000012bf ! GetKeyWait_OS syscall ID
ccr_reg_addr: .long 0xff00001c ! SH7305.CACHE.CCR register address

View File

@ -0,0 +1,24 @@
VXDEV_TOOLCHAIN_PREFIX = 'aarch64-linux-gnu-'
VXDEV_TOOLCHAIN_PROCESSOR = 'aarch64'
VXDEV_TOOLCHAIN_ENDIANNESS = 'little'
VXDEV_CFLAGS = [
'-Wall',
'-Wextra',
'-D__SUPPORT_RASPI3B__',
'-ffreestanding',
'-nostdlib',
'-fPIE',
'-O3',
'-mlittle-endian',
'-march=armv8-a',
'-fstrict-volatile-bitfields',
]
VXDEV_LDFLAGS = [
'-static',
'-Wl,-Map=map',
'-Wl,--build-id=none',
'-Wl,--emit-relocs',
]
VXDEV_ASSETS = [
'font8x12',
]

View File

@ -0,0 +1,130 @@
#ifndef VXGOS_BOOTLOADER_RASPI3B_AUXILIARY_H
#define VXGOS_BOOTLOADER_RASPI3B_AUXILIARY_H
#include "raspi3b/utils.h"
//---
// BCM2837 Auxiliary peripheral
//
// @note
// <> all of theses bits is based on the documentation on
// https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf
// <> we use MSB instead of LSB packed struct because of the architecture
// note that this cannot be modified with compiler flags.
//---
struct __bcm2837_aux
{
/* [0x00215000] pending interrupt flags */
const u32_union(AUX_IRQ,
uint32_t MiniUART :1; /* Enable Mini UART */
uint32_t SPI1 :1; /* Enable SPI1 */
uint32_t SPI2 :1; /* Enable SPI2 */
uint32_t :29;
);
/* [0x00215004] enable auxialiary modules */
u32_union(AUX_ENABLE,
uint32_t MiniUART :1; /* Enable Mini UART */
uint32_t SPI1 :1; /* Enable SPI1 */
uint32_t SPI2 :1; /* Enable SPI2 */
uint32_t :29;
);
pad(0x38);
//---
// Mini UART register
//---
/* [0x00215040] used to write or write data from the FIFOs. */
u32_union(AUX_MU_IO,
uint32_t DATA : 8;
uint32_t :24;
);
/* [0x00215044] used to enable interrupts */
u32_union(AUX_MU_IER,
uint32_t RIE :1; /* Receive Interrupt Enable */
uint32_t TIE :1; /* Transmit Interrupt Enable */
uint32_t :30;
);
/* [0x00215048] Interrupt Status */
u32_union(AUX_MU_IIR,
uint32_t PENDING :1; /* =0 if pending interrupts */
uint32_t TXFLUSH :1; /* Receiver holds valid byte */
uint32_t RXFLUSH :1; /* Transmit holding register empty */
uint32_t :3;
uint32_t FIFO :2; /* FIFO enabled (TODO: check RX/TX) */
uint32_t :24;
);
/* [0x0021504c] Control Line Data */
u32_union(AUX_MU_LCR,
uint32_t CHR :2; /* Character Length */
uint32_t :4;
uint32_t BREAK :1; /* Pull down the UART1_TX line */
uint32_t DLAB :1; /* Give access to the Baudrate reg */
uint32_t :24;
);
/* [0x00215050] Control the 'modem' signals */
u32_union(AUX_MU_MCR,
uint32_t :1;
uint32_t RTS :1; /* Pull down the UART1_RTS line */
uint32_t :30;
);
/* [0x00215054] Data Status */
const u32_union(AUX_MU_LSR,
uint32_t DRIE :1; /* Received Data Ready */
uint32_t ORER :1; /* Overrrun Error */
uint32_t :3;
uint32_t TDFE :1; /* Transmitter Empty */
uint32_t TDFI :1; /* Transmitter Idle */
uint32_t :25;
);
/* [0x00215058] Data 'modem' status */
const u32_union(AUX_MU_MSR,
uint32_t :4;
uint32_t CTS :1; /* Inverse of the UART1_CTS input */
uint32_t :27;
);
/* [0x0021505c] Single Byte Storage */
u32_union(AUX_MU_SCRATCH,
uint32_t SCRATCH :8; /* SDC byte extra */
uint32_t :24;
);
/* [0x00215060] Access to Extra features */
u32_union(AUX_MU_CNTL,
uint32_t RE :1; /* Receiver Enable */
uint32_t TE :1; /* Transmitter Enable */
uint32_t ERAF :1; /* Enable Rx Auto Flow-control */
uint32_t ETAF :1; /* Enable Tx Auto Flow-control */
uint32_t RTS_AUTO :2; /* Rx Auto Flow Level */
uint32_t RTS_ASSERT :1; /* Rx RTS invert */
uint32_t CTS_ASSERT :1; /* CTS invert */
uint32_t :24;
);
/* [0x00215064] Internal Status */
const u32_union(AUX_MU_STAT,
uint32_t SYMA :1; /* Symbol Available */
uint32_t SPCA :1; /* Tx can accept a least one byte */
uint32_t RII :1; /* Rx is idle */
uint32_t TII :1; /* Tx is idle */
uint32_t ROVRE :1; /* Rx overrun */
uint32_t TDFF :1; /* Tx FIFO is full */
uint32_t RTS :1; /* status of the UART1_RTS line */
uint32_t CTS :1; /* status of the UART1_CTS line */
uint32_t TDFE :1; /* Tx FIFO is empty */
uint32_t TEND :1; /* Tx End */
uint32_t :6;
uint32_t RFFL :4; /* Rx FIFO fill level */
uint32_t :4;
uint32_t TFFL :4; /* Tx FIFO fill level */
uint32_t :4;
);
/* [0x00215068] baudrate counter */
u32_union(AUX_MU_BAUD,
uint32_t BAUD :16; /* Baudrate */
uint32_t :16;
);
} VPACKED(4);
#define BCM2837_AUXILIARY (*((volatile struct __bcm2837_aux *)0x3f215000))
#endif /* VXGOS_BOOTLOADER_RASPI3B_AUXILIARY_H */

Some files were not shown because too many files have changed in this diff Show More