1 /* $NetBSD: alloc.c,v 1.2 2007/06/25 21:38:43 joerg Exp $ */
4 * Copyright (c) 2007 Joerg Sonnenberger <joerg@NetBSD.org>.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <x86emu/x86emu_i8254.h>
36 #define KASSERT(x) assert(x)
39 #define I8254_FREQ 1193182 /* Hz */
42 bcd2bin(uint16_t bcd_val
)
44 return bcd_val
% 0x10 + (bcd_val
/ 0x10 % 0x10 * 10) +
45 (bcd_val
/ 0x100 % 0x10 * 100) + (bcd_val
/ 0x1000 % 0x10 * 1000);
49 bin2bcd(uint16_t bin_val
)
51 return (bin_val
% 10) + (bin_val
/ 10 % 10 * 0x10) +
52 (bin_val
/ 100 % 10 * 0x100) + (bin_val
/ 1000 % 10 * 0x1000);
56 * Compute tick of the virtual timer based on start time and
60 x86emu_i8254_gettick(struct x86emu_i8254
*sc
)
62 struct timespec curtime
;
65 (*sc
->gettime
)(&curtime
);
67 tick
= (curtime
.tv_sec
- sc
->base_time
.tv_sec
) * I8254_FREQ
;
68 tick
+= (uint64_t)(curtime
.tv_nsec
- sc
->base_time
.tv_nsec
) * I8254_FREQ
/ 1000000000;
73 /* Compute current counter value. */
75 x86emu_i8254_counter(struct x86emu_i8254_timer
*timer
, uint64_t curtick
)
79 /* Initial value if timer is disabled or not yet started */
80 if (timer
->gate_high
|| timer
->start_tick
> curtick
)
81 return timer
->active_counter
;
83 /* Compute maximum value based on BCD/binary mode */
84 if (timer
->active_is_bcd
)
89 curtick
-= timer
->start_tick
;
91 /* Check if first run over the time counter is over. */
92 if (curtick
<= timer
->active_counter
)
93 return timer
->active_counter
- curtick
;
94 /* Now curtick > 0 as both values above are unsigned. */
96 /* Special case of active_counter == maxtick + 1 */
97 if (timer
->active_counter
== 0 && curtick
- 1 <= maxtick
)
98 return maxtick
+ 1 - curtick
;
100 /* For periodic timers, compute current periode. */
101 if (timer
->active_mode
& 2)
102 return timer
->active_counter
- curtick
% timer
->active_counter
;
104 /* For one-shot timers, compute overflow. */
105 curtick
-= maxtick
+ 1;
106 return maxtick
- curtick
% maxtick
+ 1;
110 x86emu_i8254_out(struct x86emu_i8254_timer
*timer
, uint64_t curtick
)
117 * After the write of the LSB and before the write of the MSB,
118 * this should return LOW.
122 * If the timer was not started yet or is disabled,
125 if (timer
->gate_high
|| timer
->start_tick
> curtick
)
126 return (timer
->active_mode
!= 0);
128 /* Max tick based on BCD/binary mode */
129 if (timer
->active_is_bcd
)
134 curtick
-= timer
->start_tick
;
136 /* Return LOW until counter is 0, afterwards HIGH until reload. */
137 if (timer
->active_mode
== 0 || timer
->active_mode
== 1)
138 return curtick
>= timer
->start_tick
;
140 /* Return LOW until the counter is 0, raise to HIGH and go LOW again. */
141 if (timer
->active_mode
== 5 || timer
->active_mode
== 7)
142 return curtick
!= timer
->start_tick
;
145 * Return LOW until the counter is 1, raise to HIGH and go LOW
146 * again. Afterwards reload the counter.
148 if (timer
->active_mode
== 2 || timer
->active_mode
== 3) {
149 curtick
%= timer
->active_counter
;
150 return curtick
+ 1 != timer
->active_counter
;
154 * If the initial counter is even, return HIGH for the first half
155 * and LOW for the second. If it is even, bias the first half.
157 curtick
%= timer
->active_counter
;
158 return curtick
< (timer
->active_counter
+ 1) / 2;
162 x86emu_i8254_latch_status(struct x86emu_i8254_timer
*timer
, uint64_t curtick
)
164 if (timer
->status_is_latched
)
166 timer
->latched_status
= timer
->active_is_bcd
? 1 : 0;
167 timer
->latched_status
|= timer
->active_mode
<< 1;
168 timer
->latched_status
|= timer
->rw_status
;
169 timer
->latched_status
|= timer
->null_count
? 0x40 : 0;
173 x86emu_i8254_latch_counter(struct x86emu_i8254_timer
*timer
, uint64_t curtick
)
175 if (!timer
->counter_is_latched
)
176 return; /* Already latched. */
177 timer
->latched_counter
= x86emu_i8254_counter(timer
, curtick
);
178 timer
->counter_is_latched
= true;
182 x86emu_i8254_write_command(struct x86emu_i8254
*sc
, uint8_t val
)
184 struct x86emu_i8254_timer
*timer
;
187 if ((val
>> 6) == 3) {
188 /* Read Back Command */
191 curtick
= x86emu_i8254_gettick(sc
);
192 for (i
= 0; i
< 3; ++i
) {
193 timer
= &sc
->timer
[i
];
195 if ((val
& (2 << i
)) == 0)
197 if ((val
& 0x10) != 0)
198 x86emu_i8254_latch_status(timer
, curtick
);
199 if ((val
& 0x20) != 0)
200 x86emu_i8254_latch_counter(timer
, curtick
);
205 timer
= &sc
->timer
[val
>> 6];
207 switch (val
& 0x30) {
209 x86emu_i8254_latch_counter(timer
, x86emu_i8254_gettick(sc
));
212 timer
->write_lsb
= timer
->read_lsb
= true;
213 timer
->write_msb
= timer
->read_msb
= false;
216 timer
->write_lsb
= timer
->read_lsb
= false;
217 timer
->write_msb
= timer
->read_msb
= true;
220 timer
->write_lsb
= timer
->read_lsb
= true;
221 timer
->write_msb
= timer
->read_msb
= true;
224 timer
->rw_status
= val
& 0x30;
225 timer
->null_count
= true;
226 timer
->new_mode
= (val
>> 1) & 0x7;
227 timer
->new_is_bcd
= (val
& 1) == 1;
231 x86emu_i8254_read_counter(struct x86emu_i8254
*sc
,
232 struct x86emu_i8254_timer
*timer
)
237 /* If status was latched by Read Back Command, return it. */
238 if (timer
->status_is_latched
) {
239 timer
->status_is_latched
= false;
240 return timer
->latched_status
;
244 * The value of the counter is either the latched value
245 * or the current counter.
247 if (timer
->counter_is_latched
)
248 val
= timer
->latched_counter
;
250 val
= x86emu_i8254_counter(&sc
->timer
[2],
251 x86emu_i8254_gettick(sc
));
253 if (timer
->active_is_bcd
)
256 /* Extract requested byte. */
257 if (timer
->read_lsb
) {
259 timer
->read_lsb
= false;
260 } else if (timer
->read_msb
) {
262 timer
->read_msb
= false;
264 output
= 0; /* Undefined value. */
266 /* Clean latched status if all requested bytes have been read. */
267 if (!timer
->read_lsb
&& !timer
->read_msb
)
268 timer
->counter_is_latched
= false;
274 x86emu_i8254_write_counter(struct x86emu_i8254
*sc
,
275 struct x86emu_i8254_timer
*timer
, uint8_t val
)
277 /* Nothing to write, undefined. */
278 if (!timer
->write_lsb
&& !timer
->write_msb
)
281 /* Update requested bytes. */
282 if (timer
->write_lsb
) {
283 timer
->new_counter
&= ~0xff;
284 timer
->new_counter
|= val
;
285 timer
->write_lsb
= false;
287 KASSERT(timer
->write_msb
);
288 timer
->new_counter
&= ~0xff00;
289 timer
->new_counter
|= val
<< 8;
290 timer
->write_msb
= false;
293 /* If all requested bytes have been written, update counter. */
294 if (!timer
->write_lsb
&& !timer
->write_msb
) {
295 timer
->null_count
= false;
296 timer
->counter_is_latched
= false;
297 timer
->status_is_latched
= false;
298 timer
->active_is_bcd
= timer
->new_is_bcd
;
299 timer
->active_mode
= timer
->new_mode
;
300 timer
->start_tick
= x86emu_i8254_gettick(sc
) + 1;
301 if (timer
->new_is_bcd
)
302 timer
->active_counter
= bcd2bin(timer
->new_counter
);
307 x86emu_i8254_read_nmi(struct x86emu_i8254
*sc
)
311 val
= (sc
->timer
[2].gate_high
) ? 1 : 0;
312 if (x86emu_i8254_out(&sc
->timer
[2], x86emu_i8254_gettick(sc
)))
319 x86emu_i8254_write_nmi(struct x86emu_i8254
*sc
, uint8_t val
)
323 old_gate
= sc
->timer
[2].gate_high
;
324 sc
->timer
[2].gate_high
= (val
& 1) == 1;
325 if (!old_gate
&& sc
->timer
[2].gate_high
)
326 sc
->timer
[2].start_tick
= x86emu_i8254_gettick(sc
) + 1;
330 x86emu_i8254_init(struct x86emu_i8254
*sc
, void (*gettime
)(struct timespec
*))
332 struct x86emu_i8254_timer
*timer
;
335 sc
->gettime
= gettime
;
336 (*sc
->gettime
)(&sc
->base_time
);
338 for (i
= 0; i
< 3; ++i
) {
339 timer
= &sc
->timer
[i
];
340 timer
->gate_high
= false;
341 timer
->start_tick
= 0;
342 timer
->active_counter
= 0;
343 timer
->active_mode
= 0;
344 timer
->active_is_bcd
= false;
345 timer
->counter_is_latched
= false;
346 timer
->read_lsb
= false;
347 timer
->read_msb
= false;
348 timer
->status_is_latched
= false;
349 timer
->null_count
= false;
354 x86emu_i8254_inb(struct x86emu_i8254
*sc
, uint16_t port
)
356 KASSERT(x86emu_i8254_claim_port(sc
, port
));
358 return x86emu_i8254_read_counter(sc
, &sc
->timer
[0]);
360 return x86emu_i8254_read_counter(sc
, &sc
->timer
[1]);
362 return x86emu_i8254_read_counter(sc
, &sc
->timer
[2]);
364 return 0xff; /* unsupported */
365 return x86emu_i8254_read_nmi(sc
);
369 x86emu_i8254_outb(struct x86emu_i8254
*sc
, uint16_t port
, uint8_t val
)
371 KASSERT(x86emu_i8254_claim_port(sc
, port
));
373 x86emu_i8254_write_counter(sc
, &sc
->timer
[0], val
);
374 else if (port
== 0x41)
375 x86emu_i8254_write_counter(sc
, &sc
->timer
[1], val
);
376 else if (port
== 0x42)
377 x86emu_i8254_write_counter(sc
, &sc
->timer
[2], val
);
378 else if (port
== 0x43)
379 x86emu_i8254_write_command(sc
, val
);
381 x86emu_i8254_write_nmi(sc
, val
);
386 x86emu_i8254_claim_port(struct x86emu_i8254
*sc
, uint16_t port
)
388 /* i8254 registers */
389 if (port
>= 0x40 && port
< 0x44)
391 /* NMI register, used to control timer 2 and the output of it */