From: ljsebald <ljs...@us...> - 2024-01-29 02:22:03
|
This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "A pseudo Operating System for the Dreamcast.". The branch, master has been updated via 42f4e9aee02c38aaeb6aa63688d1910f2b47853d (commit) from e309ccb1c6363de8cbe8206a1ed7f307b27bef2d (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 42f4e9aee02c38aaeb6aa63688d1910f2b47853d Author: Falco Girgis <gyr...@gm...> Date: Sun Jan 28 20:21:17 2024 -0600 UBC Driver Upgrade (#416) * Started implementation, register configuration - Added first pass at driver for validating configuration and selecting an available UBC channel (when there is one) - Started programming UBC registers based on breakpoints * More driver implementation work. * Old UBC driver removed * Added a WIP example/testbed. * UBC breakpoints working! - Driver actually works now, fixed a few bugs - Started on test app, which demonstrates a complex data breakpoint * Cleanup pass + lots of Doxygen documentation - Cleaned up the UBC driver header and source - Removed commented out legacy stub - Fixed initialization so that it happens without the user having to call ubc_init() manually - changed ubc_breakpoint_t::address from uintptr_t to void* - Ensured proper calls to ubc_wait() after programming UBC registers * Completed documentation, renamed a few things. * More test coverage - Added test coverage for super complex data watchpoint scenarios - Documented entirety of UBC driver - Reordered channel checking in the break IRQ handler to service channel B first, which is required for sequential breakpoints to work right. - Added logic to migrate non-data breakpoints from channel B to channel A if a breakpoint gets removed from channel A, so that B is available for a data watchpoint later. - All tests in the example/test suite are passing. - Still need to test for API error conditions - Still need to test channel B->A migrations * UBC driver, API, examples/tests - Finished commenting and fleshing out the example/test suite - Fixed a few bugs in corner cases after testing them all - Reorganized ubc_breakpoint_t inner structs and default values - wrote more detailed doxygen comments for ubc.h + detailed all potential "gotchas" when working with the API * Fixed Doxygen warnings. * Forgot to update the CHANGELOG... Oops. * Apply suggestions from code review ----------------------------------------------------------------------- Summary of changes: doc/CHANGELOG | 1 + examples/dreamcast/basic/Makefile | 5 +- .../basic/{threading/once => breaking}/Makefile | 12 +- examples/dreamcast/basic/breaking/breaking.c | 358 ++++++++++++++++++ include/kos/cdefs.h | 7 +- include/kos/iovec.h | 5 +- kernel/arch/dreamcast/hardware/Makefile | 2 +- kernel/arch/dreamcast/hardware/ubc.c | 406 +++++++++++++++++++++ kernel/arch/dreamcast/include/dc/ubc.h | 399 ++++++++++++++++---- kernel/arch/dreamcast/kernel/init.c | 10 +- 10 files changed, 1113 insertions(+), 92 deletions(-) copy examples/dreamcast/basic/{threading/once => breaking}/Makefile (57%) create mode 100644 examples/dreamcast/basic/breaking/breaking.c create mode 100755 kernel/arch/dreamcast/hardware/ubc.c diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 5ebce19b..62cd90d8 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -218,6 +218,7 @@ KallistiOS version 2.1.0 ----------------------------------------------- - DC Created separate performance counter driver and enhanced API [FG] - *** Implemented _POSIX_CPUTIME in clock_gettime() using perf counter timer [FG] - *** Implemented scandir() and alphasort() POSIX functions from dirent.h [FG] +- DC Added new driver for the SH4's UBC + high-level breakpoint API [FG] KallistiOS version 2.0.0 ----------------------------------------------- - DC Broadband Adapter driver fixes [Megan Potter == MP] diff --git a/examples/dreamcast/basic/Makefile b/examples/dreamcast/basic/Makefile index 45715c80..8c9132b7 100644 --- a/examples/dreamcast/basic/Makefile +++ b/examples/dreamcast/basic/Makefile @@ -1,7 +1,7 @@ # KallistiOS ##version## # # examples/dreamcast/basic/Makefile -# (c)2002 Megan Potter +# Copyright (C) 2002 Megan Potter # all: @@ -14,6 +14,7 @@ all: $(KOS_MAKE) -C stackprotector $(KOS_MAKE) -C memtest32 $(KOS_MAKE) -C watchdog + $(KOS_MAKE) -C breaking clean: $(KOS_MAKE) -C exec clean @@ -25,6 +26,7 @@ clean: $(KOS_MAKE) -C stackprotector clean $(KOS_MAKE) -C memtest32 clean $(KOS_MAKE) -C watchdog clean + $(KOS_MAKE) -C breaking clean dist: $(KOS_MAKE) -C exec dist @@ -36,3 +38,4 @@ dist: $(KOS_MAKE) -C stackprotector dist $(KOS_MAKE) -C memtest32 dist $(KOS_MAKE) -C watchdog dist + $(KOS_MAKE) -C breaking dist diff --git a/examples/dreamcast/basic/threading/once/Makefile b/examples/dreamcast/basic/breaking/Makefile similarity index 57% copy from examples/dreamcast/basic/threading/once/Makefile copy to examples/dreamcast/basic/breaking/Makefile index 52d83b34..87f96037 100644 --- a/examples/dreamcast/basic/threading/once/Makefile +++ b/examples/dreamcast/basic/breaking/Makefile @@ -1,11 +1,12 @@ # KallistiOS ##version## # -# basic/threading/once/Makefile -# Copyright (C) 2009 Lawrence Sebald +# examples/dreamcast/basic/breaking/Makefile +# Copyright (c) 2024 Falco Girgis # -TARGET = once_test.elf -OBJS = once_test.o +TARGET = breaking.elf +OBJS = breaking.o +KOS_CFLAGS += -std=gnu2x -Wno-strict-aliasing all: rm-elf $(TARGET) @@ -18,7 +19,7 @@ rm-elf: -rm -f $(TARGET) $(TARGET): $(OBJS) - kos-cc -o $(TARGET) $(OBJS) + kos-cc -o $(TARGET) $(OBJS) -lm run: $(TARGET) $(KOS_LOADER) $(TARGET) @@ -26,4 +27,3 @@ run: $(TARGET) dist: $(TARGET) -rm -f $(OBJS) $(KOS_STRIP) $(TARGET) - diff --git a/examples/dreamcast/basic/breaking/breaking.c b/examples/dreamcast/basic/breaking/breaking.c new file mode 100644 index 00000000..7564bb15 --- /dev/null +++ b/examples/dreamcast/basic/breaking/breaking.c @@ -0,0 +1,358 @@ +/* KallistiOS ##version## + + breaking.c + Copyright (C) 2024 Falco Girgis + +*/ + +/* This program serves as an example for using the API around the SH4's "User + Break Controller" to create and manage breakpoints. It was also written to + serve as a series of automatable tests validating the implementation of the + driver in KOS. + + The following breakpoint configurations are tested: + - breaking on instructions + - breaking on reading from a region of memory + - breaking on writing a particular value with a particular sized access + to a memory location + - breaking on accessing a region of memory with a particular sized + access with a particular range of values + - sequential breaking on accessing an instruction followed by + accessing a region of memory with a particular sized access with a + particular range of values +*/ + +#include <dc/ubc.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdalign.h> +#include <stdatomic.h> + +/* Convenience macro we use throughout the test suite to ensure our breakpoint + has or has not fired as expected at the given point in program execution. */ +#define VERIFY(expr) \ + do { \ + if(!(expr)) { \ + fprintf(stderr, "%s failed at %s:%d!\n", \ + #expr, __FUNCTION__, __LINE__); \ + return false; \ + } \ + } while(0); + +/* Atomic used to flag whether our breakpoint has been handled. */ +static atomic_bool handled = false; + +/* Callback function used to handle a breakpoint request. */ +static bool on_break(const ubc_breakpoint_t *bp, + const irq_context_t *ctx, + void *ud) { + /* Signal to the outside that the breakpoint has been handled. */ + handled = true; + + /* Print the location of the program counter when the breakpoint + IRQ was signaled (minus 2 if we're breaking AFTER instruction + execution!) */ + printf("\tBREAKPOINT HIT! [PC = %x]\n", (unsigned)CONTEXT_PC(*ctx) - 2); + + /* Userdata pointer used to hold a boolean used as the return value, which + dictates whether a breakpoint persists or is removed after being + handled. */ + return (bool)ud; +} + +/* Dummy test function used as breakpoint instruction target. */ +static __noinline int test_function(const char *str1, const char* str2) { + return strcmp(str1, str2); +} + +/* Configures and validates a generic breakpoint on an instruction address. */ +static bool break_on_instruction(void) { + /* Create a bare-minimal breakpoint which will break on any access to its + address. */ + const ubc_breakpoint_t bp = { + .address = test_function, /* Address of interest */ + }; + + /* Reset our breakpoint handler flag. */ + handled = false; + + printf("Breaking on instruction...\n"); + + /* Add the breakpoint with our handler and our userdata */ + VERIFY(ubc_add_breakpoint(&bp, on_break, (void *)true)); + + /* Call our test_function, which should trigger the breakpoint! */ + volatile int result = test_function("Sega", "Nintendo"); + (void)result; + + /* BREAKPOINT EXPECTED HERE! */ + + /* Verify our breakpoint was actually handled. */ + VERIFY(handled); + + printf("\tSUCCESS!\n"); + + return true; +} + +/* Configures and validates a read-only data watchpoint on a range of + addresses. */ +static bool break_on_data_region_read(void) { + char upper_boundary = 0; + alignas(1024) char vars[1024]; + char lower_boundary = 0; + + /* Creates a data breakpoint for anything that does a read from an address + that matches the upper 22 bits of "vars." */ + const ubc_breakpoint_t bp = { + /* Address of interest */ + .address = vars, + /* Low address bits to ignore, creating an address range */ + .address_mask = ubc_address_mask_10, + /* Operand/Data access only */ + .access = ubc_access_operand, + /* Operand-access breakpoint settings */ + .operand = { + /* Read-only */ + .rw = ubc_rw_read + } + }; + + handled = false; + + printf("Breaking on data region read...\n"); + + VERIFY(ubc_add_breakpoint(&bp, on_break, (void *)false)); + + /* Read from above the specified range. */ + volatile char temp; + temp = upper_boundary; (void)temp; + VERIFY(!handled); + + /* Read from below the specified range. */ + temp = lower_boundary; + VERIFY(!handled); + + /* Write to the specified range. */ + vars[512] = 1; + VERIFY(!handled); + + /* Read from the start of the specified range. */ + temp = vars[0]; + VERIFY(handled); + handled = false; + + /* Read from the middle of the specified range. */ + temp = vars[512]; + VERIFY(handled); + handled = false; + + /* Read from the end of the specified range. */ + temp = vars[1023]; + VERIFY(handled); + + VERIFY(ubc_remove_breakpoint(&bp)); + + printf("\tSUCCESS!\n"); + + return true; +} + +/* Configures and validates a data watchpoint looking for a particular value + to be written to a particular address as a particular size. */ +static bool break_on_sized_data_write_value(void) { + uint16_t var; + + /* Create a write-only data breakpoint on the address of "var," looking for + the value of `3` to be written. */ + const ubc_breakpoint_t bp = { + .address = &var, /* address to break on */ + .access = ubc_access_operand, /* instruction, operand, or both */ + .operand = { + .rw = ubc_rw_write, /* read, write, or both */ + .size = ubc_size_16bit, /* 8, 16, 32, 64-bit, or any */ + .data = { + .enabled = true, /* turn on data comparison */ + .value = 3 /* data to compare */ + } + } + }; + + handled = false; + + printf("Breaking on sized data write with value...\n"); + + VERIFY(ubc_add_breakpoint(&bp, on_break, (void *)true)); + + volatile uint16_t tmp; + tmp = var; (void)tmp; + VERIFY(!handled); /* We only did a read. */ + + var = 43; + VERIFY(!handled); /* We wrote the wrong value. */ + + *(uint8_t*)&var = 3; + VERIFY(!handled); /* We accessed it as the wrong size. */ + + var = 3; + /* BREAKPOINT SHOULD TRIGGER HERE! */ + VERIFY(handled); /* We wrote right value as the right size! */ + + printf("\tSUCCESS!\n"); + + return true; +} + +/* Watching for a read or write of a certain size, of a range of values, to or + from a region of memory. */ +static bool break_on_sized_operand_region_access_value_range(void) { + uint32_t upper_boundary = 0; + alignas(1024) uint32_t vars[1024 / sizeof(uint32_t)]; + uint32_t lower_boundary = 0; + + /* Create a read/write data breakpoint which looks for a sized operand + access to or from a range of addresses with a range of values. */ + const ubc_breakpoint_t bp = { + .address = vars, /* address to break on */ + .address_mask = ubc_address_mask_10, /* create address range */ + .access = ubc_access_operand, /* break on data access */ + .operand = { + .size = ubc_size_32bit, /* break on dword accesses */ + .data = { + .enabled = true, /* turn on data comparison */ + .value = 0x7ff, /* data to compare */ + .mask = 0x3 /* mask off 2 bits to create a value range */ + } + } + }; + + handled = false; + + printf("Breaking on sized operand region access with value range...\n"); + + VERIFY(ubc_add_breakpoint(&bp, on_break, (void *)false)); + + /* Read just above the region-of-interest. */ + volatile uint32_t tmp32; + tmp32 = upper_boundary; (void)tmp32; + VERIFY(!handled); + + /* Write just below the region-of-interest. */ + lower_boundary = tmp32; (void)lower_boundary; + VERIFY(!handled); + + /* Write to the region-of-interest as the wrong data size. */ + *(bool *)vars = 0x3; + VERIFY(!handled); + + /* Read from the region-of-interest as the wrong data size. */ + volatile uint16_t tmp16; (void)tmp16; + tmp16 = ((uint16_t *)vars)[1023 / sizeof(uint16_t)]; + VERIFY(!handled); + + /* Write to the region-of-interest an incorrect value. */ + vars[512 / sizeof(uint32_t)] = 0x8fd; + VERIFY(!handled); + + /* Write to the region-of-interest an incorrect value. */ + vars[512 / sizeof(uint32_t)] = 0x3; + VERIFY(!handled); + + /* Write to the region-of-interest an in-range value. */ + vars[512 / sizeof(uint32_t)] = 0x7ff; + VERIFY(handled); + handled = false; + + /* Write to the region-of-interest an in-range value. */ + vars[512 / sizeof(uint32_t)] = 0x7fd; + VERIFY(handled); + handled = false; + + /* Read from the region-of-interest an in-range value. */ + tmp32 = vars[512 / sizeof(uint32_t)]; + VERIFY(handled); + + VERIFY(ubc_remove_breakpoint(&bp)); + + printf("\tSUCCESS!\n"); + + return true; +} + +/* Configure and validate a sequential breakpoint. */ +bool break_on_sequence(void) { + alignas(1024) static uint32_t vars[1024 / sizeof(uint32_t)]; + + /* Create a sequential breakpoint on the conditions: + a. instruction - break on test_function + b. operand - break on writing value range to data range */ + VERIFY(ubc_add_breakpoint(&(static ubc_breakpoint_t) { + /* Address of first breakpoint */ + .address = test_function, + /* Second breakpoint in the sequence */ + .next = &(static ubc_breakpoint_t) { + .address = &vars, + .address_mask = ubc_address_mask_10, + .access = ubc_access_operand, + .operand = { + .rw = ubc_rw_write, + .size = ubc_size_32bit, + .data = { + .enabled = true, + .value = 0x7fc, + .mask = 0x3 + } + }, + } + }, + on_break, + (void *)true)); + + /* Reset whether we've been handled. */ + handled = false; + + printf("Breaking on sequence...\n"); + + vars[0] = 0xfc; + VERIFY(!handled); /* Wrote wrong value without condition A first */ + + test_function("Sega", "Sony"); + VERIFY(!handled); /* Hit condition A, but not B. */ + + *(uint16_t *)vars = 0x7fd; + VERIFY(!handled); /* Wrote wrong value as wrong sized type .*/ + + vars[512 / sizeof(uint32_t)] = 0xfc; + VERIFY(!handled); /* Wrote wrong value. */ + + vars[0] = 0x7fc; + VERIFY(handled); /* Wrote correct value, fulfilling condition B */ + + printf("\tSUCCESS!\n"); + + return true; +} + +/* Program entry point. */ +int main(int argc, char* argv[]) { + bool success = true; + + printf("Testing breakpoints...\n\n"); + + /* Run test cases and accumulate results. */ + success &= break_on_instruction(); + success &= break_on_data_region_read(); + success &= break_on_sized_data_write_value(); + success &= break_on_sized_operand_region_access_value_range(); + success &= break_on_sequence(); + + if(success) { + printf("\n***** Breakpoint Test: SUCCESS *****\n"); + return EXIT_SUCCESS; + } else { + fprintf(stderr, "\n***** Breakpoint Test: FAILURE *****\n"); + return EXIT_FAILURE; + } +} diff --git a/include/kos/cdefs.h b/include/kos/cdefs.h index 20407755..9e34f13a 100644 --- a/include/kos/cdefs.h +++ b/include/kos/cdefs.h @@ -155,10 +155,15 @@ #endif #ifndef __always_inline -/** \brief Ask the compiler to always inline a given function. */ +/** \brief Ask the compiler to \a always inline a given function. */ #define __always_inline inline __attribute__((__always_inline__)) #endif +#ifndef __no_inline +/** \brief Ask the compiler to \a never inline a given function. */ +#define __no_inline __attribute__((__noinline__)) +#endif + /* GCC macros for special cases */ /* #if __GNUC__ == */ diff --git a/include/kos/iovec.h b/include/kos/iovec.h index f3ac8244..64322f41 100644 --- a/include/kos/iovec.h +++ b/include/kos/iovec.h @@ -7,6 +7,7 @@ /** \file kos/iovec.h \brief Deprecated header file for I/O scatter/gather arrays. + \deprecated See <sys/uio.h> for what used to be here. */ @@ -16,7 +17,9 @@ #include <sys/uio.h> -/** \brief Compatibility typedef for old code. */ +/** \brief Compatibility typedef for old code. + \deprecated +*/ typedef struct iovec iovec_t; #endif /* __KOS_IOVEC_H */ diff --git a/kernel/arch/dreamcast/hardware/Makefile b/kernel/arch/dreamcast/hardware/Makefile index 460e6067..297c4493 100644 --- a/kernel/arch/dreamcast/hardware/Makefile +++ b/kernel/arch/dreamcast/hardware/Makefile @@ -25,7 +25,7 @@ OBJS += asic.o g2bus.o ...<truncated>... hooks/post-receive -- A pseudo Operating System for the Dreamcast. |