Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 24 Oct 1996 12:43:24 -0600 (MDT)
From:      Nate Williams <nate@mt.sri.com>
To:        mobile@freebsd.org, current@freebsd.org, mi@aldan.zipnet.net
Subject:   Announce: New PS/2 driver
Message-ID:  <199610241843.MAA06370@rocky.mt.sri.com>

next in thread | raw e-mail | index | archive | help
Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> basically
re-wrote the existing psm.c driver, and although I haven't looked at the
code a whole lot, I have been able to test it against my laptops, the
NEC Versa and two different models of IBM ThinkPad.  In both cases it
recognized that I had a two-button PS/2 mouse on board.  Before I commit
this what I consider to be improved driver to the tree I want to give
folks a chance to try it out.

If you have a PS/2 mouse that needs the PSM_NORESET option, *OR* the
current driver doesn't recognize your mouse w/out hacks, or if you
simply have a PS/2 mouse that works with the current driver can you
replace /sys/i386/isa/psm.c with the below driver and see if it still
works?

If nobody has anything bad to say, or if I don't get *any* response in a
week I'm going to commit the new version since it appears less 'kludgy'
(the comments are certainly more verbose) than the existing version.

There is one outstanding PR regarding the -current psm driver, so I'd
like the originator to please test this driver out.  If I don't hear
anything back from him, I'll assume the driver works and I'm closing the
PR.



Nate

ps. The driver should also work under -stable as well, although I
removed that code for integration purposes.

-----------
/*-
 * Copyright (c) 1992, 1993 Erik Forsberg.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  Ported to 386bsd Oct 17, 1992
 *  Sandi Donno, Computer Science, University of Cape Town, South Africa
 *  Please send bug reports to sandi@cs.uct.ac.za
 *
 *  Thanks are also due to Rick Macklem, rick@snowhite.cis.uoguelph.ca -
 *  although I was only partially successful in getting the alpha release
 *  of his "driver for the Logitech and ATI Inport Bus mice for use with
 *  386bsd and the X386 port" to work with my Microsoft mouse, I nevertheless
 *  found his code to be an invaluable reference when porting this driver
 *  to 386bsd.
 *
 *  Further modifications for latest 386BSD+patchkit and port to NetBSD,
 *  Andrew Herbert <andrew@werple.apana.org.au> - 8 June 1993
 *
 *  Cloned from the Microsoft Bus Mouse driver, also by Erik Forsberg, by
 *  Andrew Herbert - 12 June 1993
 *
 *  Modified for PS/2 mouse by Charles Hannum <mycroft@ai.mit.edu>
 *  - 13 June 1993
 *
 *  Modified for PS/2 AUX mouse by Shoji Yuen <yuen@nuie.nagoya-u.ac.jp>
 *  - 24 October 1993
 *
 *  Hardware access routines and probe logic rewritten by
 *  Kazutaka Yokota <yokota@zodiac.mech.utsunomiya-u.ac.jp>
 *  - 3 October 1996. Posted to hackers@freebsd.org.
 *  - 14 October 1996.
 *  - 22 October 1996.
 */

#include "psm.h"

#if NPSM > 0

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/syslog.h>
#ifdef DEVFS
#include <sys/devfsext.h>
#endif /*DEVFS*/

#include <machine/mouse.h>
#include <machine/clock.h>

#include <i386/isa/isa.h>
#include <i386/isa/isa_device.h>

/*
 * driver specific options: the following options may be set by
 *  `options' statements in the kernel configuration file.
 */

/* controls debug logging: 0: no logging, 1: brief, 2: verbose */
#ifndef PSM_DEBUG
#define PSM_DEBUG	0
#endif

/* timing parameters */
#ifndef PSM_RESETDELAY
#define PSM_RESETDELAY  	200     /* wait 200msec after mouse reset */
#endif
#ifndef PSM_MAXWAIT
#define PSM_MAXWAIT		5 	/* wait 5 times at most after reset */
#endif

/* end of driver specific options */

/* constants */

/* I/O ports */
#define PSM_STATUS_PORT 	4	/* status port, read */
#define PSM_COMMAND_PORT	4	/* controller command port, write */
#define PSM_DATA_PORT		0	/* data port, read/write 
					   also used as keyboard command
					   and mouse command port */
/* FIXME: `IO_PSMSIZE' should really be in `isa.h'. */
#define IO_PSMSIZE		(PSM_COMMAND_PORT - PSM_DATA_PORT + 1)

/* controller commands (sent to PSM_COMMAND_PORT) */
#define PSMC_SET_COMMAND_BYTE 	0x0060
#define PSMC_GET_COMMAND_BYTE 	0x0020
#define PSMC_WRITE_TO_AUX    	0x00d4
#define PSMC_DISABLE_AUX_PORT 	0x00a7
#define PSMC_ENABLE_AUX_PORT 	0x00a8
#define PSMC_TEST_AUX_PORT   	0x00a9
#define PSMC_DIAGNOSE	     	0x00aa
#define PSMC_TEST_KBD_PORT   	0x00ab
#define PSMC_DISABLE_KBD_PORT 	0x00ad
#define PSMC_ENABLE_KBD_PORT 	0x00ae

/* controller command byte (set by PSMC_SET_COMMAND_BYTE) */
#define PSM_TRANSLATION_MODE	0x0040
#define PSM_RESERVED_BITS	0x0004
#define PSM_ENABLE_KBD_PORT    	0x0000
#define PSM_DISABLE_KBD_PORT   	0x0010
#define PSM_ENABLE_AUX_PORT	0x0000
#define PSM_DISABLE_AUX_PORT	0x0020
#define PSM_ENABLE_AUX_INT	0x0002
#define PSM_DISABLE_AUX_INT	0x0000
#define PSM_ENABLE_KBD_INT     	0x0001
#define PSM_DISABLE_KBD_INT    	0x0000
#define PSM_KBD_CONTROL_BITS	(PSM_DISABLE_KBD_PORT | PSM_ENABLE_KBD_INT)
#define PSM_AUX_CONTROL_BITS	(PSM_DISABLE_AUX_PORT | PSM_ENABLE_AUX_INT)

/* keyboard device commands (sent to PSM_DATA_PORT) */
#define PSMC_RESET_KBD	     	0x00ff

/* aux device commands (sent to PSM_DATA_PORT) */
#define PSMC_RESET_DEV	     	0x00ff
#define PSMC_ENABLE_DEV      	0x00f4
#define PSMC_DISABLE_DEV     	0x00f5
#define PSMC_SEND_DEV_ID     	0x00f2
#define PSMC_SEND_DEV_STATUS 	0x00e9
#define PSMC_SET_SCALING11	0x00e6
#define PSMC_SET_SCALING21	0x00e7
#define PSMC_SET_RESOLUTION	0x00e8
#define PSMC_SET_STREAM_MODE	0x00ea
#define PSMC_SET_SAMPLING_RATE	0x00f3

/* PSMC_SET_RESOLUTION argument */
#define PSMD_RESOLUTION_1	0	/* 1 count/mm */
#define PSMD_RESOLUTION_2	1	/* 2 count/mm */
#define PSMD_RESOLUTION_4	2	/* 4 count/mm (default after reset) */
#define PSMD_RESOLUTION_8	3	/* 8 count/mm */

/* status bits (PSM_STATUS_PORT) */
#define PSMS_KBD_BUFFER_FULL	0x0001
#define PSMS_AUX_BUFFER_FULL	0x0021
#define PSMS_CONTROLLER_BUSY 	0x0002

/* return code */
#define PSM_ACK 		0x00fa
#define PSM_RESEND		0x00fe

#define PSM_RESET_DONE		0x00aa
#define PSM_RESET_FAIL		0x00fc
#define PSM_DIAG_DONE		0x0055
#define PSM_DIAG_FAIL		0x00fd
#define PSM_ECHO		0x00ee

/* aux device ID */
#define PSM_MOUSE_ID		0
#define PSM_BALLPOINT_ID	2

#if 0
/* misc */
#define TRUE			(-1)
#define FALSE			0
#endif

/* some macros */
#define PSMUNIT(dev)		(minor(dev) >> 1)

#ifndef min
#define min(x,y) 		((x) < (y) ? (x) : (y))
#endif

/* ring buffer structure */
#define PSM_CHUNK		128	/* chunk size for read */
#define PSM_BSIZE		1024	/* buffer size */

struct ringbuf {
	int count, first, last;
	char queue[PSM_BSIZE];
};

/* PS/2 mouse device */
static struct {
	int addr;		/* base I/O port address */
	int id; 		/* device ID */
	int command_byte;	/* controller command byte */
	int buttons;		/* # of buttons */
} psm_dev[NPSM];

/* PS/2 mouse driver software control block */
static struct psm_softc {	/* Driver status information */
	struct ringbuf inq;	/* Input queue */
	struct selinfo rsel;	/* Process selecting for Input */
	unsigned char state;	/* Mouse driver state */
	unsigned char status;	/* Mouse button status */
	unsigned char button;	/* Previous mouse button status bits */
	int x, y;		/* accumulated motion in the X,Y axis */
#ifdef DEVFS
	void	*devfs_token;
	void	*n_devfs_token;
#endif
} psm_softc[NPSM];

/* driver state flags */
#define PSM_OPEN		1	/* Device is open */
#define PSM_ASLP		2	/* Waiting for mouse data */

/* function prototypes */
static int psmprobe __P((struct isa_device *));
static int psmattach __P((struct isa_device *));

static d_open_t psmopen;
static d_close_t psmclose;
static d_read_t psmread;
static d_ioctl_t psmioctl;
static d_select_t psmselect;

/* device driver declarateion */
struct isa_driver psmdriver = { psmprobe, psmattach, "psm" };

#define CDEV_MAJOR		21

static	struct	cdevsw psm_cdevsw = {
	psmopen,	psmclose,	psmread,	nowrite,	/* 21 */
	psmioctl,	nostop, 	nullreset,	nodevtotty,
	psmselect,	nommap, 	NULL,		"psm",	NULL,	-1
};

/* device I/O routines */

static int
wait_while_controller_busy(int port)
{
	/* FIXME: CPU will stay inside the loop for 100msec at most */
	int retry = 10000;

	while(inb(port + PSM_STATUS_PORT) & PSMS_CONTROLLER_BUSY) {
	    DELAY(10);
	    if (--retry < 0)
		return FALSE;
	}
	return TRUE;
}

static int
wait_until_controller_is_really_idle(int port)
{
	/* FIXME: CPU will stay inside the loop for 100msec at most */
	int retry = 10000;

	while(inb(port + PSM_STATUS_PORT) &
	      (PSMS_CONTROLLER_BUSY | PSMS_KBD_BUFFER_FULL)) {
	    DELAY(10);
	    if (--retry < 0)
		return FALSE;
	}
	return TRUE;
}

static int
wait_for_data(int port)
{
	/* FIXME: CPU will stay inside the loop for 200msec at most */
	int retry = 20000;

	while((inb(port + PSM_STATUS_PORT) & PSMS_KBD_BUFFER_FULL) == 0) {
	    DELAY(10);
	    if (--retry < 0)
		return FALSE;
	}
	return TRUE;
}

static int
wait_for_kbd_data(int port)
{
	/* FIXME: CPU will stay inside the loop for 200msec at most */
	int retry = 20000;

	while((inb(port + PSM_STATUS_PORT) & PSMS_AUX_BUFFER_FULL)
		!= PSMS_KBD_BUFFER_FULL) {
	    DELAY(10);
	    if (--retry < 0)
		return FALSE;
	}
	return TRUE;
}

static int
wait_for_aux_data(int port)
{
	/* FIXME: CPU will stay inside the loop for 200msec at most */
	int retry = 20000;

	while((inb(port + PSM_STATUS_PORT) & PSMS_AUX_BUFFER_FULL)
		!= PSMS_AUX_BUFFER_FULL) {
	    DELAY(10);
	    if (--retry < 0)
		return FALSE;
	}
	return TRUE;
}

static void
write_controller_command(int port, int c)
{
	wait_until_controller_is_really_idle(port);
	outb(port + PSM_COMMAND_PORT, c);
}

static void
write_controller_data(int port, int c)
{
	wait_until_controller_is_really_idle(port);
	outb(port + PSM_DATA_PORT, c);
}

static void
write_kbd_command(int port, int c)
{
	wait_until_controller_is_really_idle(port);
	outb(port + PSM_DATA_PORT, c);
}

static void
write_aux_command(int port, int c)
{
	write_controller_command(port, PSMC_WRITE_TO_AUX);
	write_controller_data(port, c);
}

static int
read_controller_data(int port)
{
	wait_while_controller_busy(port);
	if (!wait_for_data(port))
	    return -1;		/* timeout */
	return inb(port + PSM_DATA_PORT);
}

static int
read_aux_data(int port)
{
	wait_while_controller_busy(port);
	if (!wait_for_aux_data(port))
	    return -1;		/* timeout */
	return inb(port + PSM_DATA_PORT);
}

static void
empty_kbd_buffer(int port)
{
	int b;
#if PSM_DEBUG >= 2
	int c = 0;
#endif

	while((inb(port + PSM_STATUS_PORT) & PSMS_AUX_BUFFER_FULL)
		== PSMS_KBD_BUFFER_FULL) {
	    b = inb(port + PSM_DATA_PORT);
	    DELAY(10);
#if PSM_DEBUG >= 2
	    ++c;
#endif
	}
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: %d char read (empty_kbd_buffer)\n",c);
#endif
}

static void
empty_aux_buffer(int port)
{
	int b;
#if PSM_DEBUG >= 2
	int c = 0;
#endif

	while((inb(port + PSM_STATUS_PORT) & PSMS_AUX_BUFFER_FULL)
		== PSMS_AUX_BUFFER_FULL) {
	    b = inb(port + PSM_DATA_PORT);
	    DELAY(10);
#if PSM_DEBUG >= 2
	    ++c;
#endif
	}
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: %d char read (empty_aux_buffer)\n",c);
#endif
}

static void
empty_both_buffer(int port)
{
	int b;
#if PSM_DEBUG >= 2
	int c = 0;
#endif

	while(inb(port + PSM_STATUS_PORT) & PSMS_KBD_BUFFER_FULL) {
	    b = inb(port + PSM_DATA_PORT);
	    DELAY(10);
#if PSM_DEBUG >= 2
	    ++c;
#endif
	}
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: %d char read (empty_both_buffer)\n",c);
#endif
}

/* keyboard and mouse device control */

/* NOTE: enable the keyboard port before calling `reset_kbd()'. */
static int
reset_kbd(int port)
{
	int retry = PSM_MAXWAIT;
	int c;

	empty_both_buffer(port);
	write_kbd_command(port,PSMC_RESET_KBD);
	c = read_controller_data(port);
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: RESET_KBD return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	while(retry-- > 0) {
	    /* wait awhile, well, in fact we must wait quite looooooooooooong */
	    DELAY(PSM_RESETDELAY*1000);
	    c = read_controller_data(port);	/* RESET_DONE/RESET_FAIL */
	    if (c != -1) 	/* wait again if the controller is not ready */
		break;
	}
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: RESET_KBD status:%04x\n",c);
#endif
	if (c != PSM_RESET_DONE)
	    return FALSE;

	return TRUE;
}

static int
reset_aux_dev(int port)
{
	int retry = PSM_MAXWAIT;
	int c;

	empty_both_buffer(port);
	write_aux_command(port,PSMC_RESET_DEV);
	c = read_controller_data(port); 	/* read_aux_data()? */
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: RESET_AUX return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	while(retry-- > 0) {
	    /* wait awhile, well, quite looooooooooooong */
	    DELAY(PSM_RESETDELAY*1000);
	    c = read_aux_data(port);	/* RESET_DONE/RESET_FAIL */
	    if (c != -1) 	/* wait again if the controller is not ready */
		break;
	}
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: RESET_AUX status:%04x\n",c);
#endif
	if (c != PSM_RESET_DONE)	/* reset status */
	    return FALSE;

	c = read_aux_data(port);	/* device ID */
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: RESET_AUX ID:%04x\n",c);
#endif
	/* NOTE: we could check the device ID now, but leave it later... */

	return TRUE;
}

static int
test_controller(int port)
{
	int c;

	empty_both_buffer(port);
	write_controller_command(port,PSMC_DIAGNOSE);
	c = read_controller_data(port);	/* DIAG_DONE/DIAG_FAIL */
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: DIAGNOSE status:%04x\n",c);
#endif

	return (c == PSM_DIAG_DONE);
}

static int
test_kbd_port(int port)
{
	int c;

	empty_both_buffer(port);
	write_controller_command(port,PSMC_TEST_KBD_PORT);
	c = read_controller_data(port);
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: TEST_KBD_PORT status:%04x\n",c);
#endif

	return c;
}

static int
test_aux_port(int port)
{
	int c;

	empty_both_buffer(port);
	write_controller_command(port,PSMC_TEST_AUX_PORT);
	c = read_controller_data(port);
#if PSM_DEBUG >= 1
	log(LOG_DEBUG,"psm: TEST_AUX_PORT status:%04x\n",c);
#endif

	return c;
}

static int
enable_aux_dev(int port)
{
	int c;

	write_aux_command(port,PSMC_ENABLE_DEV);
	c = read_controller_data(port);	/* read_aux_data()? */
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: ENABLE_DEV return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	return TRUE;
}

static int
disable_aux_dev(int port)
{
	int c;

	write_aux_command(port,PSMC_DISABLE_DEV);
	c = read_controller_data(port);	/* read_aux_data()? */
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: DISABLE_DEV return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	return TRUE;
}

static int
get_mouse_status(int port,int *status)
{
	int c;

	empty_both_buffer(port);
	write_aux_command(port,PSMC_SEND_DEV_STATUS);
	c = read_controller_data(port); 	/* read_aux_data()? */
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SEND_AUX_STATUS return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	status[0] = read_aux_data(port);
	status[1] = read_aux_data(port);
	status[2] = read_aux_data(port);

	return TRUE;
}

static int
get_aux_id(int port)
{
	int id;
	int c;

	empty_both_buffer(port);
	write_aux_command(port,PSMC_SEND_DEV_ID);
	/* 10ms delay */
	DELAY(10000);

	c = read_controller_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SEND_DEV_ID return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return -1;

	id = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: device ID: %04x\n",id);
#endif

	return id;
}

static int
set_mouse_sampling_rate(int port,int rate)
{
	int c;

	write_aux_command(port,PSMC_SET_SAMPLING_RATE);
	c = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SET_SAMPLING_RATE return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	write_aux_command(port,rate);
	c = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SET_SAMPLING_RATE data sent:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	return TRUE;
}

static int
set_mouse_scaling(int port)
{
	int c;

	write_aux_command(port,PSMC_SET_SCALING11);
	c = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SET_SCALING11 return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	return TRUE;
}

static int
set_mouse_resolution(int port,int res)
{
	int c;

	write_aux_command(port,PSMC_SET_RESOLUTION);
	c = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SET_RESOLUTION return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	write_aux_command(port,res);
	c = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SET_RESOLUTION data sent:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	return TRUE;
}

/* NOTE: once `set_mouse_mode()' is called, the mouse device must be
   re-enabled by calling `enable_aux_dev()' */
static int
set_mouse_mode(int port)
{
	int c;

	write_aux_command(port,PSMC_SET_STREAM_MODE);
	c = read_aux_data(port);
#if PSM_DEBUG >= 2
	log(LOG_DEBUG,"psm: SET_STREAM_MODE return code:%04x\n",c);
#endif
	if (c != PSM_ACK)
	    return FALSE;

	return TRUE;
}

static int
get_mouse_buttons(int port)
{
	int c = 2;		/* assume two buttons by default */
	int status[3];

	/* NOTE: a special sequence to obtain Logitech-Mouse-specific
	   information: set resolution to 1 count/mm, set scaling to 1:1,
	   set scaling to 1:1, set scaling to 1:1. Then the second
	   byte of the mouse status bytes is the number of available 
	   buttons. */
	if (!set_mouse_resolution(port,PSMD_RESOLUTION_1))
	    return c;
	if (set_mouse_scaling(port) && set_mouse_scaling(port)
		&& set_mouse_scaling(port) && get_mouse_status(port,status)) {
#if PSM_DEBUG >= 1
	    log(LOG_DEBUG,"psm: status %02x %02x %02x (get_mouse_buttons)\n",
		status[0],status[1],status[2]);
#endif
	    if (status[1] == 3)	
		return 3;
	}
	return c;
}

/* FIXME: someday, I will get the list of valid pointing devices and
   their IDs... */
static int
is_a_mouse(int id)
{
	static int valid_ids[] = {
		PSM_MOUSE_ID,		/* mouse */
		PSM_BALLPOINT_ID,	/* ballpoint device */
		-1			/* end of table */
	};
	/*
	int i;

	for(i = 0; valid_ids[i] >= 0; ++i) {
	    if (valid_ids[i] == id)
		return TRUE;
	}
	return FALSE;
	*/
	return TRUE;
}

static void
recover_from_error(int port)
{
	/* discard anything left in the output buffer */
	empty_both_buffer(port);

	/* NOTE: PSMC_RESET_KBD may not restore the communication between
	   the keyboard and the controller. */
	/* reset_kbd(port); */
	/* NOTE: somehow diagnostic and keyboard port test commands bring
	   the keyboard back. */
	test_controller(port);
	test_kbd_port(port);
}

static void
restore_controller(int port,int command_byte)
{
	write_controller_command(port,PSMC_SET_COMMAND_BYTE);
	write_controller_data(port,command_byte);
	wait_while_controller_busy(port);
}

/* psm driver entry points */

static int
psmprobe(struct isa_device *dvp)
{
	int unit = dvp->id_unit;
	int ioport = dvp->id_iobase;
	int test_status;
#if PSM_DEBUG >= 1
	int stat[3];
#endif

	/* FIXME: shouldn't we check `unit < NPSM'? */
	psm_dev[unit].addr = 0;

	/* FIXME: the keyboard interrupt should be disabled while
	   probing a mouse? */

	/* NOTE: two bits in the command byte controls the operation of
	   the aux port (mouse port): the aux port disable bit (bit 5) and
	   the aux port interrupt (IRQ 12) enable bit (bit 2).
	   When this probe routine is called, there are following possibilities
	   about the presence of the aux port and the PS/2 mouse.

	   Case 1: aux port disabled (bit 5:1), aux int. disabled (bit 2:0)
	   The aux port most certainly exists. A device may or may not be
	   connected to the port. No driver is probably installed yet.

	   Case 2: aux port enabled (bit 5:0), aux int. disabled (bit 2:0)
	   Three possibile situations here:

	   Case 2a: 
	   The aux port does not exist, therefore, is not explicitly disabled.
	   Case 2b:
	   The aux port exists. A device and a driver may exist,
	   using the device in the polling(remote) mode.
	   Case 2c:
	   The aux port exists. A device may exist, but someone who knows
	   nothing about the aux port has set the command byte this way
	   (this is the case with `syscons').

	   Case 3: aux port disabled (bit 5:1), aux int. enabled (bit 2:1)
	   The aux port exists, but someone is controlloing the device and
	   temporalily disabled the port.

	   Case 4: aux port enabled (bit 5:0), aux int. enabled (bit 2:1)
	   The aux port exists, a device is attached to the port, and
	   someone is controlling the device. Some BIOS set the bits this
	   way after boot.

	   All in all, it is no use examing the bits for detecting
	   the presence of the port and the mouse device.
	*/

	/* save the current command byte; it will be used later */
	write_controller_command(ioport,PSMC_GET_COMMAND_BYTE);
	psm_dev[unit].command_byte = read_controller_data(ioport);
#if PSM_DEBUG >= 1
	printf("psm%d: current command byte:%04x\n",
		unit,psm_dev[unit].command_byte);
#endif
	if (psm_dev[unit].command_byte == -1) {
	    printf("psm%d: unable to get the current command byte value.\n",
		unit);
	    return (0);
	}

	/* disable the keyboard port while probing the aux port, which
	   must be enabled during this routine */
	write_controller_command(ioport,PSMC_DISABLE_KBD_PORT);
	write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	write_controller_data(ioport,
		(psm_dev[unit].command_byte 
			& ~(PSM_KBD_CONTROL_BITS | PSM_AUX_CONTROL_BITS))
		| PSM_DISABLE_KBD_PORT | PSM_DISABLE_KBD_INT
		| PSM_ENABLE_AUX_PORT | PSM_DISABLE_AUX_INT);
	wait_while_controller_busy(ioport);

	/* NOTE: `test_aux_port()' is designed to return with zero
	   if the aux port exists and is functioning. However, some
	   controllers appears to respond with zero even when the aux port
	   doesn't exist. (It may be that this is only the case when the
	   controller DOES have the aux port but the port is not wired
	   on the motherboard.) The keyboard controllers without the port,
	   such as the original AT, are supporsed to return with
	   an error code or simply time out. In any case, we have to
	   continue probing the port even when the controller passes 
	   this test.
	*/
	test_status = test_aux_port(ioport);
	switch(test_status) {
	    case 0:	/* no error */
		break;
	    case -1: 	/* time out */
	    default: 	/* error */
		recover_from_error(ioport);
		restore_controller(ioport,psm_dev[unit].command_byte);
		return (0);
	}

	/* NOTE: some controllers appears to hang the `keyboard' when
	   the aux port doesn't exist and `PSMC_RESET_DEV' is issued. */
	if (!reset_aux_dev(ioport)) {
	    recover_from_error(ioport);
	    restore_controller(ioport,psm_dev[unit].command_byte);
	    return (0);
	}

	/* both the aux port and the aux device is functioning, see
	   if the device can be enabled.
	   NOTE: when enabled, the device will start sending data;
	   we shall immediately disable the device once we know 
	   the device can be enabled. */
	if (!enable_aux_dev(ioport)) {
	    restore_controller(ioport,psm_dev[unit].command_byte);
	    return (0);
	}
	if (!disable_aux_dev(ioport)) {
	    restore_controller(ioport,psm_dev[unit].command_byte);
	    return (0);
	}
	empty_both_buffer(ioport);	/* remove stray data if any */

	/* verify the device is a mouse */
	psm_dev[unit].id = get_aux_id(ioport);
	if (!is_a_mouse(psm_dev[unit].id)) {
	    restore_controller(ioport,psm_dev[unit].command_byte);
	    return (0);
	}

	/* # of buttons */
	psm_dev[unit].buttons = get_mouse_buttons(ioport);

	/* set mouse parameters */
	/* FIXME: I don't know if these parameters are reasonable */
	set_mouse_resolution(ioport,PSMD_RESOLUTION_8); /* 8 count/mm */
	set_mouse_sampling_rate(ioport,100);		/* 100/sec */
	set_mouse_scaling(ioport);			/* 1:1 scaling */
	set_mouse_mode(ioport);				/* stream mode */

#if PSM_DEBUG >= 1
	/* just check the status of the mouse */
	get_mouse_status(ioport,stat);
	log(LOG_DEBUG,"psm%d: status %02x %02x %02x\n",
	    unit,stat[0],stat[1],stat[2]);
#endif

	/* disable the aux port for now... */
	/* WARNING: we save the controller command byte and use it later
	   during `psmopen()' and `psmclose()'. This will be OK, so long
	   as the keyboard/console device driver won't change the command
	   byte in the course of its operation (this is the case with
	   `syscons'). If not,... */
	psm_dev[unit].command_byte &= ~PSM_AUX_CONTROL_BITS;
	write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	write_controller_data(ioport,psm_dev[unit].command_byte
		| (PSM_DISABLE_AUX_PORT | PSM_DISABLE_AUX_INT));
	wait_while_controller_busy(ioport);

	/* done */
	return (IO_PSMSIZE);
}

static int
psmattach(struct isa_device *dvp)
{
	int unit = dvp->id_unit;
	int ioport = dvp->id_iobase;
	struct psm_softc *sc = &psm_softc[unit];

	/* Save I/O base address */
	psm_dev[unit].addr = ioport;

	/* Setup initial state */
	sc->state = 0;

	/* Done */
#ifdef	DEVFS
	sc->devfs_token = 
		devfs_add_devswf(&psm_cdevsw, unit << 1, DV_CHR, 0, 0, 0666, 
				 "psm%d", unit);
	sc->n_devfs_token = 
		devfs_add_devswf(&psm_cdevsw, (unit<<1)+1, DV_CHR,0, 0, 0666,
				 "npsm%d", unit);
#endif

	printf("psm%d: device ID %d, %d buttons?\n",
		unit,psm_dev[unit].id,psm_dev[unit].buttons);

	return (1);
	/* return (0); XXX eh? usually 1 indicates success */
}

static int
psmopen(dev_t dev, int flag, int fmt, struct proc *p)
{
	int unit = PSMUNIT(dev);
	int ioport;
	struct psm_softc *sc;
#if PSM_DEBUG >= 2
	int stat[3];
#endif

	/* Validate unit number */
	if (unit >= NPSM)
		return (ENXIO);

	/* Get device data */
	sc = &psm_softc[unit];
	ioport = psm_dev[unit].addr;

	/* If device does not exist */
	if (ioport == 0)
		return (ENXIO);

	/* Disallow multiple opens */
	if (sc->state & PSM_OPEN)
		return (EBUSY);

	/* Initialize state */
	sc->state |= PSM_OPEN;
	sc->rsel.si_flags = 0;
	sc->rsel.si_pid = 0;
	sc->status = 0;
	sc->button = 0;
	sc->x = 0;
	sc->y = 0;

	/* initialize a ring buffer */
	sc->inq.count = sc->inq.first = sc->inq.last = 0;

	/* enable the aux port and temporalily disable the keyboard */
	write_controller_command(ioport,PSMC_DISABLE_KBD_PORT);
	write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	write_controller_data(ioport,
		(psm_dev[unit].command_byte & ~PSM_KBD_CONTROL_BITS)
		| (PSM_DISABLE_KBD_PORT | PSM_DISABLE_KBD_INT)
		| (PSM_ENABLE_AUX_PORT | PSM_DISABLE_AUX_INT));
	wait_while_controller_busy(ioport);

	/* enable the mouse device */
	if (!enable_aux_dev(ioport)) {
	    write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	    write_controller_data(ioport,psm_dev[unit].command_byte
		| (PSM_DISABLE_AUX_PORT | PSM_DISABLE_AUX_INT));
	    wait_while_controller_busy(ioport);
	    log(LOG_ERR,"psm%d: unable to enable the pointing device.\n",unit);
	    return (EIO);
	}
#if PSM_DEBUG >= 2
	get_mouse_status(ioport,stat);
	log(LOG_DEBUG,"psm%d: status %02x %02x %02x\n",
	    unit,stat[0],stat[1],stat[2]);
#endif

	/* enable the aux port and interrupt */
	write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	write_controller_data(ioport,psm_dev[unit].command_byte
		| PSM_ENABLE_AUX_PORT | PSM_ENABLE_AUX_INT);
	wait_while_controller_busy(ioport);

	/* done */
	return (0);
}

static int
psmclose(dev_t dev, int flag, int fmt, struct proc *p)
{
	int unit = PSMUNIT(dev);
	int ioport = psm_dev[unit].addr;
	struct psm_softc *sc = &psm_softc[unit];

	/* disable the aux interrupt */
	write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	write_controller_data(ioport,psm_dev[unit].command_byte
		| (PSM_ENABLE_AUX_PORT | PSM_DISABLE_AUX_INT));
	wait_while_controller_busy(ioport);

	/* remove anything left in the output buffer */
	empty_aux_buffer(ioport);	

	/* disable the aux device, port and interrupt */
	disable_aux_dev(ioport);
	write_controller_command(ioport,PSMC_SET_COMMAND_BYTE);
	write_controller_data(ioport,psm_dev[unit].command_byte
		| (PSM_DISABLE_AUX_PORT | PSM_DISABLE_AUX_INT));
	wait_while_controller_busy(ioport);

	/* remove anything left in the output buffer */
	empty_aux_buffer(ioport);	

	/* Complete the close */
	sc->state &= ~PSM_OPEN;

	/* close is almost always successful */
	return (0);
}

static int
psmread(dev_t dev, struct uio *uio, int flag)
{
	int s;
	int error = 0;	/* keep compiler quiet, even though initialisation
			   is unnecessary */
	unsigned length;
	struct psm_softc *sc;
	unsigned char buffer[PSM_CHUNK];

	/* Get device information */
	sc = &psm_softc[PSMUNIT(dev)];

	/* Block until mouse activity occured */
	s = spltty();
	while (sc->inq.count == 0) {
		if (minor(dev) & 0x1) {
			splx(s);
			return (EWOULDBLOCK);
		}
		sc->state |= PSM_ASLP;
		error = tsleep((caddr_t)sc, PZERO | PCATCH, "psmrea", 0);
		if (error != 0) {
			splx(s);
			return (error);
		}
	}

	/* Transfer as many chunks as possible */
	while (sc->inq.count > 0 && uio->uio_resid > 0) {
		length = min(sc->inq.count, uio->uio_resid);
		if (length > sizeof(buffer))
			length = sizeof(buffer);

		/* Remove a small chunk from input queue */
		if (sc->inq.first + length >= PSM_BSIZE) {
			bcopy(&sc->inq.queue[sc->inq.first],
		 	      buffer, PSM_BSIZE - sc->inq.first);
			bcopy(sc->inq.queue, &buffer[PSM_BSIZE - sc->inq.first],
			      length - (PSM_BSIZE - sc->inq.first));
		}
		else
			bcopy(&sc->inq.queue[sc->inq.first], buffer, length);

		sc->inq.first = (sc->inq.first + length) % PSM_BSIZE;
		sc->inq.count -= length;

		/* Copy data to user process */
		error = uiomove(buffer, length, uio);
		if (error)
			break;
	}
	sc->x = sc->y = 0;

	/* Allow interrupts again */
	splx(s);
	return (error);
}

static int
psmioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p)
{
	struct psm_softc *sc;
	struct mouseinfo info;
	int s, error;

	/* Get device information */
	sc = &psm_softc[PSMUNIT(dev)];

	/* Perform IOCTL command */
	switch (cmd) {

	case MOUSEIOCREAD:
		/* Don't modify info while calculating */
		s = spltty();

		/* Build mouse status octet */
		info.status = sc->status;
		if (sc->x || sc->y)
			info.status |= MOVEMENT;

		/* Encode X and Y motion as good as we can */
		if (sc->x > 127)
			info.xmotion = 127;
		else if (sc->x < -128)
			info.xmotion = -128;
		else
			info.xmotion = sc->x;

		if (sc->y > 127)
			info.ymotion = 127;
		else if (sc->y < -128)
			info.ymotion = -128;
		else
			info.ymotion = sc->y;

		/* Reset historical information */
		sc->x = 0;
		sc->y = 0;
		sc->status &= ~BUTCHNGMASK;

		/* Allow interrupts and copy result buffer */
		splx(s);
		error = copyout(&info, addr, sizeof(struct mouseinfo));
		break;

	default:
		error = EINVAL;
		break;
	}

	/* Return error code */
	return (error);
}

void
psmintr(int unit)
{
	int ioport = psm_dev[unit].addr;
	struct psm_softc *sc = &psm_softc[unit];

	/* is this really for us? */
	if ((inb(ioport + PSM_STATUS_PORT) & PSMS_AUX_BUFFER_FULL)
		!= PSMS_AUX_BUFFER_FULL) 
	    return;

	/* read a byte */
	sc->inq.queue[sc->inq.last++ % PSM_BSIZE] = inb(ioport + PSM_DATA_PORT);
	sc->inq.count++;

	if (sc->state & PSM_ASLP) {
		sc->state &= ~PSM_ASLP;
		wakeup((caddr_t)sc);
	}
	selwakeup(&sc->rsel);
}

static int
psmselect(dev_t dev, int rw, struct proc *p)
{
	struct psm_softc *sc = &psm_softc[PSMUNIT(dev)];
	int s, ret;

	/* Silly to select for output */
	if (rw == FWRITE)
		return (0);

	/* Return true if a mouse event available */
	s = spltty();
	if (sc->inq.count) {
		ret = 1;
	} else {
		selrecord(p, &sc->rsel);
		ret = 0;
	}
	splx(s);

	return (ret);
}

static int psm_devsw_installed = FALSE;

static void
psm_drvinit(void *unused)
{
	dev_t dev;

	if (!psm_devsw_installed) {
		dev = makedev(CDEV_MAJOR, 0);
		cdevsw_add(&dev,&psm_cdevsw, NULL);
		psm_devsw_installed = TRUE;
    	}
}

SYSINIT(psmdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE + CDEV_MAJOR,psm_drvinit,NULL)

#endif /* NPSM > 0 */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199610241843.MAA06370>