📄 time.c
字号:
atomic_inc(sw_ptr);}/* * Make get_sync_clock return 0 again. * Needs to be called from a context disabled for preemption. */static void etr_enable_sync_clock(void){ atomic_t *sw_ptr = &__get_cpu_var(etr_sync_word); atomic_set_mask(0x80000000, sw_ptr);}/* * Reset ETR attachment. */static void etr_reset(void){ etr_eacr = (struct etr_eacr) { .e0 = 0, .e1 = 0, ._pad0 = 4, .dp = 0, .p0 = 0, .p1 = 0, ._pad1 = 0, .ea = 0, .es = 0, .sl = 0 }; if (etr_setr(&etr_eacr) == 0) etr_tolec = get_clock(); else { set_bit(ETR_FLAG_ENOSYS, &etr_flags); if (etr_port0_online || etr_port1_online) { printk(KERN_WARNING "Running on non ETR capable " "machine, only local mode available.\n"); etr_port0_online = etr_port1_online = 0; } }}static int __init etr_init(void){ struct etr_aib aib; if (test_bit(ETR_FLAG_ENOSYS, &etr_flags)) return 0; /* Check if this machine has the steai instruction. */ if (etr_steai(&aib, ETR_STEAI_STEPPING_PORT) == 0) set_bit(ETR_FLAG_STEAI, &etr_flags); setup_timer(&etr_timer, etr_timeout, 0UL); if (!etr_port0_online && !etr_port1_online) set_bit(ETR_FLAG_EACCES, &etr_flags); if (etr_port0_online) { set_bit(ETR_EVENT_PORT0_CHANGE, &etr_events); schedule_work(&etr_work); } if (etr_port1_online) { set_bit(ETR_EVENT_PORT1_CHANGE, &etr_events); schedule_work(&etr_work); } return 0;}arch_initcall(etr_init);/* * Two sorts of ETR machine checks. The architecture reads: * "When a machine-check niterruption occurs and if a switch-to-local or * ETR-sync-check interrupt request is pending but disabled, this pending * disabled interruption request is indicated and is cleared". * Which means that we can get etr_switch_to_local events from the machine * check handler although the interruption condition is disabled. Lovely.. *//* * Switch to local machine check. This is called when the last usable * ETR port goes inactive. After switch to local the clock is not in sync. */void etr_switch_to_local(void){ if (!etr_eacr.sl) return; etr_disable_sync_clock(NULL); set_bit(ETR_EVENT_SWITCH_LOCAL, &etr_events); schedule_work(&etr_work);}/* * ETR sync check machine check. This is called when the ETR OTE and the * local clock OTE are farther apart than the ETR sync check tolerance. * After a ETR sync check the clock is not in sync. The machine check * is broadcasted to all cpus at the same time. */void etr_sync_check(void){ if (!etr_eacr.es) return; etr_disable_sync_clock(NULL); set_bit(ETR_EVENT_SYNC_CHECK, &etr_events); schedule_work(&etr_work);}/* * ETR external interrupt. There are two causes: * 1) port state change, check the usability of the port * 2) port alert, one of the ETR-data-validity bits (v1-v2 bits of the * sldr-status word) or ETR-data word 1 (edf1) or ETR-data word 3 (edf3) * or ETR-data word 4 (edf4) has changed. */static void etr_ext_handler(__u16 code){ struct etr_interruption_parameter *intparm = (struct etr_interruption_parameter *) &S390_lowcore.ext_params; if (intparm->pc0) /* ETR port 0 state change. */ set_bit(ETR_EVENT_PORT0_CHANGE, &etr_events); if (intparm->pc1) /* ETR port 1 state change. */ set_bit(ETR_EVENT_PORT1_CHANGE, &etr_events); if (intparm->eai) /* * ETR port alert on either port 0, 1 or both. * Both ports are not up-to-date now. */ set_bit(ETR_EVENT_PORT_ALERT, &etr_events); schedule_work(&etr_work);}static void etr_timeout(unsigned long dummy){ set_bit(ETR_EVENT_UPDATE, &etr_events); schedule_work(&etr_work);}/* * Check if the etr mode is pss. */static inline int etr_mode_is_pps(struct etr_eacr eacr){ return eacr.es && !eacr.sl;}/* * Check if the etr mode is etr. */static inline int etr_mode_is_etr(struct etr_eacr eacr){ return eacr.es && eacr.sl;}/* * Check if the port can be used for TOD synchronization. * For PPS mode the port has to receive OTEs. For ETR mode * the port has to receive OTEs, the ETR stepping bit has to * be zero and the validity bits for data frame 1, 2, and 3 * have to be 1. */static int etr_port_valid(struct etr_aib *aib, int port){ unsigned int psc; /* Check that this port is receiving OTEs. */ if (aib->tsp == 0) return 0; psc = port ? aib->esw.psc1 : aib->esw.psc0; if (psc == etr_lpsc_pps_mode) return 1; if (psc == etr_lpsc_operational_step) return !aib->esw.y && aib->slsw.v1 && aib->slsw.v2 && aib->slsw.v3; return 0;}/* * Check if two ports are on the same network. */static int etr_compare_network(struct etr_aib *aib1, struct etr_aib *aib2){ // FIXME: any other fields we have to compare? return aib1->edf1.net_id == aib2->edf1.net_id;}/* * Wrapper for etr_stei that converts physical port states * to logical port states to be consistent with the output * of stetr (see etr_psc vs. etr_lpsc). */static void etr_steai_cv(struct etr_aib *aib, unsigned int func){ BUG_ON(etr_steai(aib, func) != 0); /* Convert port state to logical port state. */ if (aib->esw.psc0 == 1) aib->esw.psc0 = 2; else if (aib->esw.psc0 == 0 && aib->esw.p == 0) aib->esw.psc0 = 1; if (aib->esw.psc1 == 1) aib->esw.psc1 = 2; else if (aib->esw.psc1 == 0 && aib->esw.p == 1) aib->esw.psc1 = 1;}/* * Check if the aib a2 is still connected to the same attachment as * aib a1, the etv values differ by one and a2 is valid. */static int etr_aib_follows(struct etr_aib *a1, struct etr_aib *a2, int p){ int state_a1, state_a2; /* Paranoia check: e0/e1 should better be the same. */ if (a1->esw.eacr.e0 != a2->esw.eacr.e0 || a1->esw.eacr.e1 != a2->esw.eacr.e1) return 0; /* Still connected to the same etr ? */ state_a1 = p ? a1->esw.psc1 : a1->esw.psc0; state_a2 = p ? a2->esw.psc1 : a2->esw.psc0; if (state_a1 == etr_lpsc_operational_step) { if (state_a2 != etr_lpsc_operational_step || a1->edf1.net_id != a2->edf1.net_id || a1->edf1.etr_id != a2->edf1.etr_id || a1->edf1.etr_pn != a2->edf1.etr_pn) return 0; } else if (state_a2 != etr_lpsc_pps_mode) return 0; /* The ETV value of a2 needs to be ETV of a1 + 1. */ if (a1->edf2.etv + 1 != a2->edf2.etv) return 0; if (!etr_port_valid(a2, p)) return 0; return 1;}/* * The time is "clock". xtime is what we think the time is. * Adjust the value by a multiple of jiffies and add the delta to ntp. * "delay" is an approximation how long the synchronization took. If * the time correction is positive, then "delay" is subtracted from * the time difference and only the remaining part is passed to ntp. */static void etr_adjust_time(unsigned long long clock, unsigned long long delay){ unsigned long long delta, ticks; struct timex adjust; /* * We don't have to take the xtime lock because the cpu * executing etr_adjust_time is running disabled in * tasklet context and all other cpus are looping in * etr_sync_cpu_start. */ if (clock > xtime_cc) { /* It is later than we thought. */ delta = ticks = clock - xtime_cc; delta = ticks = (delta < delay) ? 0 : delta - delay; delta -= do_div(ticks, CLK_TICKS_PER_JIFFY); init_timer_cc = init_timer_cc + delta; jiffies_timer_cc = jiffies_timer_cc + delta; xtime_cc = xtime_cc + delta; adjust.offset = ticks * (1000000 / HZ); } else { /* It is earlier than we thought. */ delta = ticks = xtime_cc - clock; delta -= do_div(ticks, CLK_TICKS_PER_JIFFY); init_timer_cc = init_timer_cc - delta; jiffies_timer_cc = jiffies_timer_cc - delta; xtime_cc = xtime_cc - delta; adjust.offset = -ticks * (1000000 / HZ); } if (adjust.offset != 0) { printk(KERN_NOTICE "etr: time adjusted by %li micro-seconds\n", adjust.offset); adjust.modes = ADJ_OFFSET_SINGLESHOT; do_adjtimex(&adjust); }}#ifdef CONFIG_SMPstatic void etr_sync_cpu_start(void *dummy){ int *in_sync = dummy; etr_enable_sync_clock(); /* * This looks like a busy wait loop but it isn't. etr_sync_cpus * is called on all other cpus while the TOD clocks is stopped. * __udelay will stop the cpu on an enabled wait psw until the * TOD is running again. */ while (*in_sync == 0) { __udelay(1); /* * A different cpu changes *in_sync. Therefore use * barrier() to force memory access. */ barrier(); } if (*in_sync != 1) /* Didn't work. Clear per-cpu in sync bit again. */ etr_disable_sync_clock(NULL); /* * This round of TOD syncing is done. Set the clock comparator * to the next tick and let the processor continue. */ setup_jiffy_timer();}static void etr_sync_cpu_end(void *dummy){}#endif /* CONFIG_SMP *//* * Sync the TOD clock using the port refered to by aibp. This port * has to be enabled and the other port has to be disabled. The * last eacr update has to be more than 1.6 seconds in the past. */static int etr_sync_clock(struct etr_aib *aib, int port){ struct etr_aib *sync_port; unsigned long long clock, delay; int in_sync, follows; int rc; /* Check if the current aib is adjacent to the sync port aib. */ sync_port = (port == 0) ? &etr_port0 : &etr_port1; follows = etr_aib_follows(sync_port, aib, port); memcpy(sync_port, aib, sizeof(*aib)); if (!follows) return -EAGAIN; /* * Catch all other cpus and make them wait until we have * successfully synced the clock. smp_call_function will * return after all other cpus are in etr_sync_cpu_start. */ in_sync = 0; preempt_disable(); smp_call_function(etr_sync_cpu_start,&in_sync,0,0); local_irq_disable(); etr_enable_sync_clock(); /* Set clock to next OTE. */ __ctl_set_bit(14, 21); __ctl_set_bit(0, 29); clock = ((unsigned long long) (aib->edf2.etv + 1)) << 32; if (set_clock(clock) == 0) { __udelay(1); /* Wait for the clock to start. */ __ctl_clear_bit(0, 29); __ctl_clear_bit(14, 21); etr_stetr(aib); /* Adjust Linux timing variables. */ delay = (unsigned long long) (aib->edf2.etv - sync_port->edf2.etv) << 32; etr_adjust_time(clock, delay); setup_jiffy_timer(); /* Verify that the clock is properly set. */ if (!etr_aib_follows(sync_port, aib, port)) { /* Didn't work. */ etr_disable_sync_clock(NULL); in_sync = -EAGAIN; rc = -EAGAIN; } else { in_sync = 1; rc = 0; } } else { /* Could not set the clock ?!? */ __ctl_clear_bit(0, 29); __ctl_clear_bit(14, 21); etr_disable_sync_clock(NULL); in_sync = -EAGAIN; rc = -EAGAIN; } local_irq_enable(); smp_call_function(etr_sync_cpu_end,NULL,0,0); preempt_enable(); return rc;}/* * Handle the immediate effects of the different events. * The port change event is used for online/offline changes. */static struct etr_eacr etr_handle_events(struct etr_eacr eacr){ if (test_and_clear_bit(ETR_EVENT_SYNC_CHECK, &etr_events)) eacr.es = 0; if (test_and_clear_bit(ETR_EVENT_SWITCH_LOCAL, &etr_events)) eacr.es = eacr.sl = 0; if (test_and_clear_bit(ETR_EVENT_PORT_ALERT, &etr_events)) etr_port0_uptodate = etr_port1_uptodate = 0; if (test_and_clear_bit(ETR_EVENT_PORT0_CHANGE, &etr_events)) { if (eacr.e0) /* * Port change of an enabled port. We have to * assume that this can have caused an stepping * port switch. */ etr_tolec = get_clock(); eacr.p0 = etr_port0_online; if (!eacr.p0) eacr.e0 = 0; etr_port0_uptodate = 0; } if (test_and_clear_bit(ETR_EVENT_PORT1_CHANGE, &etr_events)) { if (eacr.e1) /* * Port change of an enabled port. We have to * assume that this can have caused an stepping * port switch. */ etr_tolec = get_clock(); eacr.p1 = etr_port1_online; if (!eacr.p1) eacr.e1 = 0; etr_port1_uptodate = 0; } clear_bit(ETR_EVENT_UPDATE, &etr_events); return eacr;}/* * Set up a timer that expires after the etr_tolec + 1.6 seconds if * one of the ports needs an update. */static void etr_set_tolec_timeout(unsigned long long now){ unsigned long micros; if ((!etr_eacr.p0 || etr_port0_uptodate) && (!etr_eacr.p1 || etr_port1_uptodate)) return; micros = (now > etr_tolec) ? ((now - etr_tolec) >> 12) : 0; micros = (micros > 1600000) ? 0 : 1600000 - micros; mod_timer(&etr_timer, jiffies + (micros * HZ) / 1000000 + 1);}/* * Set up a time that expires after 1/2 second. */static void etr_set_sync_timeout(void){ mod_timer(&etr_timer, jiffies + HZ/2);}/* * Update the aib information for one or both ports. */static struct etr_eacr etr_handle_update(struct etr_aib *aib, struct etr_eacr eacr){ /* With both ports disabled the aib information is useless. */ if (!eacr.e0 && !eacr.e1) return eacr; /* Update port0 or port1 with aib stored in etr_work_fn. */ if (aib->esw.q == 0) { /* Information for port 0 stored. */ if (eacr.p0 && !etr_port0_uptodate) { etr_port0 = *aib; if (etr_port0_online) etr_port0_uptodate = 1; } } else { /* Information for port 1 stored. */ if (eacr.p1 && !etr_port1_uptodate) { etr_port1 = *aib; if (etr_port0_online) etr_port1_uptodate = 1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -