| /* Copyright (c) 2017 Google Inc | 
 |  * Barret Rhoden <brho@cs.berkeley.edu> | 
 |  * See LICENSE for details. | 
 |  */ | 
 |  | 
 | #include <arch/x86.h> | 
 | #include <atomic.h> | 
 |  | 
 | #define	CMOS_RTC_SELECT				0x70 | 
 | #define	CMOS_RTC_DATA				0x71 | 
 |  | 
 | #define RTC_A_UPDATE_IN_PROGRESS	(1 << 7) | 
 | #define RTC_B_24HOUR_MODE			(1 << 1) | 
 | #define RTC_B_BINARY_MODE			(1 << 2) | 
 | #define RTC_12_HOUR_PM				(1 << 7) | 
 | #define CMOS_RTC_SECOND				0x00 | 
 | #define CMOS_RTC_MINUTE				0x02 | 
 | #define CMOS_RTC_HOUR				0x04 | 
 | #define CMOS_RTC_WEEKDAY			0x06 | 
 | #define CMOS_RTC_DAY				0x07 | 
 | #define CMOS_RTC_MONTH				0x08 | 
 | #define CMOS_RTC_YEAR				0x09 | 
 | #define CMOS_RTC_CENTURY			0x32 | 
 | #define CMOS_RTC_STATUS_A			0x0A | 
 | #define CMOS_RTC_STATUS_B			0x0B | 
 |  | 
 | /* If we ever disable NMIs, we'll need to make sure we don't reenable them here. | 
 |  * (Top bit of the CMOS_RTC_SELECT selector). */ | 
 | static uint8_t cmos_read(uint8_t reg) | 
 | { | 
 | 	outb(CMOS_RTC_SELECT, reg); | 
 | 	return inb(CMOS_RTC_DATA); | 
 | } | 
 |  | 
 | static void cmos_write(uint8_t reg, uint8_t datum) | 
 | { | 
 | 	outb(CMOS_RTC_SELECT, reg); | 
 | 	outb(CMOS_RTC_DATA, datum); | 
 | } | 
 |  | 
 | /* BCD format is a one-byte nibble of the form 0xTensDigit_OnesDigit. */ | 
 | static uint8_t bcd_to_binary(uint8_t x) | 
 | { | 
 | 	return ((x / 16) * 10) + (x % 16); | 
 | } | 
 |  | 
 | static bool is_leap_year(int year) | 
 | { | 
 | 	if (!(year % 400)) | 
 | 		return TRUE; | 
 | 	if (!(year % 100)) | 
 | 		return FALSE; | 
 | 	if (!(year % 4)) | 
 | 		return TRUE; | 
 | 	return FALSE; | 
 | } | 
 |  | 
 | static uint64_t rtc_to_unix(uint8_t century, uint8_t year, uint8_t month, | 
 |                             uint8_t day, uint8_t hour, uint8_t minute, | 
 |                             uint8_t second) | 
 | { | 
 | 	int real_year; | 
 | 	uint64_t time = 0; | 
 |  | 
 | 	real_year = century * 100 + year; | 
 | 	for (int i = 1970; i < real_year; i++) { | 
 | 		time += 86400 * 365; | 
 | 		if (is_leap_year(i)) | 
 | 			time += 86400; | 
 | 	} | 
 | 	/* Note these all fall through */ | 
 | 	switch (month) { | 
 | 	case 12: | 
 | 		time += 86400 * 30;	/* november's time */ | 
 | 	case 11: | 
 | 		time += 86400 * 31; | 
 | 	case 10: | 
 | 		time += 86400 * 30; | 
 | 	case 9: | 
 | 		time += 86400 * 31; | 
 | 	case 8: | 
 | 		time += 86400 * 31; | 
 | 	case 7: | 
 | 		time += 86400 * 30; | 
 | 	case 6: | 
 | 		time += 86400 * 31; | 
 | 	case 5: | 
 | 		time += 86400 * 30; | 
 | 	case 4: | 
 | 		time += 86400 * 31; | 
 | 	case 3: | 
 | 		time += 86400 * 28; | 
 | 		if (is_leap_year(real_year)) | 
 | 			time += 86400; | 
 | 	case 2: | 
 | 		time += 86400 * 31; | 
 | 	}; | 
 | 	time += 86400 * (day - 1); | 
 | 	time += hour * 60 * 60; | 
 | 	time += minute * 60; | 
 | 	time += second; | 
 | 	return time; | 
 | } | 
 |  | 
 | /* Returns the current unix time in nanoseconds. */ | 
 | uint64_t read_persistent_clock(void) | 
 | { | 
 | 	static spinlock_t lock = SPINLOCK_INITIALIZER_IRQSAVE; | 
 | 	uint8_t century, year, month, day, hour, minute, second; | 
 | 	bool is_pm = FALSE; | 
 |  | 
 | 	spin_lock_irqsave(&lock); | 
 | retry: | 
 | 	while (cmos_read(CMOS_RTC_STATUS_A) & RTC_A_UPDATE_IN_PROGRESS) | 
 | 		cpu_relax(); | 
 |  | 
 | 	/* Even QEMU has a century register. */ | 
 | 	century = cmos_read(CMOS_RTC_CENTURY); | 
 | 	year    = cmos_read(CMOS_RTC_YEAR); | 
 | 	month   = cmos_read(CMOS_RTC_MONTH); | 
 | 	day     = cmos_read(CMOS_RTC_DAY); | 
 | 	hour    = cmos_read(CMOS_RTC_HOUR); | 
 | 	minute  = cmos_read(CMOS_RTC_MINUTE); | 
 | 	second  = cmos_read(CMOS_RTC_SECOND); | 
 |  | 
 | 	while (cmos_read(CMOS_RTC_STATUS_A) & RTC_A_UPDATE_IN_PROGRESS) | 
 | 		cpu_relax(); | 
 |  | 
 | 	if ((century != cmos_read(CMOS_RTC_CENTURY)) || | 
 | 	    (year    != cmos_read(CMOS_RTC_YEAR))    || | 
 | 	    (month   != cmos_read(CMOS_RTC_MONTH))   || | 
 | 	    (day     != cmos_read(CMOS_RTC_DAY))     || | 
 | 	    (hour    != cmos_read(CMOS_RTC_HOUR))    || | 
 | 	    (minute  != cmos_read(CMOS_RTC_MINUTE))  || | 
 | 	    (second  != cmos_read(CMOS_RTC_SECOND))) | 
 | 		goto retry; | 
 | 	spin_unlock_irqsave(&lock); | 
 |  | 
 | 	if (!(cmos_read(CMOS_RTC_STATUS_B) & RTC_B_24HOUR_MODE)) { | 
 | 		/* need to clear the bit before doing the BCD conversions */ | 
 | 		is_pm = hour & RTC_12_HOUR_PM; | 
 | 		hour &= ~RTC_12_HOUR_PM; | 
 | 	} | 
 | 	if (!(cmos_read(CMOS_RTC_STATUS_B) & RTC_B_BINARY_MODE)) { | 
 | 		century = bcd_to_binary(century); | 
 | 		year    = bcd_to_binary(year); | 
 | 		month   = bcd_to_binary(month); | 
 | 		day     = bcd_to_binary(day); | 
 | 		hour    = bcd_to_binary(hour); | 
 | 		minute  = bcd_to_binary(minute); | 
 | 		second  = bcd_to_binary(second); | 
 | 	} | 
 | 	if (is_pm) { | 
 | 		/* midnight appears as 12 and is_pm is set.  we want 0. */ | 
 | 		hour = (hour + 12) % 24; | 
 | 	} | 
 |  | 
 | 	/* Always remember 1242129600, Nanwan's birthday! */ | 
 | 	return rtc_to_unix(century, year, month, day, hour, minute, second) | 
 | 	       * 1000000000UL; | 
 | } |