[s390x] Add time source based on the architectural Time-of-Day clock

The S/390 architecture provides instructions to read the Time-of-Day
(TOD) clock, which increments at a well-defined rate regardless of the
underlying physical clock speed, and has an epoch that starts from
zero at the beginning of the 20th century.

Use this clock to provide both interval timing (i.e. udelay() and
currticks()) and the wall-clock time source.  For short interval
timing, we choose to save on code size by treating one millisecond as
1024 microseconds.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-06-29 12:37:08 +01:00
parent e5d0c804ee
commit f39cf1cd5c
5 changed files with 265 additions and 1 deletions
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* Time-of-Day (TOD) Clock
*
*/
#include <errno.h>
#include <ipxe/time.h>
#include <ipxe/timer.h>
#include <ipxe/facility.h>
#include <ipxe/tod.h>
#include <config/time.h>
#ifdef TIME_TOD
#define TIME_PREFIX_tod
#else
#define TIME_PREFIX_tod __tod_
#endif
struct timer tod_timer __timer ( TIMER_PREFERRED );
/** Colour for debug messages */
#define colour &tod_timer
/**
* Get current wall-clock time in seconds since the Epoch
*
* @ret time Time, in seconds
*/
static time_t tod_now ( void ) {
unsigned long ticks;
/* Get current time in TOD ticks */
ticks = tod_ticks();
/* Convert (exactly) to seconds since the Epoch */
return ( ( ticks - TOD_EPOCH ) / ( 1000000 * TOD_TICKS_PER_US ) );
}
/**
* Get current system time in ticks
*
* @ret ticks Current time, in ticks
*/
static unsigned long tod_currticks ( void ) {
unsigned long ticks;
/* Get current time in TOD ticks */
ticks = tod_ticks();
/* Convert (approximately) to system ticks */
return ( ticks / ( TOD_TICKS_PER_MS / TICKS_PER_MS ) );
}
/**
* Delay for a fixed number of microseconds
*
* @v usecs Number of microseconds for which to delay
*/
static void tod_udelay ( unsigned long usecs ) {
unsigned long start;
unsigned long elapsed;
unsigned long threshold;
/* Delay until sufficient time has elapsed */
start = tod_ticks();
threshold = ( usecs * TOD_TICKS_PER_US );
do {
elapsed = ( tod_ticks() - start );
} while ( elapsed < threshold );
}
/**
* Probe timer
*
* @ret rc Return status code
*/
static int tod_probe ( void ) {
enum tod_state state;
int multiple;
/* Check multiple-epoch facility (for debugging only) */
multiple = facility_is_installed ( FACILITY_MULTIPLE_EPOCH );
DBGC ( colour, "TOD multiple epoch facility is%s installed\n",
( multiple ? "" : " not" ) );
/* Get clock state */
state = tod_state();
DBGC ( colour, "TOD clock is in %s state\n",
tod_state_name ( state ) );
/* Check clock is running */
if ( ! tod_is_running ( state ) )
return -ENODEV;
return 0;
}
/** Time-of-day interval timer */
struct timer tod_timer __timer ( TIMER_PREFERRED ) = {
.name = "tod",
.probe = tod_probe,
.currticks = tod_currticks,
.udelay = tod_udelay,
};
PROVIDE_TIME ( tod, time_now, tod_now );
+1
View File
@@ -15,6 +15,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/
#define ERRFILE_prno ( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 )
#define ERRFILE_tod ( ERRFILE_ARCH | ERRFILE_CORE | 0x00020000 )
/** @} */
+5 -1
View File
@@ -12,12 +12,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** Message-security-assist extension 5 facility ID */
#define FACILITY_MSA5 57
/** Multiple-epoch facility ID */
#define FACILITY_MULTIPLE_EPOCH 139
/** Installed facilities */
struct s390x_facilities {
/** Bit mask of installed facilities */
uint64_t mask[4];
};
extern int facility_is_installed ( unsigned int facility );
extern int __attribute__ (( pure ))
facility_is_installed ( unsigned int facility );
#endif /* _IPXE_FACILITY_H */
+124
View File
@@ -0,0 +1,124 @@
#ifndef _IPXE_TOD_H
#define _IPXE_TOD_H
/** @file
*
* Time-of-Day (TOD) Clock
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
/** An extended time of day (as read by "stcke") */
union tod_extended {
/** Time-of-day clock value */
struct {
/** Epoch */
uint8_t epoch;
/** Time-of-day clock within epoch */
uint8_t tod[13];
/** Programmable field */
uint8_t program[2];
} __attribute__ (( packed ));
/** Tick count */
uint64_t ticks;
};
/**
* Tick rate per microsecond
*
* The time-of-day clock is architecturally defined such that bit
* number 51 (using big-endian bit numbering) increments once per
* microsecond.
*
* When reading the "ticks" field, which effectively shifts this value
* right by eight bits to create space for the epoch value, we end up
* with a value that increments by 16 every microsecond.
*/
#define TOD_TICKS_PER_US 16
/**
* Tick rate per millisecond
*
* For short interval timing, we accept a 2.4% error for the sake of
* being able to use a shift rather than a division.
*/
#define TOD_TICKS_PER_MS ( 1024 * TOD_TICKS_PER_US )
/**
* Tick offset from the Unix Epoch
*
* The time-of-day clock epoch starts at 1900-01-01 00:00 +0000.
*/
#define TOD_EPOCH 0x7d91048bca0000UL
/**
* Get current time-of-day clock value (in ticks)
*
* @ret ticks Current time, in ticks
*/
static inline unsigned long tod_ticks ( void ) {
union tod_extended tod;
/* Read clock */
__asm__ ( "stcke %0" : "=R" ( tod ) );
return tod.ticks;
}
/** Time-of-day clock states */
enum tod_state {
/** Clock is running and set */
TOD_STATE_SET,
/** Clock is running but not set (i.e. relative timing only) */
TOD_STATE_NOT_SET,
/** Clock is in an error state */
TOD_STATE_ERROR,
/** Clock is not running */
TOD_STATE_STOPPED,
};
/**
* Get current time-of-day clock state
*
* @ret state Clock state
*/
static inline enum tod_state tod_state ( void ) {
union tod_extended tod;
enum tod_state state;
/* Read clock */
__asm__ ( "stcke %0" : "=R" ( tod ), "=@cc" ( state ) );
return state;
}
/**
* Check if clock is running
*
* @v state Clock state
* @ret is_running Clock is running
*/
static inline int tod_is_running ( enum tod_state state ) {
/* Clock is running in either the "set" or "not-set" states */
return ( state <= TOD_STATE_NOT_SET );
}
/**
* Get clock state name (for debugging)
*
* @v state Clock state
* @ret name Clock state name
*/
static inline const char * tod_state_name ( enum tod_state state ) {
static const char *states[] = {
[TOD_STATE_SET] = "set",
[TOD_STATE_NOT_SET] = "not-set",
[TOD_STATE_ERROR] = "error",
[TOD_STATE_STOPPED] = "stopped",
};
return states[state];
}
#endif /* _IPXE_TOD_H */
+3
View File
@@ -53,3 +53,6 @@ REQUIRE_OBJECT ( acpi_timer );
#ifdef TIMER_ZICNTR
REQUIRE_OBJECT ( zicntr );
#endif
#ifdef TIMER_TOD
REQUIRE_OBJECT ( tod );
#endif