xref: /inferno-os/os/pc/devrtc.c (revision 4eb166cf184c1f102fb79e31b1465ea3e2021c39)
1 #include	"u.h"
2 #include	"../port/lib.h"
3 #include	"mem.h"
4 #include	"dat.h"
5 #include	"fns.h"
6 #include	"../port/error.h"
7 
8 /*
9  *  real time clock and non-volatile ram
10  */
11 
12 enum {
13 	Paddr=		0x70,	/* address port */
14 	Pdata=		0x71,	/* data port */
15 
16 	Seconds=	0x00,
17 	Minutes=	0x02,
18 	Hours=		0x04,
19 	Mday=		0x07,
20 	Month=		0x08,
21 	Year=		0x09,
22 	Status=		0x0A,
23 
24 	Nvoff=		128,	/* where usable nvram lives */
25 	Nvsize=		256,
26 
27 	Nbcd=		6,
28 };
29 
30 typedef struct Rtc	Rtc;
31 struct Rtc
32 {
33 	int	sec;
34 	int	min;
35 	int	hour;
36 	int	mday;
37 	int	mon;
38 	int	year;
39 };
40 
41 
42 enum{
43 	Qdir = 0,
44 	Qrtc,
45 	Qnvram,
46 };
47 
48 Dirtab rtcdir[]={
49 	".",	{Qdir, 0, QTDIR},	0,	0555,
50 	"nvram",	{Qnvram, 0},	Nvsize,	0664,
51 	"rtc",		{Qrtc, 0},	0,	0664,
52 };
53 
54 static ulong rtc2sec(Rtc*);
55 static void sec2rtc(ulong, Rtc*);
56 
57 void
58 rtcinit(void)
59 {
60 	if(ioalloc(Paddr, 2, 0, "rtc/nvr") < 0)
61 		panic("rtcinit: ioalloc failed");
62 }
63 
64 static Chan*
65 rtcattach(char* spec)
66 {
67 	return devattach('r', spec);
68 }
69 
70 static Walkqid*
71 rtcwalk(Chan* c, Chan *nc, char** name, int nname)
72 {
73 	return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
74 }
75 
76 static int
77 rtcstat(Chan* c, uchar* dp, int n)
78 {
79 	return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
80 }
81 
82 static Chan*
83 rtcopen(Chan* c, int omode)
84 {
85 	omode = openmode(omode);
86 	switch((ulong)c->qid.path){
87 	case Qrtc:
88 		if(strcmp(up->env->user, eve)!=0 && omode!=OREAD)
89 			error(Eperm);
90 		break;
91 	case Qnvram:
92 		if(strcmp(up->env->user, eve)!=0)
93 			error(Eperm);
94 	}
95 	return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
96 }
97 
98 static void
99 rtcclose(Chan*)
100 {
101 }
102 
103 #define GETBCD(o) ((bcdclock[o]&0xf) + 10*(bcdclock[o]>>4))
104 
105 static long
106 _rtctime(void)
107 {
108 	uchar bcdclock[Nbcd];
109 	Rtc rtc;
110 	int i;
111 
112 	/* don't do the read until the clock is no longer busy */
113 	for(i = 0; i < 10000; i++){
114 		outb(Paddr, Status);
115 		if(inb(Pdata) & 0x80)
116 			continue;
117 
118 		/* read clock values */
119 		outb(Paddr, Seconds);	bcdclock[0] = inb(Pdata);
120 		outb(Paddr, Minutes);	bcdclock[1] = inb(Pdata);
121 		outb(Paddr, Hours);	bcdclock[2] = inb(Pdata);
122 		outb(Paddr, Mday);	bcdclock[3] = inb(Pdata);
123 		outb(Paddr, Month);	bcdclock[4] = inb(Pdata);
124 		outb(Paddr, Year);	bcdclock[5] = inb(Pdata);
125 
126 		outb(Paddr, Status);
127 		if((inb(Pdata) & 0x80) == 0)
128 			break;
129 	}
130 
131 	/*
132 	 *  convert from BCD
133 	 */
134 	rtc.sec = GETBCD(0);
135 	rtc.min = GETBCD(1);
136 	rtc.hour = GETBCD(2);
137 	rtc.mday = GETBCD(3);
138 	rtc.mon = GETBCD(4);
139 	rtc.year = GETBCD(5);
140 
141 	/*
142 	 *  the world starts jan 1 1970
143 	 */
144 	if(rtc.year < 70)
145 		rtc.year += 2000;
146 	else
147 		rtc.year += 1900;
148 	return rtc2sec(&rtc);
149 }
150 
151 static Lock nvrtlock;
152 
153 long
154 rtctime(void)
155 {
156 	int i;
157 	long t, ot;
158 
159 	ilock(&nvrtlock);
160 
161 	/* loop till we get two reads in a row the same */
162 	t = _rtctime();
163 	for(i = 0; i < 100; i++){
164 		ot = t;
165 		t = _rtctime();
166 		if(ot == t)
167 			break;
168 	}
169 	if(i == 100) print("we are boofheads\n");
170 
171 	iunlock(&nvrtlock);
172 
173 	return t;
174 }
175 
176 static long
177 rtcread(Chan* c, void* buf, long n, vlong off)
178 {
179 	ulong t;
180 	char *a, *start;
181 	ulong offset = off;
182 
183 	if(c->qid.type & QTDIR)
184 		return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
185 
186 	switch((ulong)c->qid.path){
187 	case Qrtc:
188 		t = rtctime();
189 		n = readnum(offset, buf, n, t, 12);
190 		return n;
191 	case Qnvram:
192 		if(n == 0)
193 			return 0;
194 		if(n > Nvsize)
195 			n = Nvsize;
196 		a = start = smalloc(n);
197 
198 		ilock(&nvrtlock);
199 		for(t = offset; t < offset + n; t++){
200 			if(t >= Nvsize)
201 				break;
202 			outb(Paddr, Nvoff+t);
203 			*a++ = inb(Pdata);
204 		}
205 		iunlock(&nvrtlock);
206 
207 		if(waserror()){
208 			free(start);
209 			nexterror();
210 		}
211 		memmove(buf, start, t - offset);
212 		poperror();
213 
214 		free(start);
215 		return t - offset;
216 	}
217 	error(Ebadarg);
218 	return 0;
219 }
220 
221 #define PUTBCD(n,o) bcdclock[o] = (n % 10) | (((n / 10) % 10)<<4)
222 
223 static long
224 rtcwrite(Chan* c, void* buf, long n, vlong off)
225 {
226 	int t;
227 	char *a, *start;
228 	Rtc rtc;
229 	ulong secs;
230 	uchar bcdclock[Nbcd];
231 	char *cp, *ep;
232 	ulong offset = off;
233 
234 	if(offset!=0)
235 		error(Ebadarg);
236 
237 
238 	switch((ulong)c->qid.path){
239 	case Qrtc:
240 		/*
241 		 *  read the time
242 		 */
243 		cp = ep = buf;
244 		ep += n;
245 		while(cp < ep){
246 			if(*cp>='0' && *cp<='9')
247 				break;
248 			cp++;
249 		}
250 		secs = strtoul(cp, 0, 0);
251 
252 		/*
253 		 *  convert to bcd
254 		 */
255 		sec2rtc(secs, &rtc);
256 		PUTBCD(rtc.sec, 0);
257 		PUTBCD(rtc.min, 1);
258 		PUTBCD(rtc.hour, 2);
259 		PUTBCD(rtc.mday, 3);
260 		PUTBCD(rtc.mon, 4);
261 		PUTBCD(rtc.year, 5);
262 
263 		/*
264 		 *  write the clock
265 		 */
266 		ilock(&nvrtlock);
267 		outb(Paddr, Seconds);	outb(Pdata, bcdclock[0]);
268 		outb(Paddr, Minutes);	outb(Pdata, bcdclock[1]);
269 		outb(Paddr, Hours);	outb(Pdata, bcdclock[2]);
270 		outb(Paddr, Mday);	outb(Pdata, bcdclock[3]);
271 		outb(Paddr, Month);	outb(Pdata, bcdclock[4]);
272 		outb(Paddr, Year);	outb(Pdata, bcdclock[5]);
273 		iunlock(&nvrtlock);
274 		return n;
275 	case Qnvram:
276 		if(n == 0)
277 			return 0;
278 		if(n > Nvsize)
279 			n = Nvsize;
280 
281 		start = a = smalloc(n);
282 		if(waserror()){
283 			free(start);
284 			nexterror();
285 		}
286 		memmove(a, buf, n);
287 		poperror();
288 
289 		ilock(&nvrtlock);
290 		for(t = offset; t < offset + n; t++){
291 			if(t >= Nvsize)
292 				break;
293 			outb(Paddr, Nvoff+t);
294 			outb(Pdata, *a++);
295 		}
296 		iunlock(&nvrtlock);
297 
298 		free(start);
299 		return t - offset;
300 	}
301 	error(Ebadarg);
302 	return 0;
303 }
304 
305 Dev rtcdevtab = {
306 	'r',
307 	"rtc",
308 
309 	devreset,
310 	rtcinit,
311 	devshutdown,
312 	rtcattach,
313 	rtcwalk,
314 	rtcstat,
315 	rtcopen,
316 	devcreate,
317 	rtcclose,
318 	rtcread,
319 	devbread,
320 	rtcwrite,
321 	devbwrite,
322 	devremove,
323 	devwstat,
324 };
325 
326 #define SEC2MIN 60L
327 #define SEC2HOUR (60L*SEC2MIN)
328 #define SEC2DAY (24L*SEC2HOUR)
329 
330 /*
331  *  days per month plus days/year
332  */
333 static	int	dmsize[] =
334 {
335 	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
336 };
337 static	int	ldmsize[] =
338 {
339 	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
340 };
341 
342 /*
343  *  return the days/month for the given year
344  */
345 static int*
346 yrsize(int y)
347 {
348 	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
349 		return ldmsize;
350 	else
351 		return dmsize;
352 }
353 
354 /*
355  *  compute seconds since Jan 1 1970
356  */
357 static ulong
358 rtc2sec(Rtc *rtc)
359 {
360 	ulong secs;
361 	int i;
362 	int *d2m;
363 
364 	secs = 0;
365 
366 	/*
367 	 *  seconds per year
368 	 */
369 	for(i = 1970; i < rtc->year; i++){
370 		d2m = yrsize(i);
371 		secs += d2m[0] * SEC2DAY;
372 	}
373 
374 	/*
375 	 *  seconds per month
376 	 */
377 	d2m = yrsize(rtc->year);
378 	for(i = 1; i < rtc->mon; i++)
379 		secs += d2m[i] * SEC2DAY;
380 
381 	secs += (rtc->mday-1) * SEC2DAY;
382 	secs += rtc->hour * SEC2HOUR;
383 	secs += rtc->min * SEC2MIN;
384 	secs += rtc->sec;
385 
386 	return secs;
387 }
388 
389 /*
390  *  compute rtc from seconds since Jan 1 1970
391  */
392 static void
393 sec2rtc(ulong secs, Rtc *rtc)
394 {
395 	int d;
396 	long hms, day;
397 	int *d2m;
398 
399 	/*
400 	 * break initial number into days
401 	 */
402 	hms = secs % SEC2DAY;
403 	day = secs / SEC2DAY;
404 	if(hms < 0) {
405 		hms += SEC2DAY;
406 		day -= 1;
407 	}
408 
409 	/*
410 	 * generate hours:minutes:seconds
411 	 */
412 	rtc->sec = hms % 60;
413 	d = hms / 60;
414 	rtc->min = d % 60;
415 	d /= 60;
416 	rtc->hour = d;
417 
418 	/*
419 	 * year number
420 	 */
421 	if(day >= 0)
422 		for(d = 1970; day >= *yrsize(d); d++)
423 			day -= *yrsize(d);
424 	else
425 		for (d = 1970; day < 0; d--)
426 			day += *yrsize(d-1);
427 	rtc->year = d;
428 
429 	/*
430 	 * generate month
431 	 */
432 	d2m = yrsize(rtc->year);
433 	for(d = 1; day >= d2m[d]; d++)
434 		day -= d2m[d];
435 	rtc->mday = day + 1;
436 	rtc->mon = d;
437 
438 	return;
439 }
440 
441 uchar
442 nvramread(int addr)
443 {
444 	uchar data;
445 
446 	ilock(&nvrtlock);
447 	outb(Paddr, addr);
448 	data = inb(Pdata);
449 	iunlock(&nvrtlock);
450 
451 	return data;
452 }
453 
454 void
455 nvramwrite(int addr, uchar data)
456 {
457 	ilock(&nvrtlock);
458 	outb(Paddr, addr);
459 	outb(Pdata, data);
460 	iunlock(&nvrtlock);
461 }
462