1 /* readclock - read the real time clock		Authors: T. Holm & E. Froese
2  *
3  * Changed to be user-space driver.
4  */
5 
6 /************************************************************************/
7 /*									*/
8 /*   readclock.c							*/
9 /*									*/
10 /*		Read the clock value from the 64 byte CMOS RAM		*/
11 /*		area, then set system time.				*/
12 /*									*/
13 /*		If the machine ID byte is 0xFC or 0xF8, the device	*/
14 /*		/dev/mem exists and can be opened for reading,		*/
15 /*		and no errors in the CMOS RAM are reported by the	*/
16 /*		RTC, then the time is read from the clock RAM		*/
17 /*		area maintained by the RTC.				*/
18 /*									*/
19 /*		The clock RAM values are decoded and fed to mktime	*/
20 /*		to make a time_t value, then stime(2) is called.	*/
21 /*									*/
22 /*		This fails if:						*/
23 /*									*/
24 /*		If the machine ID does not match 0xFC or 0xF8 (no	*/
25 /*		error message.)						*/
26 /*									*/
27 /*		If the machine ID is 0xFC or 0xF8 and /dev/mem		*/
28 /*		is missing, or cannot be accessed.			*/
29 /*									*/
30 /*		If the RTC reports errors in the CMOS RAM.		*/
31 /*									*/
32 /************************************************************************/
33 /*    origination          1987-Dec-29              efth                */
34 /*    robustness	   1990-Oct-06		    C. Sylvain		*/
35 /* incorp. B. Evans ideas  1991-Jul-06		    C. Sylvain		*/
36 /*    set time & calibrate 1992-Dec-17		    Kees J. Bot		*/
37 /*    clock timezone	   1993-Oct-10		    Kees J. Bot		*/
38 /*    set CMOS clock	   1994-Jun-12		    Kees J. Bot		*/
39 /************************************************************************/
40 
41 #include <sys/types.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <time.h>
46 #include <errno.h>
47 #include <minix/type.h>
48 #include <minix/const.h>
49 #include <minix/syslib.h>
50 #include <minix/sysutil.h>
51 #include <minix/com.h>
52 #include <minix/log.h>
53 #include <machine/cmos.h>
54 #include <sys/svrctl.h>
55 
56 #include "readclock.h"
57 
58 #define MACH_ID_ADDR	0xFFFFE	/* BIOS Machine ID at FFFF:000E */
59 
60 #define PC_AT		   0xFC	/* Machine ID byte for PC/AT,
61 				 * PC/XT286, and PS/2 Models 50, 60 */
62 #define PS_386		   0xF8	/* Machine ID byte for PS/2 Model 80 */
63 
64 /* Manufacturers usually use the ID value of the IBM model they emulate.
65  * However some manufacturers, notably HP and COMPAQ, have had different
66  * ideas in the past.
67  *
68  * Machine ID byte information source:
69  *	_The Programmer's PC Sourcebook_ by Thom Hogan,
70  *	published by Microsoft Press
71  */
72 
73 /* used for logging */
74 static struct log log = {
75 	.name = "cmos_clock",
76 	.log_level = LEVEL_INFO,
77 	.log_func = default_log
78 };
79 
80 static int read_register(int reg_addr);
81 static int write_register(int reg_addr, int value);
82 
83 static int arch_init(void);
84 static int arch_get_time(struct tm *t, int flags);
85 static int arch_set_time(struct tm *t, int flags);
86 static int arch_pwr_off(void);
87 static void arch_exit(void);
88 
89 int
90 arch_setup(struct rtc *r)
91 {
92 	r->init = arch_init;
93 	r->get_time = arch_get_time;
94 	r->set_time = arch_set_time;
95 	r->pwr_off = arch_pwr_off;
96 	r->exit = arch_exit;
97 
98 	return OK;
99 }
100 
101 static int
102 arch_init(void)
103 {
104 	int s;
105 	unsigned char mach_id, cmos_state;
106 
107 	if ((s = sys_readbios(MACH_ID_ADDR, &mach_id, sizeof(mach_id))) != OK) {
108 		log_warn(&log, "sys_readbios failed: %d.\n", s);
109 
110 		return -1;
111 	}
112 
113 	if (mach_id != PS_386 && mach_id != PC_AT) {
114 		log_warn(&log, "Machine ID unknown.");
115 		log_warn(&log, "Machine ID byte = %02x\n", mach_id);
116 
117 		return -1;
118 	}
119 
120 	cmos_state = read_register(CMOS_STATUS);
121 
122 	if (cmos_state & (CS_LOST_POWER | CS_BAD_CHKSUM | CS_BAD_TIME)) {
123 		log_warn(&log, "CMOS RAM error(s) found...");
124 		log_warn(&log, "CMOS state = 0x%02x\n", cmos_state);
125 
126 		if (cmos_state & CS_LOST_POWER)
127 			log_warn(&log,
128 			    "RTC lost power. Reset CMOS RAM with SETUP.");
129 		if (cmos_state & CS_BAD_CHKSUM)
130 			log_warn(&log, "CMOS RAM checksum is bad. Run SETUP.");
131 		if (cmos_state & CS_BAD_TIME)
132 			log_warn(&log,
133 			    "Time invalid in CMOS RAM. Reset clock.");
134 		return -1;
135 	}
136 
137 	return OK;
138 }
139 
140 /***********************************************************************/
141 /*                                                                     */
142 /*    arch_get_time( time )                                            */
143 /*                                                                     */
144 /*    Update the structure pointed to by time with the current time    */
145 /*    as read from CMOS RAM of the RTC.				       */
146 /*    If necessary, the time is converted into a binary format before  */
147 /*    being stored in the structure.                                   */
148 /*                                                                     */
149 /***********************************************************************/
150 
151 static int
152 arch_get_time(struct tm *t, int flags)
153 {
154 	int osec, n;
155 
156 	do {
157 		osec = -1;
158 		n = 0;
159 		do {
160 			/* Clock update in progress? */
161 			if (read_register(RTC_REG_A) & RTC_A_UIP)
162 				continue;
163 
164 			t->tm_sec = read_register(RTC_SEC);
165 			if (t->tm_sec != osec) {
166 				/* Seconds changed.  First from -1, then because the
167 				 * clock ticked, which is what we're waiting for to
168 				 * get a precise reading.
169 				 */
170 				osec = t->tm_sec;
171 				n++;
172 			}
173 		} while (n < 2);
174 
175 		/* Read the other registers. */
176 		t->tm_min = read_register(RTC_MIN);
177 		t->tm_hour = read_register(RTC_HOUR);
178 		t->tm_mday = read_register(RTC_MDAY);
179 		t->tm_mon = read_register(RTC_MONTH);
180 		t->tm_year = read_register(RTC_YEAR);
181 
182 		/* Time stable? */
183 	} while (read_register(RTC_SEC) != t->tm_sec
184 	    || read_register(RTC_MIN) != t->tm_min
185 	    || read_register(RTC_HOUR) != t->tm_hour
186 	    || read_register(RTC_MDAY) != t->tm_mday
187 	    || read_register(RTC_MONTH) != t->tm_mon
188 	    || read_register(RTC_YEAR) != t->tm_year);
189 
190 	if ((read_register(RTC_REG_B) & RTC_B_DM_BCD) == 0) {
191 		/* Convert BCD to binary (default RTC mode). */
192 		t->tm_year = bcd_to_dec(t->tm_year);
193 		t->tm_mon = bcd_to_dec(t->tm_mon);
194 		t->tm_mday = bcd_to_dec(t->tm_mday);
195 		t->tm_hour = bcd_to_dec(t->tm_hour);
196 		t->tm_min = bcd_to_dec(t->tm_min);
197 		t->tm_sec = bcd_to_dec(t->tm_sec);
198 	}
199 	t->tm_mon--;		/* Counts from 0. */
200 
201 	/* Correct the year, good until 2080. */
202 	if (t->tm_year < 80)
203 		t->tm_year += 100;
204 
205 	if ((flags & RTCDEV_Y2KBUG) == RTCDEV_Y2KBUG) {
206 		/* Clock with Y2K bug, interpret 1980 as 2000, good until 2020. */
207 		if (t->tm_year < 100)
208 			t->tm_year += 20;
209 	}
210 
211 	return OK;
212 }
213 
214 static int
215 read_register(int reg_addr)
216 {
217 	u32_t r;
218 
219 	if (sys_outb(RTC_INDEX, reg_addr) != OK) {
220 		log_warn(&log, "outb failed of %x\n", RTC_INDEX);
221 		return -1;
222 	}
223 	if (sys_inb(RTC_IO, &r) != OK) {
224 		log_warn(&log, "inb failed of %x (index %x) failed\n", RTC_IO,
225 		    reg_addr);
226 		return -1;
227 	}
228 	return r;
229 }
230 
231 /***********************************************************************/
232 /*                                                                     */
233 /*    arch_set_time( time )                                            */
234 /*                                                                     */
235 /*    Set the CMOS RTC to the time found in the structure.             */
236 /*                                                                     */
237 /***********************************************************************/
238 
239 static int
240 arch_set_time(struct tm *t, int flags)
241 {
242 	int regA, regB;
243 
244 	if ((flags & RTCDEV_CMOSREG) == RTCDEV_CMOSREG) {
245 		/* Set A and B registers to their proper values according to the AT
246 		 * reference manual.  (For if it gets messed up, but the BIOS doesn't
247 		 * repair it.)
248 		 */
249 		write_register(RTC_REG_A, RTC_A_DV_OK | RTC_A_RS_DEF);
250 		write_register(RTC_REG_B, RTC_B_24);
251 	}
252 
253 	/* Inhibit updates. */
254 	regB = read_register(RTC_REG_B);
255 	write_register(RTC_REG_B, regB | RTC_B_SET);
256 
257 	t->tm_mon++;		/* Counts from 1. */
258 
259 	if ((flags & RTCDEV_Y2KBUG) == RTCDEV_Y2KBUG) {
260 		/* Set the clock back 20 years to avoid Y2K bug, good until 2020. */
261 		if (t->tm_year >= 100)
262 			t->tm_year -= 20;
263 	}
264 
265 	if ((regB & 0x04) == 0) {
266 		/* Convert binary to BCD (default RTC mode) */
267 		t->tm_year = dec_to_bcd(t->tm_year % 100);
268 		t->tm_mon = dec_to_bcd(t->tm_mon);
269 		t->tm_mday = dec_to_bcd(t->tm_mday);
270 		t->tm_hour = dec_to_bcd(t->tm_hour);
271 		t->tm_min = dec_to_bcd(t->tm_min);
272 		t->tm_sec = dec_to_bcd(t->tm_sec);
273 	}
274 	write_register(RTC_YEAR, t->tm_year);
275 	write_register(RTC_MONTH, t->tm_mon);
276 	write_register(RTC_MDAY, t->tm_mday);
277 	write_register(RTC_HOUR, t->tm_hour);
278 	write_register(RTC_MIN, t->tm_min);
279 	write_register(RTC_SEC, t->tm_sec);
280 
281 	/* Stop the clock. */
282 	regA = read_register(RTC_REG_A);
283 	write_register(RTC_REG_A, regA | RTC_A_DV_STOP);
284 
285 	/* Allow updates and restart the clock. */
286 	write_register(RTC_REG_B, regB);
287 	write_register(RTC_REG_A, regA);
288 
289 	return OK;
290 }
291 
292 static int
293 write_register(int reg_addr, int value)
294 {
295 	if (sys_outb(RTC_INDEX, reg_addr) != OK) {
296 		log_warn(&log, "outb failed of %x\n", RTC_INDEX);
297 		return -1;
298 	}
299 	if (sys_outb(RTC_IO, value) != OK) {
300 		log_warn(&log, "outb failed of %x (index %x)\n", RTC_IO,
301 		    reg_addr);
302 		return -1;
303 	}
304 
305 	return OK;
306 }
307 
308 static int
309 arch_pwr_off(void)
310 {
311 	/* Not Implemented */
312 	return ENOSYS;
313 }
314 
315 static void
316 arch_exit(void)
317 {
318 	/* Nothing to clean up here */
319 	log_debug(&log, "Exiting...");
320 }
321 
322