s390/time: LPAR offset handling
It is possible to specify a user offset for the TOD clock, e.g. +2 hours. The TOD clock will carry this offset even if the clock is synchronized with STP. This makes the time stamps acquired with get_sync_clock() useless as another LPAR migth use a different TOD offset. Use the PTFF instrution to get the TOD epoch difference and subtract it from the TOD clock value to get a physical timestamp. As the epoch difference contains the sync check delta as well the LPAR offset value to the physical clock needs to be refreshed after each clock synchronization. Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
parent
9dc06ccf46
commit
4027789192
@ -52,6 +52,11 @@ static inline void store_clock_comparator(__u64 *time)
|
|||||||
|
|
||||||
void clock_comparator_work(void);
|
void clock_comparator_work(void);
|
||||||
|
|
||||||
|
void __init ptff_init(void);
|
||||||
|
|
||||||
|
extern unsigned char ptff_function_mask[16];
|
||||||
|
extern unsigned long lpar_offset;
|
||||||
|
|
||||||
/* Function codes for the ptff instruction. */
|
/* Function codes for the ptff instruction. */
|
||||||
#define PTFF_QAF 0x00 /* query available functions */
|
#define PTFF_QAF 0x00 /* query available functions */
|
||||||
#define PTFF_QTO 0x01 /* query tod offset */
|
#define PTFF_QTO 0x01 /* query tod offset */
|
||||||
@ -69,6 +74,14 @@ struct ptff_qto {
|
|||||||
unsigned long long tod_epoch_difference;
|
unsigned long long tod_epoch_difference;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
static inline int ptff_query(unsigned int nr)
|
||||||
|
{
|
||||||
|
unsigned char *ptr;
|
||||||
|
|
||||||
|
ptr = ptff_function_mask + (nr >> 3);
|
||||||
|
return (*ptr & (0x80 >> (nr & 7))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
static inline int ptff(void *ptff_block, size_t len, unsigned int func)
|
static inline int ptff(void *ptff_block, size_t len, unsigned int func)
|
||||||
{
|
{
|
||||||
typedef struct { char _[len]; } addrtype;
|
typedef struct { char _[len]; } addrtype;
|
||||||
@ -138,7 +151,7 @@ static inline cycles_t get_cycles(void)
|
|||||||
return (cycles_t) get_tod_clock() >> 2;
|
return (cycles_t) get_tod_clock() >> 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_sync_clock(unsigned long long *clock);
|
int get_phys_clock(unsigned long long *clock);
|
||||||
void init_cpu_timer(void);
|
void init_cpu_timer(void);
|
||||||
unsigned long long monotonic_clock(void);
|
unsigned long long monotonic_clock(void);
|
||||||
|
|
||||||
|
@ -467,6 +467,7 @@ void __init startup_init(void)
|
|||||||
ipl_save_parameters();
|
ipl_save_parameters();
|
||||||
rescue_initrd();
|
rescue_initrd();
|
||||||
clear_bss_section();
|
clear_bss_section();
|
||||||
|
ptff_init();
|
||||||
init_kernel_storage_key();
|
init_kernel_storage_key();
|
||||||
lockdep_off();
|
lockdep_off();
|
||||||
setup_lowcore_early();
|
setup_lowcore_early();
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
#include <linux/gfp.h>
|
#include <linux/gfp.h>
|
||||||
#include <linux/kprobes.h>
|
#include <linux/kprobes.h>
|
||||||
#include <asm/uaccess.h>
|
#include <asm/uaccess.h>
|
||||||
|
#include <asm/facility.h>
|
||||||
#include <asm/delay.h>
|
#include <asm/delay.h>
|
||||||
#include <asm/div64.h>
|
#include <asm/div64.h>
|
||||||
#include <asm/vdso.h>
|
#include <asm/vdso.h>
|
||||||
@ -61,6 +62,25 @@ static DEFINE_PER_CPU(struct clock_event_device, comparators);
|
|||||||
ATOMIC_NOTIFIER_HEAD(s390_epoch_delta_notifier);
|
ATOMIC_NOTIFIER_HEAD(s390_epoch_delta_notifier);
|
||||||
EXPORT_SYMBOL(s390_epoch_delta_notifier);
|
EXPORT_SYMBOL(s390_epoch_delta_notifier);
|
||||||
|
|
||||||
|
unsigned char ptff_function_mask[16];
|
||||||
|
unsigned long lpar_offset;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get time offsets with PTFF
|
||||||
|
*/
|
||||||
|
void __init ptff_init(void)
|
||||||
|
{
|
||||||
|
struct ptff_qto qto;
|
||||||
|
|
||||||
|
if (!test_facility(28))
|
||||||
|
return;
|
||||||
|
ptff(&ptff_function_mask, sizeof(ptff_function_mask), PTFF_QAF);
|
||||||
|
|
||||||
|
/* get LPAR offset */
|
||||||
|
if (ptff_query(PTFF_QTO) && ptff(&qto, sizeof(qto), PTFF_QTO) == 0)
|
||||||
|
lpar_offset = qto.tod_epoch_difference;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scheduler clock - returns current time in nanosec units.
|
* Scheduler clock - returns current time in nanosec units.
|
||||||
*/
|
*/
|
||||||
@ -337,20 +357,20 @@ static unsigned long clock_sync_flags;
|
|||||||
#define CLOCK_SYNC_STP 3
|
#define CLOCK_SYNC_STP 3
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The synchronous get_clock function. It will write the current clock
|
* The get_clock function for the physical clock. It will get the current
|
||||||
* value to the clock pointer and return 0 if the clock is in sync with
|
* TOD clock, subtract the LPAR offset and write the result to *clock.
|
||||||
* the external time source. If the clock mode is local it will return
|
* The function returns 0 if the clock is in sync with the external time
|
||||||
* -EOPNOTSUPP and -EAGAIN if the clock is not in sync with the external
|
* source. If the clock mode is local it will return -EOPNOTSUPP and
|
||||||
* reference.
|
* -EAGAIN if the clock is not in sync with the external reference.
|
||||||
*/
|
*/
|
||||||
int get_sync_clock(unsigned long long *clock)
|
int get_phys_clock(unsigned long long *clock)
|
||||||
{
|
{
|
||||||
atomic_t *sw_ptr;
|
atomic_t *sw_ptr;
|
||||||
unsigned int sw0, sw1;
|
unsigned int sw0, sw1;
|
||||||
|
|
||||||
sw_ptr = &get_cpu_var(clock_sync_word);
|
sw_ptr = &get_cpu_var(clock_sync_word);
|
||||||
sw0 = atomic_read(sw_ptr);
|
sw0 = atomic_read(sw_ptr);
|
||||||
*clock = get_tod_clock();
|
*clock = get_tod_clock() - lpar_offset;
|
||||||
sw1 = atomic_read(sw_ptr);
|
sw1 = atomic_read(sw_ptr);
|
||||||
put_cpu_var(clock_sync_word);
|
put_cpu_var(clock_sync_word);
|
||||||
if (sw0 == sw1 && (sw0 & 0x80000000U))
|
if (sw0 == sw1 && (sw0 & 0x80000000U))
|
||||||
@ -364,7 +384,7 @@ int get_sync_clock(unsigned long long *clock)
|
|||||||
return -EACCES;
|
return -EACCES;
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(get_sync_clock);
|
EXPORT_SYMBOL(get_phys_clock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make get_sync_clock return -EAGAIN.
|
* Make get_sync_clock return -EAGAIN.
|
||||||
@ -758,6 +778,7 @@ static int etr_sync_clock(void *data)
|
|||||||
unsigned long long clock, old_clock, clock_delta, delay, delta;
|
unsigned long long clock, old_clock, clock_delta, delay, delta;
|
||||||
struct clock_sync_data *etr_sync;
|
struct clock_sync_data *etr_sync;
|
||||||
struct etr_aib *sync_port, *aib;
|
struct etr_aib *sync_port, *aib;
|
||||||
|
struct ptff_qto qto;
|
||||||
int port;
|
int port;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
@ -804,6 +825,10 @@ static int etr_sync_clock(void *data)
|
|||||||
etr_sync->in_sync = -EAGAIN;
|
etr_sync->in_sync = -EAGAIN;
|
||||||
rc = -EAGAIN;
|
rc = -EAGAIN;
|
||||||
} else {
|
} else {
|
||||||
|
if (ptff_query(PTFF_QTO) &&
|
||||||
|
ptff(&qto, sizeof(qto), PTFF_QTO) == 0)
|
||||||
|
/* Update LPAR offset */
|
||||||
|
lpar_offset = qto.tod_epoch_difference;
|
||||||
etr_sync->in_sync = 1;
|
etr_sync->in_sync = 1;
|
||||||
rc = 0;
|
rc = 0;
|
||||||
}
|
}
|
||||||
@ -1533,6 +1558,7 @@ static int stp_sync_clock(void *data)
|
|||||||
static int first;
|
static int first;
|
||||||
unsigned long long old_clock, delta, new_clock, clock_delta;
|
unsigned long long old_clock, delta, new_clock, clock_delta;
|
||||||
struct clock_sync_data *stp_sync;
|
struct clock_sync_data *stp_sync;
|
||||||
|
struct ptff_qto qto;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
stp_sync = data;
|
stp_sync = data;
|
||||||
@ -1558,6 +1584,10 @@ static int stp_sync_clock(void *data)
|
|||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
new_clock = old_clock + clock_delta;
|
new_clock = old_clock + clock_delta;
|
||||||
delta = adjust_time(old_clock, new_clock, 0);
|
delta = adjust_time(old_clock, new_clock, 0);
|
||||||
|
if (ptff_query(PTFF_QTO) &&
|
||||||
|
ptff(&qto, sizeof(qto), PTFF_QTO) == 0)
|
||||||
|
/* Update LPAR offset */
|
||||||
|
lpar_offset = qto.tod_epoch_difference;
|
||||||
atomic_notifier_call_chain(&s390_epoch_delta_notifier,
|
atomic_notifier_call_chain(&s390_epoch_delta_notifier,
|
||||||
0, &clock_delta);
|
0, &clock_delta);
|
||||||
fixup_clock_comparator(delta);
|
fixup_clock_comparator(delta);
|
||||||
|
@ -228,7 +228,7 @@ check_XRC (struct ccw1 *de_ccw,
|
|||||||
data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */
|
data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */
|
||||||
data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */
|
data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */
|
||||||
|
|
||||||
rc = get_sync_clock(&data->ep_sys_time);
|
rc = get_phys_clock(&data->ep_sys_time);
|
||||||
/* Ignore return code if sync clock is switched off. */
|
/* Ignore return code if sync clock is switched off. */
|
||||||
if (rc == -EOPNOTSUPP || rc == -EACCES)
|
if (rc == -EOPNOTSUPP || rc == -EACCES)
|
||||||
rc = 0;
|
rc = 0;
|
||||||
@ -339,7 +339,7 @@ static int check_XRC_on_prefix(struct PFX_eckd_data *pfxdata,
|
|||||||
pfxdata->define_extent.ga_extended |= 0x02; /* 'Extended Parameter' */
|
pfxdata->define_extent.ga_extended |= 0x02; /* 'Extended Parameter' */
|
||||||
pfxdata->validity.time_stamp = 1; /* 'Time Stamp Valid' */
|
pfxdata->validity.time_stamp = 1; /* 'Time Stamp Valid' */
|
||||||
|
|
||||||
rc = get_sync_clock(&pfxdata->define_extent.ep_sys_time);
|
rc = get_phys_clock(&pfxdata->define_extent.ep_sys_time);
|
||||||
/* Ignore return code if sync clock is switched off. */
|
/* Ignore return code if sync clock is switched off. */
|
||||||
if (rc == -EOPNOTSUPP || rc == -EACCES)
|
if (rc == -EOPNOTSUPP || rc == -EACCES)
|
||||||
rc = 0;
|
rc = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user