source: trunk/src/os2ahci/libc.c@ 31

Last change on this file since 31 was 26, checked in by markus, 15 years ago

c600 build now uses ALP instead of MASM; added calling conventions to function declarations where necessary (Watcom build)

File size: 21.4 KB
Line 
1/******************************************************************************
2 * libc.c - minimal subset of C runtime library for os2ahci
3 *
4 * Copyright (c) 2010 Christian Mueller. Parts copied from/inspired by the
5 * Linux AHCI driver; those parts are (c) Linux AHCI/ATA maintainers
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22#include "os2ahci.h"
23
24/* -------------------------- macros and constants ------------------------- */
25
26#define MSG_REPLACEMENT_STRING 1178 /* empty message with a single %; used
27 * for printing custom messages via
28 * DevHelp_Save_Message() */
29
30/* heap management constants */
31#define HEAP_SIZE 8192
32#define HEAP_UNIT 128
33#define HEAP_UNIT_CNT (HEAP_SIZE / HEAP_UNIT)
34
35/* ------------------------ typedefs and structures ------------------------ */
36
37/* mdelay() calibration status */
38typedef enum {
39 MD_NOT_CALIBRATED, /* delay loop not calibrated */
40 MD_CALIBRATION_START, /* calibration run started */
41 MD_CALIBRATION_END, /* calibration run ended */
42 MD_CALIBRATION_DONE /* calibration complete */
43} MDCAL;
44
45/* -------------------------- function prototypes -------------------------- */
46
47static void long_to_asc (long val,
48 char _far *buf,
49 int base,
50 int zero, int flen);
51static void _cdecl _far mdelay_timer_callback (ULONG timer_handle,
52 ULONG parm1,
53 ULONG parm2);
54static int mdelay_cal_end (void);
55
56/* ------------------------ global/static variables ------------------------ */
57
58/* debug COM port base address */
59u16 com_base = 0x03f8;
60
61static char hex_digits[] = "0123456789abcdef";
62static ULONG com_lock;
63
64/* message table for DosHelp_Save_Message() which prints the first string */
65static MSGTABLE init_msgtbl = {
66 MSG_REPLACEMENT_STRING,
67 1,
68 0
69};
70
71/* COM port initialization sequence */
72static struct {
73 int reg;
74 u8 data;
75} com1_init_sequence[] = {
76 3, 0x80, /* overlay divisor latch register at 0x3f8 and 0x3f9 */
77 0, 0x00, /* set low byte of divisor to 1 (115200 baud) */
78 1, 0x01, /* set high byte of divisor to 0 */
79 3, 0x03, /* reset divisor latch register overlay and set 8,n,1 */
80 1, 0x00, /* disable interrupts */
81 4, 0x0f, /* modem control register */
82 -1, 0x00
83};
84
85/* delay loop calibration data */
86volatile MDCAL mdelay_cal_status = 0; /* delay loop calibration status */
87volatile u32 mdelay_loops_per_ms = 0; /* delay loop counter */
88
89/* very small heap for dynamic memory management */
90static u8 heap_buf[HEAP_SIZE];
91static u8 heap_units[HEAP_UNIT_CNT];
92
93/* ----------------------------- start of code ----------------------------- */
94
95/******************************************************************************
96 * Initialize COM1 to 57600,n,8,1
97 */
98void init_com1(void)
99{
100 int i;
101
102 DevHelp_CreateSpinLock(&com_lock);
103
104 for (i = 0; com1_init_sequence[i].reg != -1; i++) {
105 u16 port = com_base + com1_init_sequence[i].reg;
106 u8 data = com1_init_sequence[i].data;
107 _asm {
108 mov dx, port;
109 mov al, data;
110 out dx, al;
111 }
112 }
113}
114
115/******************************************************************************
116 * Print a formatted message into a string buffer. This is very basic,
117 * supporting only strings and integers (16 and 32 bits (l), decimal (d)
118 * and hex (x)). Formatting length modifiers are only supported with a single
119 * digit -- 32-bit numbers don't need more than 9 characters -- and an
120 * optional '0' in front.
121 */
122int vsprintf(char _far *buf, const char *fmt, va_list va)
123{
124 char _far *orig = buf;
125 char _far *s;
126 int lmod;
127 int fptr;
128 int zero;
129 int flen;
130
131 for (; *fmt != '\0'; fmt++) {
132 switch (*fmt) {
133
134 case '%':
135 fmt++;
136 zero = flen = 0;
137 if (*fmt >= '0' && *fmt <= '9') {
138 /* formatting length modifiers */
139 zero = (*fmt == '0') ? 1 : 0;
140 fmt += zero;
141 if ((flen = *fmt - '0') >= 1 && flen <= 9) {
142 fmt++;
143 }
144 }
145
146 /* data type modifiers */
147 lmod = (*fmt == 'l') ? 1 : 0;
148 fptr = (*fmt == 'F') ? 1 : 0;
149 fmt += lmod + fptr;
150
151 switch (*fmt) {
152
153 case 's':
154 if (fptr) {
155 char _far *p = va_arg(va, char _far *);
156 s = (p == 0) ? "[null]" : p;
157 } else {
158 char *p = va_arg(va, char *);
159 s = (p == 0) ? "[null]" : p;
160 }
161 while ((*buf = *(s++)) != '\0')
162 buf++;
163 break;
164
165 case 'c':
166 *(buf++) = (char) va_arg(va, int);
167 break;
168
169 case 'd':
170 long_to_asc((lmod) ? va_arg(va, long)
171 : va_arg(va, int), buf, 10, zero, flen);
172 buf += strlen(buf);
173 break;
174
175 case 'x':
176 long_to_asc((lmod) ? va_arg(va, u32)
177 : va_arg(va, u16), buf, 16, zero, flen);
178 buf += strlen(buf);
179 break;
180
181 case 'p':
182 if (fptr || lmod) {
183 u16 off = va_arg(va, u16);
184 u16 seg = va_arg(va, u16);
185 long_to_asc(seg, buf, 16, 1, 4);
186 buf += strlen(buf);
187 *(buf++) = ':';
188 long_to_asc(off, buf, 16, 1, 4);
189 buf += strlen(buf);
190 } else {
191 long_to_asc(va_arg(va, u16), buf, 16, 1, 4);
192 buf += strlen(buf);
193 }
194 break;
195
196 default:
197 *(buf++) = *fmt;
198 break;
199 }
200 break;
201
202 case '\n':
203 *(buf++) = '\r';
204 *(buf++) = '\n';
205 break;
206
207 default:
208 *(buf++) = *fmt;
209 break;
210
211 }
212 }
213
214 *buf = '\0';
215 return((int) (buf - orig));
216}
217
218/*******************************************************************************
219 * Print a formatted message into a string buffer. Relies on vsprintf()
220 */
221int sprintf(char _far *buf, const char *fmt, ...)
222{
223 va_list va;
224
225 va_start(va, fmt);
226 return(vsprintf(buf, fmt, va));
227}
228
229/******************************************************************************
230 * Print messages to COM
231 *
232 * NOTES: This function uses a 1K buffer for the resulting message. Thus,
233 * messages should never exceed 1024 bytes.
234 */
235void vprintf(const char *fmt, va_list va)
236{
237 static char buf[1024];
238 char *s;
239
240 spin_lock(com_lock);
241
242 vsprintf(buf, fmt, va);
243
244 /* write debug message to COM1 */
245 for (s = buf; *s != '\0'; s++) {
246
247 /* inp() and outp() are redefined by the DDK in an incompatible
248 * way (only words). Instead of messing around with those
249 * definitions, it's safer and easier to put the whole thing
250 * into an _asm block.
251 *
252 * The C equivalent would look like this:
253 *
254 * while (!(inp(com_base + 5) & 0x20));
255 * outp(com_base, *s);
256 */
257
258 _asm {
259 /* wait until COM transmitter is idle */
260 mov dx, com_base;
261 add dx, 5;
262 transmitter_not_idle:
263 in al, dx;
264 and al, 0x20;
265 jz transmitter_not_idle;
266
267 /* output character to be sent */
268 mov dx, com_base;
269 mov bx, s;
270 mov al, [bx];
271 out dx, al;
272 };
273 }
274
275 spin_unlock(com_lock);
276}
277
278/******************************************************************************
279 * Print messages to COM port
280 */
281void printf(const char *fmt, ...)
282{
283 va_list va;
284
285 va_start(va, fmt);
286 vprintf(fmt, va);
287}
288
289/******************************************************************************
290 * Print a message to the system console. This works only during device driver
291 * initialization.
292 *
293 * NOTE: This function uses a 1K buffer for the resulting message. Thus,
294 * messages should never exceed 1024 bytes...
295 */
296void cprintf(const char *fmt, ...)
297{
298 static char buf[1024];
299 va_list va;
300 size_t len;
301
302 va_start(va, fmt);
303 vsprintf(buf, fmt, va);
304
305 if (debug) {
306 /* print the same message to COM1 as well */
307 printf("%s", buf);
308 }
309
310 /* remove trailing CR/LF (DevHelp_Save_Message() will add it again) */
311 if ((len = strlen(buf)) >= 2 && buf[len-1] == '\n' && buf[len-2] == '\r') {
312 buf[len-2] = '\0';
313 }
314
315 init_msgtbl.MsgStrings[0] = buf;
316 DevHelp_Save_Message((NPBYTE) &init_msgtbl);
317}
318
319/******************************************************************************
320 * Print hex buffer to COM port.
321 */
322void phex(const void _far *p, int len, const char *fmt, ...)
323{
324 va_list va;
325 const unsigned char _far *buf = p;
326 long pos = 0;
327 int i;
328
329 if (!debug) {
330 return;
331 }
332
333 /* print header */
334 va_start(va, fmt);
335 vprintf(fmt, va);
336
337 /* print hex block */
338 while (len > 0) {
339 printf("%Fp ", buf);
340
341 /* print hex block */
342 for (i = 0; i < 16; i++) {
343 if (i < len) {
344 printf("%c%02x", ((i == 8) ? '-' : ' '), buf[i]);
345 } else {
346 printf(" ");
347 }
348 }
349
350 /* print ASCII block */
351 printf(" ");
352 for (i = 0; i < ((len > 16) ? 16 : len); i++) {
353 printf("%c", (buf[i] >= 32 && buf[i] < 128) ? buf[i] : '.');
354 }
355 printf("\n");
356
357 pos += 16;
358 buf += 16;
359 len -= 16;
360 }
361}
362
363/******************************************************************************
364 * Return length of zero-terminated string
365 */
366size_t strlen(const char _far *s)
367{
368 int len = 0;
369
370 while (*(s++) != '\0') {
371 len++;
372 }
373 return(len);
374}
375
376/******************************************************************************
377 * Copy zero-terminated string
378 */
379char _far *strcpy(char _far *dst, const char _far *src)
380{
381 char _far *orig = dst;
382
383 while ((*(dst++) = *(src++)) != '\0');
384 return(orig);
385}
386
387/******************************************************************************
388 * Compare blocks of memory
389 */
390int memcmp(void _far *p1, void _far *p2, size_t len)
391{
392 register char _far *s1 = p1;
393 register char _far *s2 = p2;
394 int n = 0;
395
396 while (len > 0) {
397 if ((n = *(s1++) - *(s2++)) != 0) {
398 return(n);
399 }
400 len--;
401 }
402 return(0);
403}
404
405/******************************************************************************
406 * Convert a string to a long value using the specified base
407 */
408long strtol(const char _far *buf, const char _far * _far *ep, int base)
409{
410 register const char _far *s = buf;
411 long val = 0;
412 int negative = 0;
413
414 /* skip leading whitespace */
415 while (*s == ' ' || *s == '\t') {
416 s++;
417 }
418
419 /* positive or negative */
420 if (*s == '-') {
421 negative = 1;
422 s++;
423 } else if (*s == '+') {
424 s++;
425 }
426
427 /* convert string to long integer */
428 for (;; s++) {
429 int digit = (*s <= '9') ? (*s - '0') : (tolower(*s) - 'a' + 10);
430 if (digit < 0 || digit >= base) {
431 break;
432 }
433 val *= base;
434 val += digit;
435 }
436
437 if (ep != NULL) {
438 *ep = s;
439 }
440 if (negative) {
441 val = -val;
442 }
443 return(val);
444}
445
446/******************************************************************************
447 * Extremely simple and stupid implementation of malloc(). The heap is very
448 * small, only 8K at the moment, and the memory blocks are managed using a
449 * simple array of "number of heap units allocated", zero meaning this unit is
450 * available. Each heap unit is currently 128 bytes.
451 *
452 * Dynamic memory is primarily used for things like ATA identify, ATAPI
453 * sense buffers, etc. and should be freed as soon as possible, otherwise
454 * we'll quickly run out of memory.
455 *
456 * NOTE: This function is not reentrant, thus must be called with the driver-
457 * level spinlock held. The main reason for this design is that most
458 * functions that need dynamic memory are already holding the spinlock.
459 */
460void *malloc(size_t len)
461{
462 u16 units = (len + HEAP_UNIT - 1) / HEAP_UNIT;
463 u16 i;
464 u16 n;
465
466 /* find a sequence of free heap units big enough for the requested length */
467 for (i = 0; i < HEAP_UNIT_CNT; i++) {
468 if (heap_units[i] == 0) {
469 for (n = i; n < i + units && n < HEAP_UNIT_CNT; n++) {
470 if (heap_units[n] != 0) {
471 break;
472 }
473 }
474 if (n == i + units) {
475 /* found a chunk large enough; update 'heap_units[]' */
476 for (; i < n; i++) {
477 heap_units[i] = (u8) (n - i);
478 }
479 return(heap_buf + (n - units) * HEAP_UNIT);
480 }
481
482 /* keep searching... */
483 i = n;
484 } else {
485 /* skip occupied heap units */
486 i += heap_units[i] - 1;
487 }
488 }
489
490 /* out of memory */
491 dprintf("malloc(%d): out of memory\n", len);
492 return(NULL);
493}
494
495/******************************************************************************
496 * Free block of memory allocted by malloc().
497 *
498 * NOTE: This function is not reentrant, thus must be called with the driver-
499 * level spinlock held. The main reason for this design is that most
500 * functions that need dynamic memory are already holding the spinlock.
501 */
502void free(void *ptr)
503{
504 u8 *p = (u8 *) ptr;
505 u16 first_unit;
506 u16 units;
507 u16 i;
508
509 if (p < heap_buf || p >= heap_buf + sizeof(heap_buf) ||
510 (u16) (p - heap_buf) % HEAP_UNIT != 0) {
511 dprintf("free(0x%p): invalid pointer (heap_buf = 0x%p)\n",
512 (u16) p, (u16) heap_buf);
513 return;
514 }
515
516 /* clear unit allocation counters in heap_units[] */
517 first_unit = (u16) (p - heap_buf) / HEAP_UNIT;
518 units = heap_units[first_unit];
519 for (i = first_unit; i < first_unit + units; i++) {
520 heap_units[i] = 0;
521 }
522}
523
524/******************************************************************************
525 * Calibrate 'mdelay()' loop. This is done by setting up a 1 second timer
526 * with a callback that sets 'mdelay_done' to MD_CALIBRATION_END. Then it
527 * calls mdelay() with a large milliseond value an initial delay loop counter
528 * of 1,000,000. When the timer triggers, 'mdelay()' will stop and update
529 * the delay loop counter.
530 *
531 * This function needs to be called at device driver init time. Since it uses
532 * ADD timers, it must be called with interrupts enabled. All this is not very
533 * precise (we should wait for a clock tick before starting, ...) but we don't
534 * really need precise timers.
535 */
536void mdelay_cal(void)
537{
538 ULONG timer_handle;
539
540 dprintf("calibrating delay loop... ");
541
542 mdelay_loops_per_ms = 100000;
543 mdelay_cal_status = MD_CALIBRATION_START;
544
545 ADD_StartTimerMS(&timer_handle, 1000, (PFN) mdelay_timer_callback, 0, 0);
546 mdelay(999999999);
547 ADD_CancelTimer(timer_handle);
548
549 dprintf("done (loops per ms = %ld)\n", mdelay_loops_per_ms);
550}
551
552/******************************************************************************
553 * Wait specified number of milliseconds. This is implemented using a busy
554 * loop and is only good for delays in the millisecond range but never for more
555 * than a few milliseconds and only in situations where a proper timer won't do.
556 * As a rule of thumb, don't call this function and use ADD timers, instead.
557 *
558 * NOTES:
559 *
560 * - Timers are problematic on x86 platforms because there's no reliable
561 * hardware timer on all architectures and the CPU clock speed may change
562 * while executing delay loops (AMD Cool&Quiet and Intel SpeedStep), thus
563 * calibration routines won't really be sufficient. But this usually only
564 * extends the delay and we don't really need a high precision timer. The
565 * exception are things like notebooks that are clocked slower when on
566 * battery and which got booted while on battery. Should still be OK,
567 * though, because our requirements are not that strict.
568 *
569 * - The code in this function is inefficient by design to make sure it
570 * will work with future CPUs which might otherwise be too fast for
571 * our loop counters. Part of this design is using volatile variables to
572 * force memory operations.
573 *
574 * - Before using this function, call mdelay_calibrate() to determine the
575 * number of inner loops required per millisecond.
576 */
577void mdelay(u32 millies)
578{
579 volatile u32 i;
580 volatile u32 n;
581
582 for (i = 0; i < millies; i++) {
583 for (n = 0; n < mdelay_loops_per_ms; n++) {
584 if (mdelay_cal_end()) {
585 /* this is a calibration run that just ended */
586 goto complete_calibration;
587 }
588 }
589 }
590 return;
591
592complete_calibration:
593 /* complete calibration cycle */
594 if (i < 1000) {
595 /* Initial value for delay loop was too high; interpolate results for
596 * an assumed initial delay loop divided by 1000.
597 */
598 i = i * 1000 + mdelay_loops_per_ms % 1000;
599 mdelay_loops_per_ms /= 1000;
600 }
601 mdelay_loops_per_ms = (mdelay_loops_per_ms * i) / 1000;
602 mdelay_cal_status = MD_CALIBRATION_DONE;
603}
604
605/******************************************************************************
606 * Sleep specified number of milliseonds. This is implemented by yielding the
607 * CPU until the system timer value indicates we're done. This function can
608 * only be called at task time, or from a context hook.
609 *
610 * NOTE: The accuracy is limited by the OS/2 timer interrupt frequency which
611 * can lead to intervals up to 55ms (18.2 timer interrupts per second).
612 */
613void msleep(u32 millies)
614{
615 volatile PGINFOSEG gis;
616 ULONG start;
617 ULONG end;
618 PSEL p;
619
620 if (DevHelp_GetDOSVar(DHGETDOSV_SYSINFOSEG, 0, (PPVOID) &p)) {
621 /* no global info segment; use mdelay() */
622 mdelay(millies);
623 return;
624 }
625 gis = (PGINFOSEG) ((u32) *p << 16);
626 start = gis->msecs;
627 end = start + millies;
628
629 if (end < start) {
630 /* wrap-around; wait until 'msecs' has wrapped, too */
631 while (gis->msecs >= start) {
632 DevHelp_Yield();
633 }
634 }
635
636 while (gis->msecs <= end) {
637 DevHelp_Yield();
638 }
639}
640
641/******************************************************************************
642 * Halt processing by submitting an internal error. This is a last resort and
643 * should only be called when the system state is corrupt.
644 */
645void panic(char *msg)
646{
647 DevHelp_InternalError(msg, strlen(msg));
648}
649
650/******************************************************************************
651 * Disable interrupts. The reason for using a separate function for this is
652 * that the presence of _asm statements will disable compiler optimizations.
653 * In order to support nested calls, this function will return 0 if the
654 * interrupts were already disabled or != 0, if not.
655 *
656 * NOTE: SMP systems must use spinlocks, thus this function will only be
657 * compiled on non-SMP builds.
658 */
659#ifndef OS2AHCI_SMP
660int disable(void)
661{
662 int rc = 0;
663
664 _asm {
665 pushf
666 pop ax
667 and ax, 0x0200; /* "interrupts enabled" bit */
668 mov rc, ax;
669 cli
670 }
671
672 return(rc);
673}
674#endif
675
676/******************************************************************************
677 * Enable interrupts. The reason for using a separate function for this is
678 * that the presence of _asm statements will disable compiler optimizations.
679 *
680 * NOTE: SMP systems must use spinlocks, thus this function will only be
681 * compiled on non-SMP builds.
682 */
683#ifndef OS2AHCI_SMP
684void enable(void)
685{
686 _asm sti;
687}
688#endif
689
690/******************************************************************************
691 * Convert 'long' to ASCII with the specified base
692 */
693static void long_to_asc(long val, char _far *buf, int base, int zero, int flen)
694{
695 register unsigned long abs_val = (unsigned long) val;
696 char tmp[80];
697 char _far *ptmp = tmp;
698 char _far *s;
699
700 if (base > 16) {
701 sprintf(buf, "[EVAL]");
702 return;
703 }
704
705 tmp[sizeof(tmp) - 1] = '\0';
706 for (s = ptmp + sizeof(tmp) - 2; s > ptmp; s--) {
707 *s = hex_digits[abs_val % base];
708 flen--;
709 if ((abs_val /= base) == 0) {
710 break;
711 }
712 }
713
714 /* prepend minus sign if val was negative and base is decimal or less */
715 if (val < 0 && base <= 0) {
716 *(--s) = '-';
717 flen--;
718 }
719
720 /* left-pad the resulting number with zeros or spaces up to 'flen' */
721 while (flen > 0) {
722 *(--s) = (zero) ? '0' : ' ';
723 flen--;
724 }
725
726 strcpy(buf, s);
727}
728
729/******************************************************************************
730 * Timer callback handler for 'mdelay_calibrate()'
731 */
732static void _cdecl _far mdelay_timer_callback(ULONG timer_handle,
733 ULONG parm1,
734 ULONG parm2)
735{
736 mdelay_cal_status = MD_CALIBRATION_END;
737}
738
739/******************************************************************************
740 * Determine whether an mdelay calibration run has just ended. This is in a
741 * function to prevent overzealous optimizers from removing the whole delay
742 * loop in mdelay().
743 */
744static int mdelay_cal_end(void)
745{
746 return(mdelay_cal_status == MD_CALIBRATION_END);
747}
Note: See TracBrowser for help on using the repository browser.