/*   FILE: serial_test.c -- A simple kernel module to demonstrate that
 *         something fishy is going on with my Venus chipset.
 * AUTHOR: W. Michael Petullo <serial@flyn.org>
 *   DATE: 3 Mar 2001
 */

/* Before using this test module, please correctly define IOPORT below.
 * This value should be the modem's first I/O port base address, which can be
 * discovered using `lspci -vvv`.  
 */

/* Compile with `gcc -c -O2 serial_test.c` and use with version 2.4.x
 * of the Linux kernel.
 */

/* Test one generally passes -- unless run right after a cold boot and before
 * anything else touches the chipset's IER.  Test two fails more than 50%
 * of the time on my machine.  Test three passes 99.99% of the time, though 
 * in practice the kludge fails more often in the actual serial driver.
 */

/* Load the module and watch your logs -- or wherever your kernel messages
 * are sent!
 */

#define MODULE
#define __KERNEL__

#define IOPORT 0xcc00
#define NUM_TESTS 10000

#include <linux/module.h>
#include <linux/sched.h>
#include <sys/io.h>
#include <linux/delay.h>

/* ============================ print_results () =========================== */ 
void print_results(char *header, int num_tests, int failure_count)
{
    printk("\n%s\n", header);
    printk("%d iteration(s) were run, %d failed.\n", num_tests,
	   failure_count);
    printk("This is a failure rate of %d percent.\n\n",
	   (int) ((float) failure_count / num_tests * 100));
}

/* ============================ test_one () ================================ */ 
void test_one (void)
{
    unsigned char scratch;
    outb(0, IOPORT + 1);
    outb(0x0F, IOPORT + 1);
    scratch = inb(IOPORT + 1);
    udelay(10);
    print_results("Test one: simple test (I usually fail after a cold start):",
                  1, scratch == 0x0F);
}

/* ============================ test_two () ================================ */ 
void test_two (void)
/* Simulation of serial driver in the Linux kernel. */
{
    int i, failure_count = 0;
    unsigned char scratch, scratch2, scratch3;
    for (i = 0; i < NUM_TESTS; i++) {
	scratch = inb(IOPORT + 1);
	outb(0, IOPORT + 1);
	outb(0xff, 0x080);
	scratch2 = inb(IOPORT + 1);
	outb(0x0F, IOPORT + 1);
	outb(0, 0x080);
	scratch3 = inb(IOPORT + 1);
	outb(scratch, IOPORT + 1);
	if (scratch2 || scratch3 != 0x0F) {
	    failure_count++;
	}
    }
    print_results("Test two: straight serial driver fragment:", NUM_TESTS, 
                  failure_count);
}

/* ============================ test_three () ============================== */ 
void test_three (void)
/* Simulation of a kludged serial driver in the Linux kernel. */
{
    int i, failure_count = 0;
    unsigned char scratch, scratch2, scratch3;
    for (i = 0; i < NUM_TESTS; i++) {
	scratch = inb(IOPORT + 1);
	outb(0, IOPORT + 1);
	outb(0xff, 0x080);
	scratch2 = inb(IOPORT + 1);
	outb(0x0F, IOPORT + 1);
	outb(0, 0x080);
	scratch3 = inb(IOPORT + 1);
	outb(scratch, IOPORT + 1);
	udelay(10); /* This is the kludge. */
	if (scratch2 || scratch3 != 0x0F) {
	    failure_count++;
	}
    }
    print_results("Test three: kludged serial driver fragment:", NUM_TESTS, 
                  failure_count);
}

/* ============================ init_module () ============================= */ 
int init_module(void)
{
    test_one ();
    test_two ();
    test_three ();
    return 0;
}
