Skip to content

Commit

Permalink
✨ Expose VEXos' time and date functions (#127)
Browse files Browse the repository at this point in the history
* Expose VEXos' get time and date functions

* wip: add clock_gettime impl

* wip: fix errors with clock_gettime impl

* wip: add _gettimeofday impl

* Push code to build test for time implementation

* Add integer timestamp to the generated timestamp info

* Fix compile errors

* Fix compile errors 2

* Change _PROS_COMPILE_TIMESTAMP_INT to be an actual integer

* Test linking against _pros_ld_timestamp.o variables

* Test linking against _pros_ld_timestamp.o variables 2

* Test linking against _pros_ld_timestamp.o variables 3

* Test linking against _pros_ld_timestamp.o variables 4

* Convert _PROS_COMPILE_TIMESTAMP_INT back into a string

* Try using HOT_TABLE rather than the timestamp directly

* Make _PROS_COMPILESTAMP_INT a weak symbol

* Fix compiler error

* Attempt to use functions to subvert linking issues

* Try switching _PROS_COMPILE_TIMESTAMP_INT to an integer again

* Revert back to hacky callback solution

* Fix compile error

* Fix compile error 2

* Fix compile error 3

* Fix compile error 4

* Fix?

* Fix microsecond calculation

* Account for timezones when generating _PROS_COMPILE_TIMESTAMP_INT

* Fix formatting and accidental changes

* Commit for applying template

* Fix compilation errors and begin implementing clock_settime()

* Update firmware

* Update common.mk to allow POSIX functions in time.h

* Add CLOCK_MONOTONIC to clock_gettime()

* Add comment

* Fix version

Co-authored-by: Alex Brooke <[email protected]>
Co-authored-by: WillXuCodes <[email protected]>
Co-authored-by: Richard Stump <[email protected]>
  • Loading branch information
4 people committed Oct 18, 2022
1 parent 2cf5fb2 commit a1e0d24
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 4 deletions.
14 changes: 11 additions & 3 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ARCHTUPLE=arm-none-eabi-
DEVICE=VEX EDR V5

MFLAGS=-mcpu=cortex-a9 -mfpu=neon-fp16 -mfloat-abi=softfp -Os -g
CPPFLAGS=-D_POSIX_THREADS -D_UNIX98_THREAD_MUTEX_ATTRIBUTES
CPPFLAGS=-D_POSIX_THREADS -D_UNIX98_THREAD_MUTEX_ATTRIBUTES -D_POSIX_TIMERS -D_POSIX_MONOTONIC_CLOCK
GCCFLAGS=-ffunction-sections -fdata-sections -fdiagnostics-color -funwind-tables

WARNFLAGS+=-Wno-psabi
Expand Down Expand Up @@ -227,7 +227,7 @@ $(COLD_BIN): $(COLD_ELF)
$(COLD_ELF): $(COLD_LIBRARIES)
$(VV)mkdir -p $(dir $@)
$(call test_output_2,Creating cold package with $(ARCHIVE_TEXT_LIST) ,$(LD) $(LDFLAGS) $(call wlprefix,--gc-keep-exported --whole-archive $^ -lstdc++ --no-whole-archive) $(call wlprefix,-T$(FWDIR)/v5.ld $(LNK_FLAGS) -o $@),$(OK_STRING))
$(call test_output_2,Stripping cold package ,$(OBJCOPY) --strip-symbol=install_hot_table --strip-symbol=__libc_init_array --strip-symbol=_PROS_COMPILE_DIRECTORY --strip-symbol=_PROS_COMPILE_TIMESTAMP $@ $@, $(DONE_STRING))
$(call test_output_2,Stripping cold package ,$(OBJCOPY) --strip-symbol=install_hot_table --strip-symbol=__libc_init_array --strip-symbol=_PROS_COMPILE_DIRECTORY --strip-symbol=_PROS_COMPILE_TIMESTAMP --strip-symbol=_PROS_COMPILE_TIMESTAMP_INT $@ $@, $(DONE_STRING))
@echo Section sizes:
-$(VV)$(SIZETOOL) $(SIZEFLAGS) $@ $(SIZES_SED) $(SIZES_NUMFMT)

Expand Down Expand Up @@ -272,7 +272,15 @@ $(VV)mkdir -p $(dir $(LDTIMEOBJ))
@# Pipe a line of code defining _PROS_COMPILE_TOOLSTAMP and _PROS_COMPILE_DIRECTORY into GCC,
@# which allows compilation from stdin. We define _PROS_COMPILE_DIRECTORY using a command line-defined macro
@# which is the pwd | tail bit, which will truncate the path to the last 23 characters
$(call test_output_2,Adding timestamp ,echo 'char const * const _PROS_COMPILE_TIMESTAMP = __DATE__ " " __TIME__; char const * const _PROS_COMPILE_DIRECTORY = "$(shell pwd | tail -c 23)";' | $(CC) -c -x c $(CFLAGS) $(EXTRA_CFLAGS) -o $(LDTIMEOBJ) -,$(OK_STRING))
@#
@# const int _PROS_COMPILE_TIMESTAMP_INT = $(( $(date +%s) - $(date +%z) * 3600 ))
@# char const * const _PROS_COMPILE_TIEMSTAMP = __DATE__ " " __TIME__
@# char const * const _PROS_COMPILE_DIRECTORY = "$(shell pwd | tail -c 23)";
@#
@# The shell command $$(($$(date +%s)+($$(date +%-z)/100*3600))) fetches the current
@# unix timestamp, and then adds the UTC timezone offset to account for time zones.

$(call test_output_2,Adding timestamp ,echo 'const int _PROS_COMPILE_TIMESTAMP_INT = $(shell echo $$(($$(date +%s)+($$(date +%-z)/100*3600)))); char const * const _PROS_COMPILE_TIMESTAMP = __DATE__ " " __TIME__; char const * const _PROS_COMPILE_DIRECTORY = "$(shell pwd | tail -c 23)";' | $(CC) -c -x c $(CFLAGS) $(EXTRA_CFLAGS) -o $(LDTIMEOBJ) -,$(OK_STRING))
endef

# these rules are for build-compile-commands, which just print out sysroot information
Expand Down
19 changes: 19 additions & 0 deletions include/pros/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,25 @@ failed to connect or an invalid id is given.
errno = EACCES; \
return PROS_ERR; \
} \
/******************************************************************************/
/** Date and Time **/
/******************************************************************************/

extern const char* baked_date;
extern const char* baked_time;

typedef struct {
uint16_t year; // Year - 1980
uint8_t day;
uint8_t month; // 1 = January
} date_s_t;

typedef struct {
uint8_t hour;
uint8_t min;
uint8_t sec;
uint8_t sec_hund; // hundredths of a second
} time_s_t;

#ifdef __cplusplus
namespace c {
Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ void opcontrol() {

left_mtr = left;
right_mtr = right;

pros::delay(20);
}
}
1 change: 1 addition & 0 deletions src/system/dev/ser_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

__attribute__((weak)) char const* const _PROS_COMPILE_TIMESTAMP = "Unknown";
__attribute__((weak)) char const* const _PROS_COMPILE_DIRECTORY = "Unknown";
__attribute__((weak)) const int _PROS_COMPILE_TIMESTAMP_INT = 0;

void print_small_banner(void) {
uint32_t uptime = millis();
Expand Down
16 changes: 16 additions & 0 deletions src/system/hot.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ struct hot_table* const HOT_TABLE = &__HOT_TABLE;
#define MAGIC0 0x52616368
#define MAGIC1 0x8CEF7310

extern void set_get_timestamp_int_func(const int (*func)(void));
static const int get_timestamp_int(void);

__attribute__((section(".hot_magic"))) uint32_t MAGIC[] = {MAGIC0, MAGIC1};
uint32_t const volatile* const MAGIC_ADDR = MAGIC;

Expand All @@ -18,6 +21,7 @@ uint32_t const volatile* const MAGIC_ADDR = MAGIC;
// regenerates that function with the call to the correct (user-written) C++ version
extern char const* _PROS_COMPILE_TIMESTAMP;
extern char const* _PROS_COMPILE_DIRECTORY;
extern const int _PROS_COMPILE_TIMESTAMP_INT;

extern unsigned __exidx_start;
extern unsigned __exidx_end;
Expand Down Expand Up @@ -63,6 +67,10 @@ __attribute__((section(".hot_init"))) void install_hot_table(struct hot_table* c
for (void (*const* ctor)() = __init_array_start; ctor < __init_array_end; ctor++) {
(*ctor)();
}

// Set the function pointer in newlib_stubs so that it can fetch the
// timestamp in the hot package.
set_get_timestamp_int_func(get_timestamp_int);
}

// this function really exists on the cold section! Called by pros_init
Expand All @@ -78,3 +86,11 @@ void invoke_install_hot_table() {
memset(HOT_TABLE, 0, sizeof(*HOT_TABLE));
}
}

// This is a callback function used by newlib to get the unix timestamp
// newlib cannot access any symbols in the hot package, so we have the hot
// package pass a function pointer to this function. Newlib then uses that
// function pointer.
static const int get_timestamp_int(void) {
return _PROS_COMPILE_TIMESTAMP_INT;
}
110 changes: 110 additions & 0 deletions src/system/newlib_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@

#include <errno.h>
#include <stdint.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>

#include "rtos/task.h"
#include "v5_api.h"

#include "hot.h"
#include "pros/misc.h"

#define SEC_TO_MSEC 1000
#define SEC_TO_MICRO 1000000
#define MICRO_TO_NANO 1000

void _exit(int status) {
if(status != 0) dprintf(3, "Error %d\n", status); // kprintf
Expand Down Expand Up @@ -56,3 +62,107 @@ int getentropy(void *_buffer, size_t _length) {
void __sync_synchronize(void) {
__sync_synchronize();
}

// These variables are used to store the user-set time.
// When user_time_set is false, the realtime clock will use the timestamp as the
// base time. When it is true will use user_time_spec as the base time instead.
// set_microseconds stores the value of the microsecond timer when the user set
// the time.
static bool user_time_set = false;
static struct timespec user_time_spec;
static int64_t set_microseconds = 0;

int clock_settime(clockid_t clock_id, const struct timespec *tp) {
int retval = -1;

switch(clock_id) {
case CLOCK_REALTIME:
user_time_set = true;
user_time_spec = *tp;
set_microseconds = vexSystemHighResTimeGet();
retval = 0;
default:
errno = EINVAL;
}

return retval;
}

int clock_gettime(clockid_t clock_id, struct timespec* tp) {
struct timeval tv;
int retval = -1;

switch (clock_id)
{
case CLOCK_REALTIME:
retval = gettimeofday(&tv, NULL);
if (!retval) TIMEVAL_TO_TIMESPEC(&tv, tp);
break;
case CLOCK_MONOTONIC: {
uint64_t totalTime = vexSystemHighResTimeGet();
uint64_t secs = totalTime / SEC_TO_MICRO;
uint64_t micros = totalTime - secs * SEC_TO_MICRO;

tp->tv_sec = secs;
tp->tv_nsec = micros * MICRO_TO_NANO;
break;
}
default:
errno = EINVAL;
break;
}

return retval;
}

// HACK:
//
// This function pointer serves as a callback so that _gettimeofday() can call
// a function inside the hot package. Without this, _gettimeofday() cannot
// access any symbols in the hot package (where _PROS_COMPILE_TIMESTAMP_INT
// lives), and linker errors occur.
//
// When the hot package is initialized, it calls set_get_timestamp_int_func()
// and sets the callback to a function that returns the unix timestamp.
//
// Essentially, when the hot process starts:
// 1) Pass the get_timestamp_int_func to the cold package
// 2) When the cold package (this library) needs to access the timestamp,
// call the callback
// 3) Then the cold package
static const int (*get_timestamp_int_func)(void) = NULL;

void set_get_timestamp_int_func(const int (*func)(void))
{
get_timestamp_int_func = func;
}

int _gettimeofday(struct timeval* tp, void* tzvp) {
if(get_timestamp_int_func == NULL) {
return -1;
}

if(user_time_set) {
tp->tv_sec = user_time_spec.tv_sec;
tp->tv_usec = user_time_spec.tv_nsec * 1000;
tp->tv_usec += vexSystemHighResTimeGet() - set_microseconds;
}
else if (competition_is_connected()) {
// TODO: update this to get the date/time through VexOS. Apparently,
// the time is kept properly only when competition controls are
// connected. I haven't had time to check or confirm this.
//https://github.com/purduesigbots/pros/pull/127#issuecomment-1095361338
tp->tv_sec = get_timestamp_int_func();
tp->tv_usec = vexSystemHighResTimeGet();
}
else {
// When competition isn't connected, the vex's date/time functions do
// not work. Here we use a timestamp compiled into the program and then
// add the number of microseconds the program has been running to get
// the best estimate.
tp->tv_sec = get_timestamp_int_func();
tp->tv_usec = vexSystemHighResTimeGet();
}

return 1;
}
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.7.1
3.7.1

0 comments on commit a1e0d24

Please sign in to comment.