Commit | Line | Data |
---|---|---|
88a7e859 KM |
1 | /* |
2 | * Copyright (c) 1988 University of Utah. | |
3 | * Copyright (c) 1982, 1990 The Regents of the University of California. | |
4 | * All rights reserved. | |
5 | * | |
6 | * This code is derived from software contributed to Berkeley by | |
7 | * the Systems Programming Group of the University of Utah Computer | |
8 | * Science Department. | |
9 | * | |
10 | * %sccs.include.redist.c% | |
11 | * | |
c5235cef | 12 | * from: Utah $Hdr: clock.c 1.18 91/01/21$ |
88a7e859 | 13 | * |
8642881d | 14 | * @(#)clock.c 7.10 (Berkeley) %G% |
88a7e859 KM |
15 | */ |
16 | ||
bc632365 MK |
17 | #include "param.h" |
18 | #include "kernel.h" | |
b28b3a13 | 19 | #include "../dev/hilreg.h" |
88a7e859 KM |
20 | #include "clockreg.h" |
21 | ||
b28b3a13 KB |
22 | #include "../include/psl.h" |
23 | #include "../include/cpu.h" | |
88a7e859 KM |
24 | |
25 | #if defined(GPROF) && defined(PROFTIMER) | |
b28b3a13 | 26 | #include "sys/gprof.h" |
88a7e859 KM |
27 | #endif |
28 | ||
22d09b27 | 29 | int clkstd[1]; |
07c3e286 | 30 | int profhz; |
88a7e859 KM |
31 | |
32 | static int month_days[12] = { | |
33 | 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 | |
34 | }; | |
35 | struct bbc_tm *gmt_to_bbc(); | |
36 | u_char bbc_registers[13]; | |
37 | u_char write_bbc_reg(), read_bbc_reg(); | |
38 | struct hil_dev *bbcaddr = NULL; | |
39 | ||
40 | /* | |
41 | * Machine-dependent clock routines. | |
42 | * | |
43 | * Startrtclock restarts the real-time clock, which provides | |
44 | * hardclock interrupts to kern_clock.c. | |
45 | * | |
46 | * Inittodr initializes the time of day hardware which provides | |
47 | * date functions. | |
48 | * | |
49 | * Resettodr restores the time of day hardware after a time change. | |
50 | * | |
51 | * A note on the real-time clock: | |
52 | * We actually load the clock with CLK_INTERVAL-1 instead of CLK_INTERVAL. | |
53 | * This is because the counter decrements to zero after N+1 enabled clock | |
54 | * periods where N is the value loaded into the counter. | |
55 | */ | |
56 | ||
57 | /* | |
58 | * Start the real-time clock. | |
59 | */ | |
60 | startrtclock() | |
61 | { | |
22d09b27 KM |
62 | register struct clkreg *clk; |
63 | ||
c5235cef | 64 | clkstd[0] = IIOV(0x5F8000); |
22d09b27 | 65 | clk = (struct clkreg *) clkstd[0]; |
88a7e859 KM |
66 | |
67 | clk->clk_cr2 = CLK_CR1; | |
68 | clk->clk_cr1 = CLK_RESET; | |
69 | clk->clk_cr2 = CLK_CR3; | |
70 | clk->clk_cr3 = 0; | |
71 | clk->clk_msb1 = (CLK_INTERVAL-1) >> 8 & 0xFF; | |
72 | clk->clk_lsb1 = (CLK_INTERVAL-1) & 0xFF; | |
73 | clk->clk_msb2 = 0; | |
74 | clk->clk_lsb2 = 0; | |
75 | clk->clk_msb3 = 0; | |
76 | clk->clk_lsb3 = 0; | |
77 | clk->clk_cr2 = CLK_CR1; | |
78 | clk->clk_cr1 = CLK_IENAB; | |
07c3e286 KM |
79 | |
80 | tick = CLK_INTERVAL * CLK_RESOLUTION; | |
81 | hz = 1000000 / (CLK_INTERVAL * CLK_RESOLUTION); | |
88a7e859 KM |
82 | } |
83 | ||
84 | /* | |
85 | * Returns number of usec since last recorded clock "tick" | |
86 | * (i.e. clock interrupt). | |
87 | */ | |
88 | clkread() | |
89 | { | |
90 | register struct clkreg *clk = (struct clkreg *) clkstd[0]; | |
91 | register int high, low; | |
92 | ||
93 | high = clk->clk_msb1; | |
94 | low = clk->clk_lsb1; | |
95 | if (high != clk->clk_msb1) | |
96 | high = clk->clk_msb1; | |
97 | ||
98 | high = (CLK_INTERVAL-1) - ((high << 8) | low); | |
99 | /* | |
100 | * Pending interrupt indicates that the counter has wrapped | |
101 | * since we went to splhigh(). Need to compensate. | |
102 | */ | |
103 | if (clk->clk_sr & CLK_INT1) | |
104 | high += CLK_INTERVAL; | |
105 | return((high * tick) / CLK_INTERVAL); | |
106 | } | |
107 | ||
108 | #include "clock.h" | |
109 | #if NCLOCK > 0 | |
110 | /* | |
111 | * /dev/clock: mappable high resolution timer. | |
112 | * | |
113 | * This code implements a 32-bit recycling counter (with a 4 usec period) | |
114 | * using timers 2 & 3 on the 6840 clock chip. The counter can be mapped | |
115 | * RO into a user's address space to achieve low overhead (no system calls), | |
116 | * high-precision timing. | |
117 | * | |
118 | * Note that timer 3 is also used for the high precision profiling timer | |
119 | * (PROFTIMER code above). Care should be taken when both uses are | |
120 | * configured as only a token effort is made to avoid conflicting use. | |
121 | */ | |
b28b3a13 | 122 | #include "sys/proc.h" |
c5235cef | 123 | #include "sys/resourcevar.h" |
b28b3a13 KB |
124 | #include "sys/ioctl.h" |
125 | #include "sys/malloc.h" | |
c5235cef | 126 | #include "vm/vm.h" |
88a7e859 | 127 | #include "clockioctl.h" |
b28b3a13 | 128 | #include "sys/vnode.h" |
8642881d | 129 | #include "sys/specdev.h" |
b28b3a13 | 130 | #include "sys/mman.h" |
88a7e859 KM |
131 | |
132 | int clockon = 0; /* non-zero if high-res timer enabled */ | |
133 | #ifdef PROFTIMER | |
134 | int profprocs = 0; /* # of procs using profiling timer */ | |
135 | #endif | |
136 | #ifdef DEBUG | |
137 | int clockdebug = 0; | |
138 | #endif | |
139 | ||
140 | /*ARGSUSED*/ | |
141 | clockopen(dev, flags) | |
142 | dev_t dev; | |
143 | { | |
144 | #ifdef PROFTIMER | |
145 | #ifdef GPROF | |
146 | /* | |
147 | * Kernel profiling enabled, give up. | |
148 | */ | |
149 | if (profiling) | |
150 | return(EBUSY); | |
151 | #endif | |
152 | /* | |
153 | * If any user processes are profiling, give up. | |
154 | */ | |
155 | if (profprocs) | |
156 | return(EBUSY); | |
157 | #endif | |
158 | if (!clockon) { | |
159 | startclock(); | |
160 | clockon++; | |
161 | } | |
162 | return(0); | |
163 | } | |
164 | ||
165 | /*ARGSUSED*/ | |
166 | clockclose(dev, flags) | |
167 | dev_t dev; | |
168 | { | |
bc632365 | 169 | (void) clockunmmap(dev, (caddr_t)0, curproc); /* XXX */ |
88a7e859 KM |
170 | stopclock(); |
171 | clockon = 0; | |
172 | return(0); | |
173 | } | |
174 | ||
175 | /*ARGSUSED*/ | |
bc632365 | 176 | clockioctl(dev, cmd, data, flag, p) |
88a7e859 KM |
177 | dev_t dev; |
178 | caddr_t data; | |
bc632365 | 179 | struct proc *p; |
88a7e859 KM |
180 | { |
181 | int error = 0; | |
182 | ||
183 | switch (cmd) { | |
184 | ||
88a7e859 | 185 | case CLOCKMAP: |
bc632365 | 186 | error = clockmmap(dev, (caddr_t *)data, p); |
88a7e859 KM |
187 | break; |
188 | ||
189 | case CLOCKUNMAP: | |
bc632365 | 190 | error = clockunmmap(dev, *(caddr_t *)data, p); |
88a7e859 KM |
191 | break; |
192 | ||
193 | case CLOCKGETRES: | |
194 | *(int *)data = CLK_RESOLUTION; | |
195 | break; | |
88a7e859 KM |
196 | |
197 | default: | |
198 | error = EINVAL; | |
199 | break; | |
200 | } | |
201 | return(error); | |
202 | } | |
203 | ||
204 | /*ARGSUSED*/ | |
205 | clockmap(dev, off, prot) | |
206 | dev_t dev; | |
207 | { | |
c5235cef | 208 | return((off + (INTIOBASE+CLKBASE+CLKSR-1)) >> PGSHIFT); |
88a7e859 KM |
209 | } |
210 | ||
bc632365 | 211 | clockmmap(dev, addrp, p) |
88a7e859 KM |
212 | dev_t dev; |
213 | caddr_t *addrp; | |
bc632365 | 214 | struct proc *p; |
88a7e859 | 215 | { |
22d09b27 KM |
216 | int error; |
217 | struct vnode vn; | |
218 | struct specinfo si; | |
219 | int flags; | |
220 | ||
221 | flags = MAP_FILE|MAP_SHARED; | |
222 | if (*addrp) | |
223 | flags |= MAP_FIXED; | |
224 | else | |
225 | *addrp = (caddr_t)0x1000000; /* XXX */ | |
226 | vn.v_type = VCHR; /* XXX */ | |
227 | vn.v_specinfo = &si; /* XXX */ | |
228 | vn.v_rdev = dev; /* XXX */ | |
bc632365 | 229 | error = vm_mmap(&p->p_vmspace->vm_map, (vm_offset_t *)addrp, |
22d09b27 | 230 | PAGE_SIZE, VM_PROT_ALL, flags, (caddr_t)&vn, 0); |
d0cc9c12 | 231 | return(error); |
88a7e859 KM |
232 | } |
233 | ||
bc632365 | 234 | clockunmmap(dev, addr, p) |
88a7e859 KM |
235 | dev_t dev; |
236 | caddr_t addr; | |
bc632365 | 237 | struct proc *p; |
88a7e859 | 238 | { |
22d09b27 | 239 | int rv; |
88a7e859 | 240 | |
22d09b27 KM |
241 | if (addr == 0) |
242 | return(EINVAL); /* XXX: how do we deal with this? */ | |
6055b25f | 243 | rv = vm_deallocate(&p->p_vmspace->vm_map, (vm_offset_t)addr, PAGE_SIZE); |
22d09b27 | 244 | return(rv == KERN_SUCCESS ? 0 : EINVAL); |
88a7e859 KM |
245 | } |
246 | ||
88a7e859 KM |
247 | startclock() |
248 | { | |
249 | register struct clkreg *clk = (struct clkreg *)clkstd[0]; | |
250 | ||
251 | clk->clk_msb2 = -1; clk->clk_lsb2 = -1; | |
252 | clk->clk_msb3 = -1; clk->clk_lsb3 = -1; | |
253 | ||
254 | clk->clk_cr2 = CLK_CR3; | |
255 | clk->clk_cr3 = CLK_OENAB|CLK_8BIT; | |
256 | clk->clk_cr2 = CLK_CR1; | |
257 | clk->clk_cr1 = CLK_IENAB; | |
258 | } | |
259 | ||
260 | stopclock() | |
261 | { | |
262 | register struct clkreg *clk = (struct clkreg *)clkstd[0]; | |
263 | ||
264 | clk->clk_cr2 = CLK_CR3; | |
265 | clk->clk_cr3 = 0; | |
266 | clk->clk_cr2 = CLK_CR1; | |
267 | clk->clk_cr1 = CLK_IENAB; | |
268 | } | |
269 | #endif | |
270 | ||
271 | #ifdef PROFTIMER | |
272 | /* | |
273 | * This code allows the hp300 kernel to use one of the extra timers on | |
274 | * the clock chip for profiling, instead of the regular system timer. | |
275 | * The advantage of this is that the profiling timer can be turned up to | |
276 | * a higher interrupt rate, giving finer resolution timing. The profclock | |
277 | * routine is called from the lev6intr in locore, and is a specialized | |
278 | * routine that calls addupc. The overhead then is far less than if | |
279 | * hardclock/softclock was called. Further, the context switch code in | |
280 | * locore has been changed to turn the profile clock on/off when switching | |
281 | * into/out of a process that is profiling (startprofclock/stopprofclock). | |
282 | * This reduces the impact of the profiling clock on other users, and might | |
283 | * possibly increase the accuracy of the profiling. | |
284 | */ | |
285 | int profint = PRF_INTERVAL; /* Clock ticks between interrupts */ | |
286 | int profscale = 0; /* Scale factor from sys clock to prof clock */ | |
287 | char profon = 0; /* Is profiling clock on? */ | |
288 | ||
289 | /* profon values - do not change, locore.s assumes these values */ | |
290 | #define PRF_NONE 0x00 | |
291 | #define PRF_USER 0x01 | |
292 | #define PRF_KERNEL 0x80 | |
293 | ||
294 | initprofclock() | |
295 | { | |
296 | #if NCLOCK > 0 | |
bc632365 | 297 | struct proc *p = curproc; /* XXX */ |
c5235cef | 298 | |
88a7e859 KM |
299 | /* |
300 | * If the high-res timer is running, force profiling off. | |
301 | * Unfortunately, this gets reflected back to the user not as | |
302 | * an error but as a lack of results. | |
303 | */ | |
304 | if (clockon) { | |
bc632365 | 305 | p->p_stats->p_prof.pr_scale = 0; |
88a7e859 KM |
306 | return; |
307 | } | |
308 | /* | |
309 | * Keep track of the number of user processes that are profiling | |
310 | * by checking the scale value. | |
311 | * | |
312 | * XXX: this all assumes that the profiling code is well behaved; | |
313 | * i.e. profil() is called once per process with pcscale non-zero | |
314 | * to turn it on, and once with pcscale zero to turn it off. | |
315 | * Also assumes you don't do any forks or execs. Oh well, there | |
316 | * is always adb... | |
317 | */ | |
bc632365 | 318 | if (p->p_stats->p_prof.pr_scale) |
88a7e859 KM |
319 | profprocs++; |
320 | else | |
321 | profprocs--; | |
322 | #endif | |
323 | /* | |
324 | * The profile interrupt interval must be an even divisor | |
325 | * of the CLK_INTERVAL so that scaling from a system clock | |
326 | * tick to a profile clock tick is possible using integer math. | |
327 | */ | |
328 | if (profint > CLK_INTERVAL || (CLK_INTERVAL % profint) != 0) | |
329 | profint = CLK_INTERVAL; | |
330 | profscale = CLK_INTERVAL / profint; | |
07c3e286 | 331 | profhz = hz * profscale; |
88a7e859 KM |
332 | } |
333 | ||
334 | startprofclock() | |
335 | { | |
336 | register struct clkreg *clk = (struct clkreg *)clkstd[0]; | |
337 | ||
338 | clk->clk_msb3 = (profint-1) >> 8 & 0xFF; | |
339 | clk->clk_lsb3 = (profint-1) & 0xFF; | |
340 | ||
341 | clk->clk_cr2 = CLK_CR3; | |
342 | clk->clk_cr3 = CLK_IENAB; | |
343 | } | |
344 | ||
345 | stopprofclock() | |
346 | { | |
347 | register struct clkreg *clk = (struct clkreg *)clkstd[0]; | |
348 | ||
349 | clk->clk_cr2 = CLK_CR3; | |
350 | clk->clk_cr3 = 0; | |
351 | } | |
352 | ||
353 | #ifdef GPROF | |
354 | /* | |
355 | * profclock() is expanded in line in lev6intr() unless profiling kernel. | |
356 | * Assumes it is called with clock interrupts blocked. | |
357 | */ | |
358 | profclock(pc, ps) | |
359 | caddr_t pc; | |
360 | int ps; | |
361 | { | |
362 | /* | |
363 | * Came from user mode. | |
364 | * If this process is being profiled record the tick. | |
365 | */ | |
366 | if (USERMODE(ps)) { | |
4f0a0b43 KM |
367 | if (curproc->p_stats->p_prof.pr_scale) |
368 | addupc(pc, &curproc->p_stats->p_prof, 1); | |
88a7e859 KM |
369 | } |
370 | /* | |
371 | * Came from kernel (supervisor) mode. | |
372 | * If we are profiling the kernel, record the tick. | |
373 | */ | |
374 | else if (profiling < 2) { | |
375 | register int s = pc - s_lowpc; | |
376 | ||
377 | if (s < s_textsize) | |
378 | kcount[s / (HISTFRACTION * sizeof (*kcount))]++; | |
379 | } | |
380 | /* | |
381 | * Kernel profiling was on but has been disabled. | |
382 | * Mark as no longer profiling kernel and if all profiling done, | |
383 | * disable the clock. | |
384 | */ | |
385 | if (profiling && (profon & PRF_KERNEL)) { | |
386 | profon &= ~PRF_KERNEL; | |
387 | if (profon == PRF_NONE) | |
388 | stopprofclock(); | |
389 | } | |
390 | } | |
391 | #endif | |
392 | #endif | |
393 | ||
394 | /* | |
395 | * Initialize the time of day register, based on the time base which is, e.g. | |
396 | * from a filesystem. | |
397 | */ | |
398 | inittodr(base) | |
399 | time_t base; | |
400 | { | |
401 | u_long timbuf = base; /* assume no battery clock exists */ | |
402 | static int bbcinited = 0; | |
403 | ||
404 | /* XXX */ | |
405 | if (!bbcinited) { | |
406 | if (badbaddr(&BBCADDR->hil_stat)) | |
407 | printf("WARNING: no battery clock\n"); | |
408 | else | |
409 | bbcaddr = BBCADDR; | |
410 | bbcinited = 1; | |
411 | } | |
412 | ||
413 | /* | |
414 | * bbc_to_gmt converts and stores the gmt in timbuf. | |
415 | * If an error is detected in bbc_to_gmt, or if the filesystem | |
416 | * time is more recent than the gmt time in the clock, | |
417 | * then use the filesystem time and warn the user. | |
418 | */ | |
419 | if (!bbc_to_gmt(&timbuf) || timbuf < base) { | |
420 | printf("WARNING: bad date in battery clock\n"); | |
421 | timbuf = base; | |
422 | } | |
423 | if (base < 5*SECYR) { | |
424 | printf("WARNING: preposterous time in file system"); | |
425 | timbuf = 6*SECYR + 186*SECDAY + SECDAY/2; | |
426 | printf(" -- CHECK AND RESET THE DATE!\n"); | |
427 | } | |
428 | ||
429 | /* Battery clock does not store usec's, so forget about it. */ | |
430 | time.tv_sec = timbuf; | |
431 | } | |
432 | ||
433 | resettodr() | |
434 | { | |
435 | register int i; | |
436 | register struct bbc_tm *tmptr; | |
437 | ||
438 | tmptr = gmt_to_bbc(time.tv_sec); | |
439 | ||
440 | decimal_to_bbc(0, 1, tmptr->tm_sec); | |
441 | decimal_to_bbc(2, 3, tmptr->tm_min); | |
442 | decimal_to_bbc(4, 5, tmptr->tm_hour); | |
443 | decimal_to_bbc(7, 8, tmptr->tm_mday); | |
444 | decimal_to_bbc(9, 10, tmptr->tm_mon); | |
445 | decimal_to_bbc(11, 12, tmptr->tm_year); | |
446 | ||
447 | /* Some bogusness to deal with seemingly broken hardware. Nonsense */ | |
448 | bbc_registers[5] = ((tmptr->tm_hour / 10) & 0x03) + 8; | |
449 | ||
450 | write_bbc_reg(15, 13); /* reset prescalar */ | |
451 | ||
452 | for (i = 0; i <= NUM_BBC_REGS; i++) | |
453 | if (bbc_registers[i] != write_bbc_reg(i, bbc_registers[i])) { | |
454 | printf("Cannot set battery backed clock\n"); | |
455 | break; | |
456 | } | |
457 | } | |
458 | ||
459 | struct bbc_tm * | |
460 | gmt_to_bbc(tim) | |
461 | long tim; | |
462 | { | |
463 | register int i; | |
464 | register long hms, day; | |
465 | static struct bbc_tm rt; | |
466 | ||
467 | day = tim / SECDAY; | |
468 | hms = tim % SECDAY; | |
469 | ||
470 | /* Hours, minutes, seconds are easy */ | |
471 | rt.tm_hour = hms / 3600; | |
472 | rt.tm_min = (hms % 3600) / 60; | |
473 | rt.tm_sec = (hms % 3600) % 60; | |
474 | ||
475 | /* Number of years in days */ | |
476 | for (i = STARTOFTIME - 1900; day >= days_in_year(i); i++) | |
477 | day -= days_in_year(i); | |
478 | rt.tm_year = i; | |
479 | ||
480 | /* Number of months in days left */ | |
481 | if (leapyear(rt.tm_year)) | |
482 | days_in_month(FEBRUARY) = 29; | |
483 | for (i = 1; day >= days_in_month(i); i++) | |
484 | day -= days_in_month(i); | |
485 | days_in_month(FEBRUARY) = 28; | |
486 | rt.tm_mon = i; | |
487 | ||
488 | /* Days are what is left over (+1) from all that. */ | |
489 | rt.tm_mday = day + 1; | |
490 | ||
491 | return(&rt); | |
492 | } | |
493 | ||
494 | bbc_to_gmt(timbuf) | |
495 | u_long *timbuf; | |
496 | { | |
497 | register int i; | |
498 | register u_long tmp; | |
499 | int year, month, day, hour, min, sec; | |
500 | ||
501 | read_bbc(); | |
502 | ||
503 | sec = bbc_to_decimal(1, 0); | |
504 | min = bbc_to_decimal(3, 2); | |
505 | ||
506 | /* | |
507 | * Hours are different for some reason. Makes no sense really. | |
508 | */ | |
509 | hour = ((bbc_registers[5] & 0x03) * 10) + bbc_registers[4]; | |
510 | day = bbc_to_decimal(8, 7); | |
511 | month = bbc_to_decimal(10, 9); | |
512 | year = bbc_to_decimal(12, 11) + 1900; | |
513 | ||
514 | range_test(hour, 0, 23); | |
515 | range_test(day, 1, 31); | |
516 | range_test(month, 1, 12); | |
517 | range_test(year, STARTOFTIME, 2000); | |
518 | ||
519 | tmp = 0; | |
520 | ||
521 | for (i = STARTOFTIME; i < year; i++) | |
522 | tmp += days_in_year(i); | |
523 | if (leapyear(year) && month > FEBRUARY) | |
524 | tmp++; | |
525 | ||
526 | for (i = 1; i < month; i++) | |
527 | tmp += days_in_month(i); | |
528 | ||
529 | tmp += (day - 1); | |
530 | tmp = ((tmp * 24 + hour) * 60 + min) * 60 + sec; | |
531 | ||
532 | *timbuf = tmp; | |
533 | return(1); | |
534 | } | |
535 | ||
536 | read_bbc() | |
537 | { | |
538 | register int i, read_okay; | |
539 | ||
540 | read_okay = 0; | |
541 | while (!read_okay) { | |
542 | read_okay = 1; | |
543 | for (i = 0; i <= NUM_BBC_REGS; i++) | |
544 | bbc_registers[i] = read_bbc_reg(i); | |
545 | for (i = 0; i <= NUM_BBC_REGS; i++) | |
546 | if (bbc_registers[i] != read_bbc_reg(i)) | |
547 | read_okay = 0; | |
548 | } | |
549 | } | |
550 | ||
551 | u_char | |
552 | read_bbc_reg(reg) | |
553 | int reg; | |
554 | { | |
555 | u_char data = reg; | |
556 | ||
557 | if (bbcaddr) { | |
558 | send_hil_cmd(bbcaddr, BBC_SET_REG, &data, 1, NULL); | |
559 | send_hil_cmd(bbcaddr, BBC_READ_REG, NULL, 0, &data); | |
560 | } | |
561 | return(data); | |
562 | } | |
563 | ||
564 | u_char | |
565 | write_bbc_reg(reg, data) | |
566 | u_int data; | |
567 | { | |
568 | u_char tmp; | |
569 | ||
570 | tmp = (u_char) ((data << HIL_SSHIFT) | reg); | |
571 | ||
572 | if (bbcaddr) { | |
573 | send_hil_cmd(bbcaddr, BBC_SET_REG, &tmp, 1, NULL); | |
574 | send_hil_cmd(bbcaddr, BBC_WRITE_REG, NULL, 0, NULL); | |
575 | send_hil_cmd(bbcaddr, BBC_READ_REG, NULL, 0, &tmp); | |
576 | } | |
577 | return(tmp); | |
578 | } |