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

Last change on this file since 87 was 87, checked in by markus, 14 years ago

changed copyright headers according to contract; removed evaluation message

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