Annotation of prex/doc/html/doc/dki.html, Revision 1.1
1.1 ! nbrk 1: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
! 2: <html>
! 3: <head>
! 4: <title>Prex Driver-Kernel Interface</title>
! 5: <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
! 6: <meta name="keywords" content="Prex, embedded, real-time, operating system, RTOS, open source, free">
! 7: <meta name="author" content="Kohsuke Ohtani">
! 8: <link rel="stylesheet" type="text/css" href="../default.css" media="screen">
! 9: <link rel="stylesheet" type="text/css" href="../print.css" media="print">
! 10: </head>
! 11: <body>
! 12: <div id="top">
! 13: </div>
! 14: <div id="middle">
! 15:
! 16: <table id="content" cellpadding="0" cellspacing="0">
! 17: <tbody>
! 18:
! 19: <tr>
! 20: <td id="header" colspan="2" valign="top">
! 21: <table width="100%" border="0" cellspacing="0" cellpadding="0">
! 22: <tr>
! 23: <td id="logo">
! 24: <a href="http://prex.sourceforge.net/">
! 25: <img alt="Prex logo" src="../img/logo.gif" border="0"
! 26: style="width: 250px; height: 54px;"></a>
! 27: </td>
! 28: <td id="brief" align="right" valign="bottom">
! 29: An Open Source, Royalty-free,<br>
! 30: Real-time Operating System
! 31: </td>
! 32: </tr>
! 33: </table>
! 34: </td>
! 35: </tr>
! 36:
! 37: <tr>
! 38: <td id="directory" style="vertical-align: top;">
! 39: <a href="http://prex.sourceforge.net/">Prex Home</a> >
! 40: <a href="index.html">Document Index</a> >
! 41: Driver-Kernel Interface
! 42: </tr>
! 43: <tr><td class="pad" colspan="2" style="vertical-align: top;"></td></tr>
! 44:
! 45: <tr>
! 46: <td id="doc" style="vertical-align: top;">
! 47: <h1>Prex Driver-Kernel Interface</h1>
! 48:
! 49: <i>Version 1.0.2, 2007/12/23</i><br>
! 50: <br>
! 51:
! 52: <h3>Table of Contents</h3>
! 53: <ul>
! 54: <li><a href="#intro">Introduction</a></li>
! 55: </ul>
! 56: <ul>
! 57: <li><a href="#gen">General Information</a>
! 58: <ul>
! 59: <li><a href="#gen">Data Types</a></li>
! 60: <li><a href="#gen">Calls from ISR</a></li>
! 61: </ul>
! 62: </li>
! 63: </ul>
! 64: <ul>
! 65: <li><a href="#obj">Device Object</a>
! 66: <ul>
! 67: <li><a href="#obj">device_create</a></li>
! 68: <li><a href="#obj">device_destroy</a></li>
! 69: <li><a href="#obj">device_broadcast</a></li>
! 70: </ul>
! 71: </li>
! 72: </ul>
! 73: <ul>
! 74: <li><a href="#kmem">Kernel Memory</a>
! 75: <ul>
! 76: <li><a href="#kmem">kmem_alloc</a></li>
! 77: <li><a href="#kmem">kmem_free</a></li>
! 78: <li><a href="#kmem">kmem_map</a></li>
! 79: </ul>
! 80: </li>
! 81: </ul>
! 82: <ul>
! 83: <li><a href="#umem">User Memory</a>
! 84: <ul>
! 85: <li><a href="#umem">umem_copyin</a></li>
! 86: <li><a href="#umem">umem_copyout</a></li>
! 87: <li><a href="#umem">umem_strnlen</a></li>
! 88: </ul>
! 89: </li>
! 90: </ul>
! 91: <ul>
! 92: <li><a href="#page">Physical Page</a>
! 93: <ul>
! 94: <li><a href="#page">page_alloc</a></li>
! 95: <li><a href="#page">page_free</a></li>
! 96: <li><a href="#page">page_reserve</a></li>
! 97: </ul>
! 98: </li>
! 99: </ul>
! 100: <ul>
! 101: <li><a href="#int">Interrupt</a>
! 102: <ul>
! 103: <li><a href="#int">irq_attach</a></li>
! 104: <li><a href="#int">irq_detach</a></li>
! 105: <li><a href="#int">irq_lock</a></li>
! 106: <li><a href="#int">irq_unlock</a></li>
! 107: </ul>
! 108: </li>
! 109: </ul>
! 110: <ul>
! 111: <li><a href="#sched">Scheduler</a>
! 112: <ul>
! 113: <li><a href="#sched">sched_lock</a></li>
! 114: <li><a href="#sched">sched_unlock</a></li>
! 115: <li><a href="#sched">sched_tsleep</a></li>
! 116: <li><a href="#sched">sched_wakeup</a></li>
! 117: <li><a href="#sched">sched_dpc</a></li>
! 118: </ul>
! 119: </li>
! 120: </ul>
! 121: <ul>
! 122: <li><a href="#timer">Timer</a>
! 123: <ul>
! 124: <li><a href="#timer">timer_callout</a></li>
! 125: <li><a href="#timer">timer_stop</a></li>
! 126: <li><a href="#timer">timer_delay</a></li>
! 127: <li><a href="#timer">timer_count</a></li>
! 128: <li><a href="#timer">timer_hook</a></li>
! 129: </ul>
! 130: </li>
! 131: </ul>
! 132: <ul>
! 133: <li><a href="#misc">Miscellaneous</a>
! 134: <ul>
! 135: <li><a href="#misc">exception_post</a></li>
! 136: <li><a href="#misc">machine_bootinfo</a></li>
! 137: <li><a href="#misc">machine_reset</a></li>
! 138: <li><a href="#misc">machine_halt</a></li>
! 139: <li><a href="#misc">phys_to_virt</a></li>
! 140: <li><a href="#misc">virt_to_phys</a></li>
! 141: <li><a href="#misc">debug_attach</a></li>
! 142: <li><a href="#misc">debug_dump</a></li>
! 143: <li><a href="#misc">printf</a></li>
! 144: <li><a href="#misc">panic</a></li>
! 145: </ul>
! 146: </li>
! 147: </ul>
! 148: <br>
! 149: <br>
! 150:
! 151: <h2 id="intro">Introduction</h2>
! 152: <p>
! 153: The Prex kernel provides the minimum service for the device drivers.
! 154: Since the driver module is separated from the kernel module, the drivers
! 155: can not access other kernel functions beyond this interface.
! 156: This mechanism helps to isolate the kernel from the driver codes.
! 157: </p>
! 158: <p>
! 159: This document describes the Driver-Kernel Interface (DKI) that can be
! 160: used by device drivers.
! 161: </p>
! 162:
! 163: <h2 id="gen">General Information</h2>
! 164:
! 165: <h3>Data Types</h3>
! 166: <p>
! 167: The following data types are defined by kernel.
! 168: </p>
! 169:
! 170: <table border="1" width="60%" cellspacing="0">
! 171: <tbody>
! 172: <tr>
! 173: <th>Data type</th>
! 174: <th>Description</th>
! 175: </tr>
! 176:
! 177: <tr>
! 178: <td>device_t</td>
! 179: <td>Used to identify the device object.</td>
! 180: </tr>
! 181: <tr>
! 182: <td>task_t</td>
! 183: <td>Used to identify the task.</td>
! 184: </tr>
! 185: </tbody>
! 186: </table>
! 187:
! 188: <h3>Calls from ISR</h3>
! 189: <p>
! 190: The driver-kernel service is limited at interrupt level
! 191: because the kernel does not synchronize all data accesses
! 192: for interrupt level access.
! 193: So, the device driver can use only the following functions
! 194: from the interrupt service routine.
! 195: </p>
! 196: <ul>
! 197: <li>irq_lock()</li>
! 198: <li>irq_unlock()</li>
! 199: <li>sched_wakeup()</li>
! 200: <li>sched_stat()</li>
! 201: <li>timer_callout()</li>
! 202: <li>timer_stop()</li>
! 203: <li>timer_count()</li>
! 204: <li>sched_lock()</li>
! 205: <li>sched_unlock()</li>
! 206: <li>sched_tsleep()</li>
! 207: <li>sched_wakeup()</li>
! 208: <li>sched_dpc()</li>
! 209: <li>exception_post()</li>
! 210: <li>printf()</li>
! 211: <li>panic()</li>
! 212: <li>machine_reset()</li>
! 213: <li>machine_dump()</li>
! 214: </ul>
! 215:
! 216:
! 217: <h2 id="obj">Device Object</h2>
! 218: <p>
! 219: The device object is created by the driver to communicate to the
! 220: application. Usually, the driver creates a device object for an existing
! 221: physical device. And, it can also be used to handle logical or virtual devices.
! 222: </p>
! 223:
! 224: <pre>
! 225: device_t device_create(const struct devio *io, const char *name, int flags);
! 226: int device_destroy(device_t dev);
! 227: int device_broadcast(int event, int force);
! 228: </pre>
! 229:
! 230: <dl>
! 231: <dt>device_create()</dt>
! 232: <dd>
! 233: Creates device object with the specified name in <i>name</i>.
! 234: The <i>io</i> argument points to the device I/O table for the device object.
! 235: This function returns the ID of the created device object on success, or
! 236: 0 on failure.
! 237: <pre>
! 238: /*
! 239: * Device object
! 240: */
! 241: typedef int *device_t;
! 242: </pre>
! 243: All device object has its associated device I/O table. The table defines
! 244: the function pointer for the corresponding request routines.
! 245: <pre>
! 246: /*
! 247: * Device I/O table
! 248: */
! 249: struct devio {
! 250: int (*open)(device_t dev, int mode);
! 251: int (*close)(device_t dev);
! 252: int (*read)(device_t dev, char *buf, u_long *size, u_long offset);
! 253: int (*write)(device_t dev, char *buf, u_long *size, u_long offset);
! 254: int (*ioctl)(device_t dev, int cmd, u_long arg);
! 255: int (*event)(device_t dev, int event);
! 256: };
! 257: </pre>
! 258: The driver can specify the one same I/O table for the different device
! 259: objects. All device I/O routine can get the device object ID as its
! 260: first argument. So, each routine can determine the request is sent to
! 261: which device object.
! 262: </dd>
! 263: </dl>
! 264:
! 265: <dl>
! 266: <dt>device_destroy()</dt>
! 267: <dd>
! 268: Deletes the device object specified in <i>dev</i>.
! 269: This function returns ENODEV if the specified device object does not exist.
! 270: </dd>
! 271: </dl>
! 272:
! 273: <dl>
! 274: <dt>device_broadcast()</dt>
! 275: <dd>
! 276: Broadcasts the message specified by <i>event</i> to all device objects.
! 277: If <i>force</i> is true, a kernel will ignore the value returned by
! 278: each driver, and continue event notification.
! 279: If <i>force</i> is false and any driver returns any error for the event,
! 280: a kernel stops the event notification. In this case, this function
! 281: returns an error code which is returned by that driver.
! 282: </dd>
! 283: </dl>
! 284:
! 285:
! 286: <h2 id="kmem">Kernel Memory</h2>
! 287: The kernel provides the following memory allocation services for drivers.
! 288: Please note that it can not allocate lager buffer than one page.
! 289: If the driver needs larger buffer, it should use page_alloc()
! 290: instead of kmem_alloc().
! 291: <pre>
! 292: void *kmem_alloc(size_t size);
! 293: void kmem_free(void *ptr);
! 294: void *kmem_map(void *addr, size_t size);
! 295: </pre>
! 296:
! 297: <dl>
! 298: <dt>kmem_alloc()</dt>
! 299: <dd>
! 300: Allocates the kernel buffer for the specified <i>size</i> bytes.
! 301: It returns the pointer to the allocated buffer on success, or NULL on failure.
! 302: </dd>
! 303: </dl>
! 304:
! 305: <dl>
! 306: <dt>kmem_free()</dt>
! 307: <dd>
! 308: Frees the allocated kernel buffer pointed by <i>ptr</i>.
! 309: </dd>
! 310: </dl>
! 311:
! 312: <dl>
! 313: <dt>kmem_map()</dt>
! 314: <dd>
! 315: Maps the specified virtual address <i>addr</i> to the kernel address.
! 316: It returns the pointer mapped in the kernel memory on success, or NULL if
! 317: there is no mapped memory.
! 318: </dd>
! 319: </dl>
! 320:
! 321: <h2 id="umem">User Memory</h2>
! 322: <p>
! 323: Since an access to user memory may cause a page fault, the user
! 324: buffer manipulation is handled by the kernel core code.
! 325: The driver should not access the user buffer directly. Instead,
! 326: it should use the following kernel services.
! 327: </p>
! 328: <pre>
! 329: int umem_copyin(void *uaddr, void *kaddr, size_t len);
! 330: int umem_copyout(void *kaddr, void *uaddr, size_t len);
! 331: int umem_strnlen(const char *uaddr, size_t maxlen, size_t *len);
! 332: </pre>
! 333:
! 334: <dl>
! 335: <dt>umem_copyin</dt>
! 336: <dd>
! 337: Copies the data from the user buffer to the kernel area.
! 338: Returns 0 on success, or EFAULT on failure.
! 339: </dd>
! 340: </dl>
! 341:
! 342: <dl>
! 343: <dt>umem_copyout</dt>
! 344: <dd>
! 345: Copies the data from the kernel buffer to the user area.
! 346: Returns 0 on success, or EFAULT on failure.
! 347: </dd>
! 348: </dl>
! 349:
! 350: <dl>
! 351: <dt>umem_strnlen</dt>
! 352: <dd>
! 353: Gets the length of specified string in <i>uaddr</i>.
! 354: Returns 0 on success, or EFAULT on failure.
! 355: The returned length does not include a NULL terminator.
! 356: </dd>
! 357: </dl>
! 358:
! 359: <h2 id="page">Physical Page</h2>
! 360: <pre>
! 361: void *page_alloc(u_long size);
! 362: void page_free(void *addr, u_long size);
! 363: int page_reserve(void *addr, u_long size);
! 364: </pre>
! 365:
! 366: <dl>
! 367: <dt>page_alloc()</dt>
! 368: <dd>
! 369: Allocates continuous pages for the specified <i>size</i> bytes.
! 370: This function returns the physical address of the allocated pages, or
! 371: returns NULL on failure. The kernel does not zero-fill this new page.
! 372: The requested size is automatically round up to the page boundary.
! 373: </dd>
! 374: </dl>
! 375:
! 376: <dl>
! 377: <dt>page_free()</dt>
! 378: <dd>
! 379: Frees allocated page block. The caller must provide the size information
! 380: in <i>size</i> argument that was specified for page_alloc().
! 381: </dd>
! 382: </dl>
! 383:
! 384: <dl>
! 385: <dt>page_reserve()</dt>
! 386: <dd>
! 387: Reserves pages in the specified address.
! 388: This function returns 0 on success, or -1 on failure.
! 389: </dd>
! 390: </dl>
! 391:
! 392:
! 393: <h2 id="int">Interrupt</h2>
! 394: <p>
! 395: The Prex kernel encapsulates the save/restore of the previous interrupt
! 396: state. irq_lock() will automatically save the previous interrupt state
! 397: if needed. So, the driver must call irq_unlock() for the same count of
! 398: the irq_lock() call.
! 399: </p>
! 400:
! 401: <pre>
! 402: int irq_attach(int irqno, int prio, int shared, int (*isr)(int), void (*ist)(int));
! 403: void irq_detach(int handle);
! 404: void irq_lock(void);
! 405: void irq_unlock(void);
! 406: </pre>
! 407:
! 408: <dl>
! 409: <dt>irq_attach()</dt>
! 410: <dd>
! 411: Attaches to the <i>ISR</i> (interrupt service request) and <i>ist</i>
! 412: (interrupt service thread) to the interrupt vector specified in <i>irqno</i>.
! 413: The argument <i>prio</i> is the logical interrupt priority level. The smaller
! 414: priority value is higher priority for interrupt processing.
! 415: The caller can specify one of the following interrupt priority levels.
! 416: <pre>
! 417: /*
! 418: * Interrupt priority levels
! 419: */
! 420: #define IPL_NONE 0 /* Nothing */
! 421: #define IPL_COMM 1 /* Serial, Parallel */
! 422: #define IPL_BLOCK 2 /* FDD, IDE */
! 423: #define IPL_NET 3 /* Network */
! 424: #define IPL_DISPLAY 4 /* Screen */
! 425: #define IPL_INPUT 5 /* Keyboard, Mouse */
! 426: #define IPL_AUDIO 6 /* Audio */
! 427: #define IPL_BUS 7 /* USB, PCCARD */
! 428: #define IPL_RTC 8 /* RTC Alarm */
! 429: #define IPL_PROFILE 9 /* Profiling timer */
! 430: #define IPL_CLOCK 10 /* System Clock Timer */
! 431: #define IPL_HIGH 11 /* Everything */
! 432: </pre>
! 433: If <i>shared</i> argument is true, the kernel allows the other irq owner
! 434: to attach to the same irq vector.
! 435: </dd>
! 436: </dl>
! 437:
! 438: <dl>
! 439: <dt>irq_detach()</dt>
! 440: <dd>
! 441: Detaches the interrupt from the IRQ specified by <i>handle</i>.
! 442: </dd>
! 443: </dl>
! 444:
! 445: <dl>
! 446: <dt>irq_lock()</dt>
! 447: <dd>
! 448: Masks all H/W interrupts, and increments the IRQ lock count.
! 449: </dd>
! 450: </dl>
! 451:
! 452: <dl>
! 453: <dt>irq_unlock()</dt>
! 454: <dd>
! 455: Decrements the IRQ lock count. If the IRQ lock count becomes 0,
! 456: the H/W interrupts are unmasked.
! 457: </dd>
! 458: </dl>
! 459:
! 460:
! 461: <h2 id="sched">Scheduler</h2>
! 462: <p>
! 463: The thread can sleep/wakeup for the specific event. The event works as
! 464: the queue of the sleeping threads.
! 465: </p>
! 466:
! 467: <pre>
! 468: void sched_lock(void);
! 469: void sched_unlock(void);
! 470: int sched_tsleep(struct event *evt, u_long timeout);
! 471: void sched_wakeup(struct event *evt);
! 472: void sched_dpc(struct dpc *dpc, void (*func)(void *), void *arg);
! 473: </pre>
! 474:
! 475: <dl>
! 476: <dt>sched_lock()</dt>
! 477: <dd>
! 478: Disables the thread switch, and increments the scheduling lock count.
! 479: This is used to synchronize the thread execution to protect
! 480: global resources. Since the scheduling lock count can be nested,
! 481: the caller must call the sched_unlock() routine the same number of
! 482: lock count.
! 483: </dd>
! 484: </dl>
! 485:
! 486: <dl>
! 487: <dt>sched_unlock()</dt>
! 488: <dd>
! 489: Decrements the scheduling lock count. If the scheduling lock count becomes 0,
! 490: the thread switch is enabled again.
! 491: </dd>
! 492: </dl>
! 493:
! 494: <dl>
! 495: <dt>sched_tsleep()</dt>
! 496: <dd>
! 497: Sleep the current thread until specified event occurs.
! 498: The caller can specify <i>timeout</i> value in msec.
! 499: If the <i>timeout</i> value is 0, the timeout timer does not work.
! 500: <pre>
! 501: /*
! 502: * Event for sleep/wakeup
! 503: */
! 504: struct event {
! 505: struct queue sleepq; /* Queue for waiting thread */
! 506: char *name; /* Event name */
! 507: };
! 508: </pre>
! 509: </dd>
! 510: </dl>
! 511:
! 512: <dl>
! 513: <dt>sched_wakeup()</dt>
! 514: <dd>
! 515: Wakes up all threads that are waiting for the specified event.
! 516: </dd>
! 517: </dl>
! 518:
! 519: <dl>
! 520: <dt>sched_dpc()</dt>
! 521: <dd>
! 522: Programs DPC (Deferred Procedure Call).
! 523: <pre>
! 524: /*
! 525: * DPC object
! 526: */
! 527: struct dpc {
! 528: struct queue link; /* Linkage on DPC queue */
! 529: int state;
! 530: void (*func)(void *); /* Call back routine */
! 531: void *arg; /* Argument to pass */
! 532: };
! 533: </pre>
! 534: </dd>
! 535: </dl>
! 536:
! 537: <h2 id="timer">Timer</h2>
! 538: <pre>
! 539: void timer_callout(struct timer *tmr, void (*func)(u_long), u_long arg, u_long msec);
! 540: void timer_stop(struct timer *tmr);
! 541: u_long timer_delay(u_long msec);
! 542: u_long timer_count(void);
! 543: int timer_hook(void (*func)(int));
! 544: </pre>
! 545:
! 546:
! 547: <dl>
! 548: <dt>timer_callout()</dt>
! 549: <dd>
! 550: Requests a call out timer. The specified <i>func</i> routine will be
! 551: called with <i>arg</i> argument after <i>msec</i>. The caller must
! 552: allocate the memory for the timer structure for <i>tmr</i>.
! 553: <pre>
! 554: /*
! 555: * Timer structure
! 556: */
! 557: struct timer {
! 558: struct list link; /* Linkage on timer chain */
! 559: int active; /* True if active */
! 560: u_long expire; /* Expire time (ticks) */
! 561: u_long interval; /* Time interval */
! 562: void (*func)(void *); /* Function to call */
! 563: void *arg; /* Function argument */
! 564: struct event event; /* Event for this timer */
! 565: };
! 566: </pre>
! 567: </dd>
! 568: </dl>
! 569:
! 570: <dl>
! 571: <dt>timer_stop()</dt>
! 572: <dd>
! 573: Stops a running timer.
! 574: </dd>
! 575: </dl>
! 576:
! 577: <dl>
! 578: <dt>timer_delay()</dt>
! 579: <dd>
! 580: Delays thread execution.
! 581: </dd>
! 582: </dl>
! 583:
! 584:
! 585: <dl>
! 586: <dt>timer_count()</dt>
! 587: <dd>
! 588: Returns current timer count (ticks since bootup).
! 589: </dd>
! 590: </dl>
! 591:
! 592: <dl>
! 593: <dt>timer_hook()</dt>
! 594: <dd>
! 595: Installs a hook routine for the timer tick.
! 596: </dd>
! 597: </dl>
! 598:
! 599:
! 600: <h2 id="misc">Miscellaneous</h2>
! 601: <pre>
! 602: int exception_post(task_t task, int exc);
! 603: void machine_bootinfo(struct boot_info **pbi);
! 604: void machine_reset(void);
! 605: void machine_halt(void);
! 606: void *phys_to_virt(void *p_addr);
! 607: void *virt_to_phys(void *v_addr);
! 608: void debug_dump(int index);
! 609: void debug_attach(void (*func)(char *));
! 610: void printf(const char *fmt, ...);
! 611: void panic(const char *fmt, ...);
! 612: </pre>
! 613:
! 614: <dl>
! 615: <dt>exception_post()</dt>
! 616: <dd>
! 617: Posts an exception for the specific task.
! 618: </dd>
! 619: </dl>
! 620:
! 621: <dl>
! 622: <dt>machine_bootinfo()</dt>
! 623: <dd>
! 624: Returns the pointer to the system boot infomation structure.
! 625: </dd>
! 626: </dl>
! 627:
! 628: <dl>
! 629: <dt>machine_reset()</dt>
! 630: <dd>
! 631: Resets the system.
! 632: </dd>
! 633: </dl>
! 634:
! 635: <dl>
! 636: <dt>machine_halt()</dt>
! 637: <dd>
! 638: Halts the system.
! 639: </dd>
! 640: </dl>
! 641:
! 642: <dl>
! 643: <dt>phys_to_virt()</dt>
! 644: <dd>
! 645: Return the kernel virtual address from specifid physical address.
! 646: </dd>
! 647: </dl>
! 648:
! 649: <dl>
! 650: <dt>virt_to_phys()</dt>
! 651: <dd>
! 652: Return the mapped physical address from specifid virtual address.
! 653: </dd>
! 654: </dl>
! 655:
! 656: <dl>
! 657: <dt>debug_dump()</dt>
! 658: <dd>
! 659: Dumps the system information for debug.
! 660: </dd>
! 661: </dl>
! 662:
! 663: <dl>
! 664: <dt>debug_attach()</dt>
! 665: <dd>
! 666: Attaches to the external output routine for the printf().
! 667: </dd>
! 668: </dl>
! 669:
! 670: <dl>
! 671: <dt>printf()</dt>
! 672: <dd>
! 673: Prints the driver message to the output device.
! 674: The message is enabled only with debugging kernel.
! 675: </dd>
! 676: </dl>
! 677:
! 678: <dl>
! 679: <dt>panic()</dt>
! 680: <dd>
! 681: Stops the system for the fatal error.
! 682: </dd>
! 683: </dl>
! 684:
! 685:
! 686: </td>
! 687: </tr>
! 688: <tr>
! 689: <td id="footer" colspan="2" style="vertical-align: top;">
! 690: <a href="http://sourceforge.net">
! 691: <img src="http://sourceforge.net/sflogo.php?group_id=132028&type=1"
! 692: alt="SourceForge.net Logo" border="0" height="31" width="88"></a><br>
! 693: Copyright© 2005-2007 Kohsuke Ohtani
! 694: </td>
! 695: </tr>
! 696:
! 697: </tbody>
! 698: </table>
! 699:
! 700: </div>
! 701: <div id="bottom"></div>
! 702:
! 703: </body>
! 704: </html>
CVSweb