OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [drivers/] [s390/] [char/] [hwc_rw.c] - Rev 1765

Compare with Previous | Blame | View Log

/*
 *  drivers/s390/char/hwc_rw.c
 *     driver: reading from and writing to system console on S/390 via HWC
 *
 *  S390 version
 *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Martin Peschke <mpeschke@de.ibm.com>
 *
 * 
 *
 * 
 * 
 * 
 */
 
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/bootmem.h>
#include <linux/module.h>
 
#include <asm/ebcdic.h>
#include <asm/uaccess.h>
#include <asm/types.h>
#include <asm/bitops.h>
#include <asm/setup.h>
#include <asm/page.h>
#include <asm/s390_ext.h>
#include <asm/irq.h>
 
#ifndef MIN
#define MIN(a,b) (((a<b) ? a : b))
#endif
 
extern void ctrl_alt_del (void);
 
#define HWC_RW_PRINT_HEADER "hwc low level driver: "
 
#define  USE_VM_DETECTION
 
#define  DEFAULT_CASE_DELIMITER '%'
 
#undef DUMP_HWC_INIT_ERROR
 
#undef DUMP_HWC_WRITE_ERROR
 
#undef DUMP_HWC_WRITE_LIST_ERROR
 
#undef DUMP_HWC_READ_ERROR
 
#undef DUMP_HWCB_INPUT
 
#undef BUFFER_STRESS_TEST
 
typedef struct {
	unsigned char *next;
	unsigned short int mto_char_sum;
	unsigned char mto_number;
	unsigned char times_lost;
	unsigned short int mto_number_lost;
	unsigned long int mto_char_sum_lost;
} __attribute__ ((packed)) 
 
hwcb_list_t;
 
#define MAX_HWCB_ROOM (PAGE_SIZE - sizeof(hwcb_list_t))
 
#define MAX_MESSAGE_SIZE (MAX_HWCB_ROOM - sizeof(write_hwcb_t))
 
#define BUF_HWCB hwc_data.hwcb_list_tail
#define OUT_HWCB hwc_data.hwcb_list_head
#define ALL_HWCB_MTO hwc_data.mto_number
#define ALL_HWCB_CHAR hwc_data.mto_char_sum
 
#define _LIST(hwcb) ((hwcb_list_t*)(&(hwcb)[PAGE_SIZE-sizeof(hwcb_list_t)]))
 
#define _HWCB_CHAR(hwcb) (_LIST(hwcb)->mto_char_sum)
 
#define _HWCB_MTO(hwcb) (_LIST(hwcb)->mto_number)
 
#define _HWCB_CHAR_LOST(hwcb) (_LIST(hwcb)->mto_char_sum_lost)
 
#define _HWCB_MTO_LOST(hwcb) (_LIST(hwcb)->mto_number_lost)
 
#define _HWCB_TIMES_LOST(hwcb) (_LIST(hwcb)->times_lost)
 
#define _HWCB_NEXT(hwcb) (_LIST(hwcb)->next)
 
#define BUF_HWCB_CHAR _HWCB_CHAR(BUF_HWCB)
 
#define BUF_HWCB_MTO _HWCB_MTO(BUF_HWCB)
 
#define BUF_HWCB_NEXT _HWCB_NEXT(BUF_HWCB)
 
#define OUT_HWCB_CHAR _HWCB_CHAR(OUT_HWCB)
 
#define OUT_HWCB_MTO _HWCB_MTO(OUT_HWCB)
 
#define OUT_HWCB_NEXT _HWCB_NEXT(OUT_HWCB)
 
#define BUF_HWCB_CHAR_LOST _HWCB_CHAR_LOST(BUF_HWCB)
 
#define BUF_HWCB_MTO_LOST _HWCB_MTO_LOST(BUF_HWCB)
 
#define OUT_HWCB_CHAR_LOST _HWCB_CHAR_LOST(OUT_HWCB)
 
#define OUT_HWCB_MTO_LOST _HWCB_MTO_LOST(OUT_HWCB)
 
#define BUF_HWCB_TIMES_LOST _HWCB_TIMES_LOST(BUF_HWCB)
 
#include  "hwc.h"
 
#define __HWC_RW_C__
#include "hwc_rw.h"
#undef __HWC_RW_C__
 
static unsigned char _obuf[MAX_HWCB_ROOM];
 
static unsigned char
 _page[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE)));
 
typedef unsigned long kmem_pages_t;
 
#define MAX_KMEM_PAGES (sizeof(kmem_pages_t) << 3)
 
#define HWC_WTIMER_RUNS	1
#define HWC_FLUSH		2
#define HWC_INIT		4
#define HWC_BROKEN		8
#define HWC_INTERRUPT		16
#define HWC_PTIMER_RUNS	32
 
static struct {
 
	hwc_ioctls_t ioctls;
 
	hwc_ioctls_t init_ioctls;
 
	unsigned char *hwcb_list_head;
 
	unsigned char *hwcb_list_tail;
 
	unsigned short int mto_number;
 
	unsigned int mto_char_sum;
 
	unsigned char hwcb_count;
 
	unsigned long kmem_start;
 
	unsigned long kmem_end;
 
	kmem_pages_t kmem_pages;
 
	unsigned char *obuf;
 
	unsigned short int obuf_cursor;
 
	unsigned short int obuf_count;
 
	unsigned short int obuf_start;
 
	unsigned char *page;
 
	u32 current_servc;
 
	unsigned char *current_hwcb;
 
	unsigned char write_nonprio:1;
	unsigned char write_prio:1;
	unsigned char read_nonprio:1;
	unsigned char read_prio:1;
	unsigned char read_statechange:1;
	unsigned char sig_quiesce:1;
 
	unsigned char flags;
 
	hwc_high_level_calls_t *calls;
 
	hwc_request_t *request;
 
	spinlock_t lock;
 
	struct timer_list write_timer;
 
	struct timer_list poll_timer;
} hwc_data =
{
	{
	},
	{
		8,
		    0,
		    80,
		    1,
		    MAX_KMEM_PAGES,
		    MAX_KMEM_PAGES,
 
		    0,
 
		    0x6c
 
	},
	    NULL,
	    NULL,
	    0,
	    0,
	    0,
	    0,
	    0,
	    0,
	    _obuf,
	    0,
	    0,
	    0,
	    _page,
	    0,
	    NULL,
	    0,
	    0,
	    0,
	    0,
	    0,
	    0,
	    0,
	    NULL,
	    NULL
 
};
 
static unsigned long cr0 __attribute__ ((aligned (8)));
static unsigned long cr0_save __attribute__ ((aligned (8)));
static unsigned char psw_mask __attribute__ ((aligned (8)));
 
static ext_int_info_t ext_int_info_hwc;
 
#define DELAYED_WRITE 0
#define IMMEDIATE_WRITE 1
 
static signed int do_hwc_write (int from_user, unsigned char *,
				unsigned int,
				unsigned char);
 
unsigned char hwc_ip_buf[512];
 
static asmlinkage int 
internal_print (char write_time, char *fmt,...)
{
	va_list args;
	int i;
 
	va_start (args, fmt);
	i = vsprintf (hwc_ip_buf, fmt, args);
	va_end (args);
	return do_hwc_write (0, hwc_ip_buf, i, write_time);
}
 
int 
hwc_printk (const char *fmt,...)
{
	va_list args;
	int i;
	unsigned long flags;
	int retval;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	i = vsprintf (hwc_ip_buf, fmt, args);
	va_end (args);
	retval = do_hwc_write (0, hwc_ip_buf, i, IMMEDIATE_WRITE);
 
	spin_unlock_irqrestore (&hwc_data.lock, flags);
 
	return retval;
}
 
#ifdef DUMP_HWCB_INPUT
 
static void 
dump_storage_area (unsigned char *area, unsigned short int count)
{
	unsigned short int index;
	ioctl_nl_t old_final_nl;
 
	if (!area || !count)
		return;
 
	old_final_nl = hwc_data.ioctls.final_nl;
	hwc_data.ioctls.final_nl = 1;
 
	internal_print (DELAYED_WRITE, "\n%8x   ", area);
 
	for (index = 0; index < count; index++) {
 
		if (area[index] <= 0xF)
			internal_print (DELAYED_WRITE, "0%x", area[index]);
		else
			internal_print (DELAYED_WRITE, "%x", area[index]);
 
		if ((index & 0xF) == 0xF)
			internal_print (DELAYED_WRITE, "\n%8x   ",
					&area[index + 1]);
		else if ((index & 3) == 3)
			internal_print (DELAYED_WRITE, " ");
	}
 
	internal_print (IMMEDIATE_WRITE, "\n");
 
	hwc_data.ioctls.final_nl = old_final_nl;
}
#endif
 
static inline u32 
service_call (
		     u32 hwc_command_word,
		     unsigned char hwcb[])
{
	unsigned int condition_code = 1;
 
	__asm__ __volatile__ ("L 1, 0(%0) \n\t"
			      "LRA 2, 0(%1) \n\t"
			      ".long 0xB2200012 \n\t"
			      :
			      :"a" (&hwc_command_word), "a" (hwcb)
			      :"1", "2", "memory");
 
	__asm__ __volatile__ ("IPM %0 \n\t"
			      "SRL %0, 28 \n\t"
			      :"=r" (condition_code));
 
	return condition_code;
}
 
static inline unsigned long 
hwc_ext_int_param (void)
{
	u32 param;
 
	__asm__ __volatile__ ("L %0,128\n\t"
			      :"=r" (param));
 
	return (unsigned long) param;
}
 
static int 
prepare_write_hwcb (void)
{
	write_hwcb_t *hwcb;
 
	if (!BUF_HWCB)
		return -ENOMEM;
 
	BUF_HWCB_MTO = 0;
	BUF_HWCB_CHAR = 0;
 
	hwcb = (write_hwcb_t *) BUF_HWCB;
 
	memcpy (hwcb, &write_hwcb_template, sizeof (write_hwcb_t));
 
	return 0;
}
 
static int 
sane_write_hwcb (void)
{
	unsigned short int lost_msg;
	unsigned int lost_char;
	unsigned char lost_hwcb;
	unsigned char *bad_addr;
	unsigned long page;
	int page_nr;
 
	if (!OUT_HWCB)
		return -ENOMEM;
 
	if ((unsigned long) OUT_HWCB & 0xFFF) {
 
		bad_addr = OUT_HWCB;
 
#ifdef DUMP_HWC_WRITE_LIST_ERROR
		__asm__ ("LHI 1,0xe30\n\t"
			 "LRA 2,0(%0) \n\t"
			 "J .+0 \n\t"
	      :
	      :	 "a" (bad_addr)
	      :	 "1", "2");
#endif
 
		hwc_data.kmem_pages = 0;
		if ((unsigned long) BUF_HWCB & 0xFFF) {
 
			lost_hwcb = hwc_data.hwcb_count;
			lost_msg = ALL_HWCB_MTO;
			lost_char = ALL_HWCB_CHAR;
 
			OUT_HWCB = NULL;
			BUF_HWCB = NULL;
			ALL_HWCB_MTO = 0;
			ALL_HWCB_CHAR = 0;
			hwc_data.hwcb_count = 0;
		} else {
 
			lost_hwcb = hwc_data.hwcb_count - 1;
			lost_msg = ALL_HWCB_MTO - BUF_HWCB_MTO;
			lost_char = ALL_HWCB_CHAR - BUF_HWCB_CHAR;
			OUT_HWCB = BUF_HWCB;
			ALL_HWCB_MTO = BUF_HWCB_MTO;
			ALL_HWCB_CHAR = BUF_HWCB_CHAR;
			hwc_data.hwcb_count = 1;
			page = (unsigned long) BUF_HWCB;
 
			if (page >= hwc_data.kmem_start &&
			    page <= hwc_data.kmem_end) {
 
				page_nr = (int)
				    ((page - hwc_data.kmem_start) >> 12);
				set_bit (page_nr, &hwc_data.kmem_pages);
			}
		}
 
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
		       "found invalid HWCB at address 0x%lx. List corrupted. "
			   "Lost %i HWCBs with %i characters within up to %i "
			   "messages. Saved %i HWCB with last %i characters i"
				       "within up to %i messages.\n",
				       (unsigned long) bad_addr,
				       lost_hwcb, lost_char, lost_msg,
				       hwc_data.hwcb_count,
				       ALL_HWCB_CHAR, ALL_HWCB_MTO);
	}
	return 0;
}
 
static int 
reuse_write_hwcb (void)
{
	int retval;
 
	if (hwc_data.hwcb_count < 2)
#ifdef DUMP_HWC_WRITE_LIST_ERROR
		__asm__ ("LHI 1,0xe31\n\t"
			 "LRA 2,0(%0)\n\t"
			 "LRA 3,0(%1)\n\t"
			 "J .+0 \n\t"
	      :
	      :	 "a" (BUF_HWCB), "a" (OUT_HWCB)
	      :	 "1", "2", "3");
#else
		return -EPERM;
#endif
 
	if (hwc_data.current_hwcb == OUT_HWCB) {
 
		if (hwc_data.hwcb_count > 2) {
 
			BUF_HWCB_NEXT = OUT_HWCB_NEXT;
 
			BUF_HWCB = OUT_HWCB_NEXT;
 
			OUT_HWCB_NEXT = BUF_HWCB_NEXT;
 
			BUF_HWCB_NEXT = NULL;
		}
	} else {
 
		BUF_HWCB_NEXT = OUT_HWCB;
 
		BUF_HWCB = OUT_HWCB;
 
		OUT_HWCB = OUT_HWCB_NEXT;
 
		BUF_HWCB_NEXT = NULL;
	}
 
	BUF_HWCB_TIMES_LOST += 1;
	BUF_HWCB_CHAR_LOST += BUF_HWCB_CHAR;
	BUF_HWCB_MTO_LOST += BUF_HWCB_MTO;
	ALL_HWCB_MTO -= BUF_HWCB_MTO;
	ALL_HWCB_CHAR -= BUF_HWCB_CHAR;
 
	retval = prepare_write_hwcb ();
 
	if (hwc_data.hwcb_count == hwc_data.ioctls.max_hwcb)
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "reached my own limit of "
			    "allowed buffer space for output (%i HWCBs = %li "
			  "bytes), skipped content of oldest HWCB %i time(s) "
				       "(%i lines = %i characters)\n",
				       hwc_data.ioctls.max_hwcb,
				       hwc_data.ioctls.max_hwcb * PAGE_SIZE,
				       BUF_HWCB_TIMES_LOST,
				       BUF_HWCB_MTO_LOST,
				       BUF_HWCB_CHAR_LOST);
	else
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "page allocation failed, "
			   "could not expand buffer for output (currently in "
			     "use: %i HWCBs = %li bytes), skipped content of "
			"oldest HWCB %i time(s) (%i lines = %i characters)\n",
				       hwc_data.hwcb_count,
				       hwc_data.hwcb_count * PAGE_SIZE,
				       BUF_HWCB_TIMES_LOST,
				       BUF_HWCB_MTO_LOST,
				       BUF_HWCB_CHAR_LOST);
 
	return retval;
}
 
static int 
allocate_write_hwcb (void)
{
	unsigned char *page;
	int page_nr;
 
	if (hwc_data.hwcb_count == hwc_data.ioctls.max_hwcb)
		return -ENOMEM;
 
	page_nr = find_first_zero_bit (&hwc_data.kmem_pages, MAX_KMEM_PAGES);
	if (page_nr < hwc_data.ioctls.kmem_hwcb) {
 
		page = (unsigned char *)
		    (hwc_data.kmem_start + (page_nr << 12));
		set_bit (page_nr, &hwc_data.kmem_pages);
	} else
		page = (unsigned char *) __get_free_page (GFP_ATOMIC | GFP_DMA);
 
	if (!page)
		return -ENOMEM;
 
	if (!OUT_HWCB)
		OUT_HWCB = page;
	else
		BUF_HWCB_NEXT = page;
 
	BUF_HWCB = page;
 
	BUF_HWCB_NEXT = NULL;
 
	hwc_data.hwcb_count++;
 
	prepare_write_hwcb ();
 
	BUF_HWCB_TIMES_LOST = 0;
	BUF_HWCB_MTO_LOST = 0;
	BUF_HWCB_CHAR_LOST = 0;
 
#ifdef BUFFER_STRESS_TEST
 
	internal_print (
			       DELAYED_WRITE,
			       "*** " HWC_RW_PRINT_HEADER
			    "page #%i at 0x%x for buffering allocated. ***\n",
			       hwc_data.hwcb_count, page);
 
#endif
 
	return 0;
}
 
static int 
release_write_hwcb (void)
{
	unsigned long page;
	int page_nr;
 
	if (!hwc_data.hwcb_count)
		return -ENODATA;
 
	if (hwc_data.hwcb_count == 1) {
 
		prepare_write_hwcb ();
 
		ALL_HWCB_CHAR = 0;
		ALL_HWCB_MTO = 0;
		BUF_HWCB_TIMES_LOST = 0;
		BUF_HWCB_MTO_LOST = 0;
		BUF_HWCB_CHAR_LOST = 0;
	} else {
		page = (unsigned long) OUT_HWCB;
 
		ALL_HWCB_MTO -= OUT_HWCB_MTO;
		ALL_HWCB_CHAR -= OUT_HWCB_CHAR;
		hwc_data.hwcb_count--;
 
		OUT_HWCB = OUT_HWCB_NEXT;
 
		if (page >= hwc_data.kmem_start &&
		    page <= hwc_data.kmem_end) {
			/*memset((void *) page, 0, PAGE_SIZE); */
 
			page_nr = (int) ((page - hwc_data.kmem_start) >> 12);
			clear_bit (page_nr, &hwc_data.kmem_pages);
		} else
			free_page (page);
#ifdef BUFFER_STRESS_TEST
 
		internal_print (
				       DELAYED_WRITE,
				       "*** " HWC_RW_PRINT_HEADER
			 "page at 0x%x released, %i pages still in use ***\n",
				       page, hwc_data.hwcb_count);
 
#endif
	}
	return 0;
}
 
static int 
add_mto (
		unsigned char *message,
		unsigned short int count)
{
	unsigned short int mto_size;
	write_hwcb_t *hwcb;
	mto_t *mto;
	void *dest;
 
	if (!BUF_HWCB)
		return -ENOMEM;
 
	if (BUF_HWCB == hwc_data.current_hwcb)
		return -ENOMEM;
 
	mto_size = sizeof (mto_t) + count;
 
	hwcb = (write_hwcb_t *) BUF_HWCB;
 
	if ((MAX_HWCB_ROOM - hwcb->length) < mto_size)
		return -ENOMEM;
 
	mto = (mto_t *) (((unsigned long) hwcb) + hwcb->length);
 
	memcpy (mto, &mto_template, sizeof (mto_t));
 
	dest = (void *) (((unsigned long) mto) + sizeof (mto_t));
 
	memcpy (dest, message, count);
 
	mto->length += count;
 
	hwcb->length += mto_size;
	hwcb->msgbuf.length += mto_size;
	hwcb->msgbuf.mdb.length += mto_size;
 
	BUF_HWCB_MTO++;
	ALL_HWCB_MTO++;
	BUF_HWCB_CHAR += count;
	ALL_HWCB_CHAR += count;
 
	return count;
}
 
static int write_event_data_1 (void);
 
static void 
do_poll_hwc (unsigned long data)
{
	unsigned long flags;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	write_event_data_1 ();
 
	spin_unlock_irqrestore (&hwc_data.lock, flags);
}
 
void 
start_poll_hwc (void)
{
	init_timer (&hwc_data.poll_timer);
	hwc_data.poll_timer.function = do_poll_hwc;
	hwc_data.poll_timer.data = (unsigned long) NULL;
	hwc_data.poll_timer.expires = jiffies + 2 * HZ;
	add_timer (&hwc_data.poll_timer);
	hwc_data.flags |= HWC_PTIMER_RUNS;
}
 
static int 
write_event_data_1 (void)
{
	unsigned short int condition_code;
	int retval;
	write_hwcb_t *hwcb = (write_hwcb_t *) OUT_HWCB;
 
	if ((!hwc_data.write_prio) &&
	    (!hwc_data.write_nonprio) &&
	    hwc_data.read_statechange)
		return -EOPNOTSUPP;
 
	if (hwc_data.current_servc)
		return -EBUSY;
 
	retval = sane_write_hwcb ();
	if (retval < 0)
		return -EIO;
 
	if (!OUT_HWCB_MTO)
		return -ENODATA;
 
	if (!hwc_data.write_nonprio && hwc_data.write_prio)
		hwcb->msgbuf.type = ET_PMsgCmd;
	else
		hwcb->msgbuf.type = ET_Msg;
 
	condition_code = service_call (HWC_CMDW_WRITEDATA, OUT_HWCB);
 
#ifdef DUMP_HWC_WRITE_ERROR
	if (condition_code != HWC_COMMAND_INITIATED)
		__asm__ ("LHI 1,0xe20\n\t"
			 "L 2,0(%0)\n\t"
			 "LRA 3,0(%1)\n\t"
			 "J .+0 \n\t"
	      :
	      :	 "a" (&condition_code), "a" (OUT_HWCB)
	      :	 "1", "2", "3");
#endif
 
	switch (condition_code) {
	case HWC_COMMAND_INITIATED:
		hwc_data.current_servc = HWC_CMDW_WRITEDATA;
		hwc_data.current_hwcb = OUT_HWCB;
		retval = condition_code;
		break;
	case HWC_BUSY:
		retval = -EBUSY;
		break;
	case HWC_NOT_OPERATIONAL:
		start_poll_hwc ();
	default:
		retval = -EIO;
	}
 
	return retval;
}
 
static void 
flush_hwcbs (void)
{
	while (hwc_data.hwcb_count > 1)
		release_write_hwcb ();
 
	release_write_hwcb ();
 
	hwc_data.flags &= ~HWC_FLUSH;
}
 
static int 
write_event_data_2 (u32 ext_int_param)
{
	write_hwcb_t *hwcb;
	int retval = 0;
 
#ifdef DUMP_HWC_WRITE_ERROR
	if ((ext_int_param & HWC_EXT_INT_PARAM_ADDR)
	    != (unsigned long) hwc_data.current_hwcb) {
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "write_event_data_2 : "
				       "HWCB address does not fit "
				       "(expected: 0x%lx, got: 0x%lx).\n",
				       (unsigned long) hwc_data.current_hwcb,
				       ext_int_param);
		return -EINVAL;
	}
#endif
 
	hwcb = (write_hwcb_t *) OUT_HWCB;
 
#ifdef DUMP_HWC_WRITE_LIST_ERROR
	if (((unsigned char *) hwcb) != hwc_data.current_hwcb) {
		__asm__ ("LHI 1,0xe22\n\t"
			 "LRA 2,0(%0)\n\t"
			 "LRA 3,0(%1)\n\t"
			 "LRA 4,0(%2)\n\t"
			 "LRA 5,0(%3)\n\t"
			 "J .+0 \n\t"
	      :
	      :	 "a" (OUT_HWCB),
			 "a" (hwc_data.current_hwcb),
			 "a" (BUF_HWCB),
			 "a" (hwcb)
	      :	 "1", "2", "3", "4", "5");
	}
#endif
 
#ifdef DUMP_HWC_WRITE_ERROR
	if (hwcb->response_code != 0x0020) {
		__asm__ ("LHI 1,0xe21\n\t"
			 "LRA 2,0(%0)\n\t"
			 "LRA 3,0(%1)\n\t"
			 "LRA 4,0(%2)\n\t"
			 "LH 5,0(%3)\n\t"
			 "SRL 5,8\n\t"
			 "J .+0 \n\t"
	      :
	      :	 "a" (OUT_HWCB), "a" (hwc_data.current_hwcb),
			 "a" (BUF_HWCB),
			 "a" (&(hwc_data.hwcb_count))
	      :	 "1", "2", "3", "4", "5");
	}
#endif
 
	switch (hwcb->response_code) {
	case 0x0020:
 
		retval = OUT_HWCB_CHAR;
		release_write_hwcb ();
		break;
	case 0x0040:
	case 0x0340:
	case 0x40F0:
		if (!hwc_data.read_statechange) {
			hwcb->response_code = 0;
			start_poll_hwc ();
		}
		retval = -EIO;
		break;
	default:
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "write_event_data_2 : "
				       "failed operation "
				       "(response code: 0x%x "
				       "HWCB address: 0x%x).\n",
				       hwcb->response_code,
				       hwcb);
		retval = -EIO;
	}
 
	if (retval == -EIO) {
 
		hwcb->control_mask[0] = 0;
		hwcb->control_mask[1] = 0;
		hwcb->control_mask[2] = 0;
		hwcb->response_code = 0;
	}
	hwc_data.current_servc = 0;
	hwc_data.current_hwcb = NULL;
 
	if (hwc_data.flags & HWC_FLUSH)
		flush_hwcbs ();
 
	return retval;
}
 
static void 
do_put_line (
		    unsigned char *message,
		    unsigned short count)
{
 
	if (add_mto (message, count) != count) {
 
		if (allocate_write_hwcb () < 0)
			reuse_write_hwcb ();
 
#ifdef DUMP_HWC_WRITE_LIST_ERROR
		if (add_mto (message, count) != count)
			__asm__ ("LHI 1,0xe32\n\t"
				 "LRA 2,0(%0)\n\t"
				 "L 3,0(%1)\n\t"
				 "LRA 4,0(%2)\n\t"
				 "LRA 5,0(%3)\n\t"
				 "J .+0 \n\t"
		      :
		      :	 "a" (message), "a" (&hwc_data.kmem_pages),
				 "a" (BUF_HWCB), "a" (OUT_HWCB)
		      :	 "1", "2", "3", "4", "5");
#else
		add_mto (message, count);
#endif
	}
}
 
static void 
put_line (
		 unsigned char *message,
		 unsigned short count)
{
 
	if ((!hwc_data.obuf_start) && (hwc_data.flags & HWC_WTIMER_RUNS)) {
		del_timer (&hwc_data.write_timer);
		hwc_data.flags &= ~HWC_WTIMER_RUNS;
	}
	hwc_data.obuf_start += count;
 
	do_put_line (message, count);
 
	hwc_data.obuf_start -= count;
}
 
static void 
set_alarm (void)
{
	write_hwcb_t *hwcb;
 
	if ((!BUF_HWCB) || (BUF_HWCB == hwc_data.current_hwcb))
		allocate_write_hwcb ();
 
	hwcb = (write_hwcb_t *) BUF_HWCB;
	hwcb->msgbuf.mdb.mdb_body.go.general_msg_flags |= GMF_SndAlrm;
}
 
static void 
hwc_write_timeout (unsigned long data)
{
	unsigned long flags;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	hwc_data.obuf_start = hwc_data.obuf_count;
	if (hwc_data.obuf_count)
		put_line (hwc_data.obuf, hwc_data.obuf_count);
	hwc_data.obuf_start = 0;
 
	hwc_data.obuf_cursor = 0;
	hwc_data.obuf_count = 0;
 
	write_event_data_1 ();
 
	spin_unlock_irqrestore (&hwc_data.lock, flags);
}
 
static int 
do_hwc_write (
		     int from_user,
		     unsigned char *msg,
		     unsigned int count,
		     unsigned char write_time)
{
	unsigned int i_msg = 0;
	unsigned short int spaces = 0;
	unsigned int processed_characters = 0;
	unsigned char ch;
	unsigned short int obuf_count;
	unsigned short int obuf_cursor;
	unsigned short int obuf_columns;
 
	if (hwc_data.obuf_start) {
		obuf_cursor = 0;
		obuf_count = 0;
		obuf_columns = MIN (hwc_data.ioctls.columns,
				    MAX_MESSAGE_SIZE - hwc_data.obuf_start);
	} else {
		obuf_cursor = hwc_data.obuf_cursor;
		obuf_count = hwc_data.obuf_count;
		obuf_columns = hwc_data.ioctls.columns;
	}
 
	for (i_msg = 0; i_msg < count; i_msg++) {
		if (from_user)
			get_user (ch, msg + i_msg);
		else
			ch = msg[i_msg];
 
		processed_characters++;
 
		if ((obuf_cursor == obuf_columns) &&
 
		    (ch != '\n') &&
 
		    (ch != '\t')) {
			put_line (&hwc_data.obuf[hwc_data.obuf_start],
				  obuf_columns);
			obuf_cursor = 0;
			obuf_count = 0;
		}
		switch (ch) {
 
		case '\n':
 
			put_line (&hwc_data.obuf[hwc_data.obuf_start],
				  obuf_count);
			obuf_cursor = 0;
			obuf_count = 0;
			break;
 
		case '\a':
 
			hwc_data.obuf_start += obuf_count;
			set_alarm ();
			hwc_data.obuf_start -= obuf_count;
 
			break;
 
		case '\t':
 
			do {
				if (obuf_cursor < obuf_columns) {
					hwc_data.obuf[hwc_data.obuf_start +
						      obuf_cursor]
					    = HWC_ASCEBC (' ');
					obuf_cursor++;
				} else
					break;
			} while (obuf_cursor % hwc_data.ioctls.width_htab);
 
			break;
 
		case '\f':
		case '\v':
 
			spaces = obuf_cursor;
			put_line (&hwc_data.obuf[hwc_data.obuf_start],
				  obuf_count);
			obuf_count = obuf_cursor;
			while (spaces) {
				hwc_data.obuf[hwc_data.obuf_start +
					      obuf_cursor - spaces]
				    = HWC_ASCEBC (' ');
				spaces--;
			}
 
			break;
 
		case '\b':
 
			if (obuf_cursor)
				obuf_cursor--;
			break;
 
		case '\r':
 
			obuf_cursor = 0;
			break;
 
		case 0x00:
 
			put_line (&hwc_data.obuf[hwc_data.obuf_start],
				  obuf_count);
			obuf_cursor = 0;
			obuf_count = 0;
			goto out;
 
		default:
 
			if (isprint (ch))
				hwc_data.obuf[hwc_data.obuf_start +
					      obuf_cursor++]
				    = HWC_ASCEBC (ch);
		}
		if (obuf_cursor > obuf_count)
			obuf_count = obuf_cursor;
	}
 
	if (obuf_cursor) {
 
		if (hwc_data.obuf_start ||
		    (hwc_data.ioctls.final_nl == 0)) {
 
			put_line (&hwc_data.obuf[hwc_data.obuf_start],
				  obuf_count);
			obuf_cursor = 0;
			obuf_count = 0;
		} else {
 
			if (hwc_data.ioctls.final_nl > 0) {
 
				if (hwc_data.flags & HWC_WTIMER_RUNS) {
 
					mod_timer (&hwc_data.write_timer,
						   jiffies + hwc_data.ioctls.final_nl * HZ / 10);
				} else {
 
					init_timer (&hwc_data.write_timer);
					hwc_data.write_timer.function =
					    hwc_write_timeout;
					hwc_data.write_timer.data =
					    (unsigned long) NULL;
					hwc_data.write_timer.expires =
					    jiffies +
					    hwc_data.ioctls.final_nl * HZ / 10;
					add_timer (&hwc_data.write_timer);
					hwc_data.flags |= HWC_WTIMER_RUNS;
				}
			} else;
 
		}
	} else;
 
      out:
 
	if (!hwc_data.obuf_start) {
		hwc_data.obuf_cursor = obuf_cursor;
		hwc_data.obuf_count = obuf_count;
	}
	if (write_time == IMMEDIATE_WRITE)
		write_event_data_1 ();
 
	return processed_characters;
}
 
signed int 
hwc_write (int from_user, const unsigned char *msg, unsigned int count)
{
	unsigned long flags;
	int retval;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	retval = do_hwc_write (from_user, (unsigned char *) msg,
			       count, IMMEDIATE_WRITE);
 
	spin_unlock_irqrestore (&hwc_data.lock, flags);
 
	return retval;
}
 
unsigned int 
hwc_chars_in_buffer (unsigned char flag)
{
	unsigned short int number = 0;
	unsigned long flags;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	if (flag & IN_HWCB)
		number += ALL_HWCB_CHAR;
 
	if (flag & IN_WRITE_BUF)
		number += hwc_data.obuf_cursor;
 
	spin_unlock_irqrestore (&hwc_data.lock, flags);
 
	return number;
}
 
static inline int 
nr_setbits (kmem_pages_t arg)
{
	int i;
	int nr = 0;
 
	for (i = 0; i < (sizeof (arg) << 3); i++) {
		if (arg & 1)
			nr++;
		arg >>= 1;
	}
 
	return nr;
}
 
unsigned int 
hwc_write_room (unsigned char flag)
{
	unsigned int number = 0;
	unsigned long flags;
	write_hwcb_t *hwcb;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	if (flag & IN_HWCB) {
 
		if (BUF_HWCB) {
			hwcb = (write_hwcb_t *) BUF_HWCB;
			number += MAX_HWCB_ROOM - hwcb->length;
		}
		number += (hwc_data.ioctls.kmem_hwcb -
			   nr_setbits (hwc_data.kmem_pages)) *
		    (MAX_HWCB_ROOM -
		     (sizeof (write_hwcb_t) + sizeof (mto_t)));
	}
	if (flag & IN_WRITE_BUF)
		number += MAX_HWCB_ROOM - hwc_data.obuf_cursor;
 
	spin_unlock_irqrestore (&hwc_data.lock, flags);
 
	return number;
}
 
void 
hwc_flush_buffer (unsigned char flag)
{
	unsigned long flags;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	if (flag & IN_HWCB) {
		if (hwc_data.current_servc != HWC_CMDW_WRITEDATA)
			flush_hwcbs ();
		else
			hwc_data.flags |= HWC_FLUSH;
	}
	if (flag & IN_WRITE_BUF) {
		hwc_data.obuf_cursor = 0;
		hwc_data.obuf_count = 0;
	}
	spin_unlock_irqrestore (&hwc_data.lock, flags);
}
 
unsigned short int 
seperate_cases (unsigned char *buf, unsigned short int count)
{
 
	unsigned short int i_in;
 
	unsigned short int i_out = 0;
 
	unsigned char _case = 0;
 
	for (i_in = 0; i_in < count; i_in++) {
 
		if (buf[i_in] == hwc_data.ioctls.delim) {
 
			if ((i_in + 1 < count) &&
			    (buf[i_in + 1] == hwc_data.ioctls.delim)) {
 
				buf[i_out] = hwc_data.ioctls.delim;
 
				i_out++;
 
				i_in++;
 
			} else
				_case = ~_case;
 
		} else {
 
			if (_case) {
 
				if (hwc_data.ioctls.tolower)
					buf[i_out] = _ebc_toupper[buf[i_in]];
 
				else
					buf[i_out] = _ebc_tolower[buf[i_in]];
 
			} else
				buf[i_out] = buf[i_in];
 
			i_out++;
		}
	}
 
	return i_out;
}
 
#ifdef DUMP_HWCB_INPUT
 
static int 
gds_vector_name (u16 id, unsigned char name[])
{
	int retval = 0;
 
	switch (id) {
	case GDS_ID_MDSMU:
		name = "Multiple Domain Support Message Unit";
		break;
	case GDS_ID_MDSRouteInfo:
		name = "MDS Routing Information";
		break;
	case GDS_ID_AgUnWrkCorr:
		name = "Agent Unit of Work Correlator";
		break;
	case GDS_ID_SNACondReport:
		name = "SNA Condition Report";
		break;
	case GDS_ID_CPMSU:
		name = "CP Management Services Unit";
		break;
	case GDS_ID_RoutTargInstr:
		name = "Routing and Targeting Instructions";
		break;
	case GDS_ID_OpReq:
		name = "Operate Request";
		break;
	case GDS_ID_TextCmd:
		name = "Text Command";
		break;
 
	default:
		name = "unknown GDS variable";
		retval = -EINVAL;
	}
 
	return retval;
}
#endif
 
inline static gds_vector_t *
find_gds_vector (
			gds_vector_t * start, void *end, u16 id)
{
	gds_vector_t *vec;
	gds_vector_t *retval = NULL;
 
	vec = start;
 
	while (((void *) vec) < end) {
		if (vec->gds_id == id) {
 
#ifdef DUMP_HWCB_INPUT
			int retval_name;
			unsigned char name[64];
 
			retval_name = gds_vector_name (id, name);
			internal_print (
					       DELAYED_WRITE,
					       HWC_RW_PRINT_HEADER
					  "%s at 0x%x up to 0x%x, length: %d",
					       name,
					       (unsigned long) vec,
				      ((unsigned long) vec) + vec->length - 1,
					       vec->length);
			if (retval_name < 0)
				internal_print (
						       IMMEDIATE_WRITE,
						       ", id: 0x%x\n",
						       vec->gds_id);
			else
				internal_print (
						       IMMEDIATE_WRITE,
						       "\n");
#endif
 
			retval = vec;
			break;
		}
		vec = (gds_vector_t *) (((unsigned long) vec) + vec->length);
	}
 
	return retval;
}
 
inline static gds_subvector_t *
find_gds_subvector (
			   gds_subvector_t * start, void *end, u8 key)
{
	gds_subvector_t *subvec;
	gds_subvector_t *retval = NULL;
 
	subvec = start;
 
	while (((void *) subvec) < end) {
		if (subvec->key == key) {
			retval = subvec;
			break;
		}
		subvec = (gds_subvector_t *)
		    (((unsigned long) subvec) + subvec->length);
	}
 
	return retval;
}
 
inline static int 
get_input (void *start, void *end)
{
	int count;
 
	count = ((unsigned long) end) - ((unsigned long) start);
 
	if (hwc_data.ioctls.tolower)
		EBC_TOLOWER (start, count);
 
	if (hwc_data.ioctls.delim)
		count = seperate_cases (start, count);
 
	HWC_EBCASC_STR (start, count);
 
	if (hwc_data.ioctls.echo)
		do_hwc_write (0, start, count, IMMEDIATE_WRITE);
 
	if (hwc_data.calls != NULL)
		if (hwc_data.calls->move_input != NULL)
			(hwc_data.calls->move_input) (start, count);
 
	return count;
}
 
inline static int 
eval_selfdeftextmsg (gds_subvector_t * start, void *end)
{
	gds_subvector_t *subvec;
	void *subvec_data;
	void *subvec_end;
	int retval = 0;
 
	subvec = start;
 
	while (((void *) subvec) < end) {
		subvec = find_gds_subvector (subvec, end, 0x30);
		if (!subvec)
			break;
		subvec_data = (void *)
		    (((unsigned long) subvec) +
		     sizeof (gds_subvector_t));
		subvec_end = (void *)
		    (((unsigned long) subvec) + subvec->length);
		retval += get_input (subvec_data, subvec_end);
		subvec = (gds_subvector_t *) subvec_end;
	}
 
	return retval;
}
 
inline static int 
eval_textcmd (gds_subvector_t * start, void *end)
{
	gds_subvector_t *subvec;
	gds_subvector_t *subvec_data;
	void *subvec_end;
	int retval = 0;
 
	subvec = start;
 
	while (((void *) subvec) < end) {
		subvec = find_gds_subvector (
					 subvec, end, GDS_KEY_SelfDefTextMsg);
		if (!subvec)
			break;
		subvec_data = (gds_subvector_t *)
		    (((unsigned long) subvec) +
		     sizeof (gds_subvector_t));
		subvec_end = (void *)
		    (((unsigned long) subvec) + subvec->length);
		retval += eval_selfdeftextmsg (subvec_data, subvec_end);
		subvec = (gds_subvector_t *) subvec_end;
	}
 
	return retval;
}
 
inline static int 
eval_cpmsu (gds_vector_t * start, void *end)
{
	gds_vector_t *vec;
	gds_subvector_t *vec_data;
	void *vec_end;
	int retval = 0;
 
	vec = start;
 
	while (((void *) vec) < end) {
		vec = find_gds_vector (vec, end, GDS_ID_TextCmd);
		if (!vec)
			break;
		vec_data = (gds_subvector_t *)
		    (((unsigned long) vec) + sizeof (gds_vector_t));
		vec_end = (void *) (((unsigned long) vec) + vec->length);
		retval += eval_textcmd (vec_data, vec_end);
		vec = (gds_vector_t *) vec_end;
	}
 
	return retval;
}
 
inline static int 
eval_mdsmu (gds_vector_t * start, void *end)
{
	gds_vector_t *vec;
	gds_vector_t *vec_data;
	void *vec_end;
	int retval = 0;
 
	vec = find_gds_vector (start, end, GDS_ID_CPMSU);
	if (vec) {
		vec_data = (gds_vector_t *)
		    (((unsigned long) vec) + sizeof (gds_vector_t));
		vec_end = (void *) (((unsigned long) vec) + vec->length);
		retval = eval_cpmsu (vec_data, vec_end);
	}
	return retval;
}
 
static int 
eval_evbuf (gds_vector_t * start, void *end)
{
	gds_vector_t *vec;
	gds_vector_t *vec_data;
	void *vec_end;
	int retval = 0;
 
	vec = find_gds_vector (start, end, GDS_ID_MDSMU);
	if (vec) {
		vec_data = (gds_vector_t *)
		    (((unsigned long) vec) + sizeof (gds_vector_t));
		vec_end = (void *) (((unsigned long) vec) + vec->length);
		retval = eval_mdsmu (vec_data, vec_end);
	}
	return retval;
}
 
static inline int 
eval_hwc_receive_mask (_hwcb_mask_t mask)
{
 
	hwc_data.write_nonprio
	    = ((mask & ET_Msg_Mask) == ET_Msg_Mask);
 
	hwc_data.write_prio
	    = ((mask & ET_PMsgCmd_Mask) == ET_PMsgCmd_Mask);
 
	if (hwc_data.write_prio || hwc_data.write_nonprio) {
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "can write messages\n");
		return 0;
	} else {
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "can not write messages\n");
		return -1;
	}
}
 
static inline int 
eval_hwc_send_mask (_hwcb_mask_t mask)
{
 
	hwc_data.read_statechange
	    = ((mask & ET_StateChange_Mask) == ET_StateChange_Mask);
	if (hwc_data.read_statechange)
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				     "can read state change notifications\n");
	else
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				 "can not read state change notifications\n");
 
	hwc_data.sig_quiesce
	    = ((mask & ET_SigQuiesce_Mask) == ET_SigQuiesce_Mask);
	if (hwc_data.sig_quiesce)
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "can receive signal quiesce\n");
	else
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "can not receive signal quiesce\n");
 
	hwc_data.read_nonprio
	    = ((mask & ET_OpCmd_Mask) == ET_OpCmd_Mask);
	if (hwc_data.read_nonprio)
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "can read commands\n");
 
	hwc_data.read_prio
	    = ((mask & ET_PMsgCmd_Mask) == ET_PMsgCmd_Mask);
	if (hwc_data.read_prio)
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				       "can read priority commands\n");
 
	if (hwc_data.read_prio || hwc_data.read_nonprio) {
		return 0;
	} else {
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
				     "can not read commands from operator\n");
		return -1;
	}
}
 
static int 
eval_statechangebuf (statechangebuf_t * scbuf)
{
	int retval = 0;
 
	internal_print (
			       DELAYED_WRITE,
			       HWC_RW_PRINT_HEADER
			       "HWC state change detected\n");
 
	if (scbuf->validity_hwc_active_facility_mask) {
 
	}
	if (scbuf->validity_hwc_receive_mask) {
 
		if (scbuf->mask_length != 4) {
#ifdef DUMP_HWC_INIT_ERROR
			__asm__ ("LHI 1,0xe50\n\t"
				 "LRA 2,0(%0)\n\t"
				 "J .+0 \n\t"
		      :
		      :	 "a" (scbuf)
		      :	 "1", "2");
#endif
		} else {
 
			retval += eval_hwc_receive_mask
			    (scbuf->hwc_receive_mask);
		}
	}
	if (scbuf->validity_hwc_send_mask) {
 
		if (scbuf->mask_length != 4) {
#ifdef DUMP_HWC_INIT_ERROR
			__asm__ ("LHI 1,0xe51\n\t"
				 "LRA 2,0(%0)\n\t"
				 "J .+0 \n\t"
		      :
		      :	 "a" (scbuf)
		      :	 "1", "2");
#endif
		} else {
 
			retval += eval_hwc_send_mask
			    (scbuf->hwc_send_mask);
		}
	}
	if (scbuf->validity_read_data_function_mask) {
 
	}
	return retval;
}
 
#ifdef CONFIG_SMP
extern unsigned long cpu_online_map;
static volatile unsigned long cpu_quiesce_map;
 
static void 
do_load_quiesce_psw (void)
{
	psw_t quiesce_psw;
 
	clear_bit (smp_processor_id (), &cpu_quiesce_map);
	if (smp_processor_id () == 0) {
 
		while (cpu_quiesce_map != 0) ;
 
		quiesce_psw.mask = _DW_PSW_MASK;
		quiesce_psw.addr = 0xfff;
		__load_psw (quiesce_psw);
	}
	signal_processor (smp_processor_id (), sigp_stop);
}
 
static void 
do_machine_quiesce (void)
{
	cpu_quiesce_map = cpu_online_map;
	smp_call_function (do_load_quiesce_psw, NULL, 0, 0);
	do_load_quiesce_psw ();
}
 
#else
static void 
do_machine_quiesce (void)
{
	psw_t quiesce_psw;
 
	quiesce_psw.mask = _DW_PSW_MASK;
	queisce_psw.addr = 0xfff;
	__load_psw (quiesce_psw);
}
 
#endif
 
static int 
process_evbufs (void *start, void *end)
{
	int retval = 0;
	evbuf_t *evbuf;
	void *evbuf_end;
	gds_vector_t *evbuf_data;
 
	evbuf = (evbuf_t *) start;
	while (((void *) evbuf) < end) {
		evbuf_data = (gds_vector_t *)
		    (((unsigned long) evbuf) + sizeof (evbuf_t));
		evbuf_end = (void *) (((unsigned long) evbuf) + evbuf->length);
		switch (evbuf->type) {
		case ET_OpCmd:
		case ET_CntlProgOpCmd:
		case ET_PMsgCmd:
#ifdef DUMP_HWCB_INPUT
 
			internal_print (
					       DELAYED_WRITE,
					       HWC_RW_PRINT_HEADER
					       "event buffer "
					   "at 0x%x up to 0x%x, length: %d\n",
					       (unsigned long) evbuf,
					       (unsigned long) (evbuf_end - 1),
					       evbuf->length);
			dump_storage_area ((void *) evbuf, evbuf->length);
#endif
			retval += eval_evbuf (evbuf_data, evbuf_end);
			break;
		case ET_StateChange:
			retval += eval_statechangebuf
			    ((statechangebuf_t *) evbuf);
			break;
		case ET_SigQuiesce:
 
			_machine_restart = do_machine_quiesce;
			_machine_halt = do_machine_quiesce;
			_machine_power_off = do_machine_quiesce;
			ctrl_alt_del ();
			break;
		default:
			internal_print (
					       DELAYED_WRITE,
					       HWC_RW_PRINT_HEADER
					       "unconditional read: "
					       "unknown event buffer found, "
					       "type 0x%x",
					       evbuf->type);
			retval = -ENOSYS;
		}
		evbuf = (evbuf_t *) evbuf_end;
	}
	return retval;
}
 
static int 
unconditional_read_1 (void)
{
	unsigned short int condition_code;
	read_hwcb_t *hwcb = (read_hwcb_t *) hwc_data.page;
	int retval;
 
#if 0
 
	if ((!hwc_data.read_prio) && (!hwc_data.read_nonprio))
		return -EOPNOTSUPP;
 
	if (hwc_data.current_servc)
		return -EBUSY;
#endif
 
	memset (hwcb, 0x00, PAGE_SIZE);
	memcpy (hwcb, &read_hwcb_template, sizeof (read_hwcb_t));
 
	condition_code = service_call (HWC_CMDW_READDATA, hwc_data.page);
 
#ifdef DUMP_HWC_READ_ERROR
	if (condition_code == HWC_NOT_OPERATIONAL)
		__asm__ ("LHI 1,0xe40\n\t"
			 "L 2,0(%0)\n\t"
			 "LRA 3,0(%1)\n\t"
			 "J .+0 \n\t"
	      :
	      :	 "a" (&condition_code), "a" (hwc_data.page)
	      :	 "1", "2", "3");
#endif
 
	switch (condition_code) {
	case HWC_COMMAND_INITIATED:
		hwc_data.current_servc = HWC_CMDW_READDATA;
		hwc_data.current_hwcb = hwc_data.page;
		retval = condition_code;
		break;
	case HWC_BUSY:
		retval = -EBUSY;
		break;
	default:
		retval = -EIO;
	}
 
	return retval;
}
 
static int 
unconditional_read_2 (u32 ext_int_param)
{
	read_hwcb_t *hwcb = (read_hwcb_t *) hwc_data.page;
 
#ifdef DUMP_HWC_READ_ERROR
	if ((hwcb->response_code != 0x0020) &&
	    (hwcb->response_code != 0x0220) &&
	    (hwcb->response_code != 0x60F0) &&
	    (hwcb->response_code != 0x62F0))
		__asm__ ("LHI 1,0xe41\n\t"
			 "LRA 2,0(%0)\n\t"
			 "L 3,0(%1)\n\t"
			 "J .+0\n\t"
	      :
	      :	 "a" (hwc_data.page), "a" (&(hwcb->response_code))
	      :	 "1", "2", "3");
#endif
 
	hwc_data.current_servc = 0;
	hwc_data.current_hwcb = NULL;
 
	switch (hwcb->response_code) {
 
	case 0x0020:
	case 0x0220:
		return process_evbufs (
		     (void *) (((unsigned long) hwcb) + sizeof (read_hwcb_t)),
			    (void *) (((unsigned long) hwcb) + hwcb->length));
 
	case 0x60F0:
	case 0x62F0:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
				       "unconditional read: "
				     "got interrupt and tried to read input, "
				  "but nothing found (response code=0x%x).\n",
				       hwcb->response_code);
		return 0;
 
	case 0x0100:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
			 "unconditional read: HWCB boundary violation - this "
			 "must not occur in a correct driver, please contact "
				       "author\n");
		return -EIO;
 
	case 0x0300:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
				       "unconditional read: "
			"insufficient HWCB length - this must not occur in a "
				   "correct driver, please contact author\n");
		return -EIO;
 
	case 0x01F0:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
				       "unconditional read: "
			 "invalid command - this must not occur in a correct "
				       "driver, please contact author\n");
		return -EIO;
 
	case 0x40F0:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
			       "unconditional read: invalid function code\n");
		return -EIO;
 
	case 0x70F0:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
			      "unconditional read: invalid selection mask\n");
		return -EIO;
 
	case 0x0040:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
				 "unconditional read: HWC equipment check\n");
		return -EIO;
 
	default:
		internal_print (
				       IMMEDIATE_WRITE,
				       HWC_RW_PRINT_HEADER
			"unconditional read: invalid response code %x - this "
			 "must not occur in a correct driver, please contact "
				       "author\n",
				       hwcb->response_code);
		return -EIO;
	}
}
 
static int 
write_event_mask_1 (void)
{
	unsigned int condition_code;
	int retval;
 
	condition_code = service_call (HWC_CMDW_WRITEMASK, hwc_data.page);
 
#ifdef DUMP_HWC_INIT_ERROR
 
	if (condition_code == HWC_NOT_OPERATIONAL)
		__asm__ ("LHI 1,0xe10\n\t"
			 "L 2,0(%0)\n\t"
			 "LRA 3,0(%1)\n\t"
			 "J .+0\n\t"
	      :
	      :	 "a" (&condition_code), "a" (hwc_data.page)
	      :	 "1", "2", "3");
#endif
 
	switch (condition_code) {
	case HWC_COMMAND_INITIATED:
		hwc_data.current_servc = HWC_CMDW_WRITEMASK;
		hwc_data.current_hwcb = hwc_data.page;
		retval = condition_code;
		break;
	case HWC_BUSY:
		retval = -EBUSY;
		break;
	default:
		retval = -EIO;
	}
 
	return retval;
}
 
static int 
write_event_mask_2 (u32 ext_int_param)
{
	init_hwcb_t *hwcb = (init_hwcb_t *) hwc_data.page;
	int retval = 0;
 
	if (hwcb->response_code != 0x0020) {
#ifdef DUMP_HWC_INIT_ERROR
		__asm__ ("LHI 1,0xe11\n\t"
			 "LRA 2,0(%0)\n\t"
			 "L 3,0(%1)\n\t"
			 "J .+0\n\t"
	      :
	      :	 "a" (hwcb), "a" (&(hwcb->response_code))
	      :	 "1", "2", "3");
#else
		retval = -1;
#endif
	} else {
		if (hwcb->mask_length != 4) {
#ifdef DUMP_HWC_INIT_ERROR
			__asm__ ("LHI 1,0xe52\n\t"
				 "LRA 2,0(%0)\n\t"
				 "J .+0 \n\t"
		      :
		      :	 "a" (hwcb)
		      :	 "1", "2");
#endif
		} else {
			retval += eval_hwc_receive_mask
			    (hwcb->hwc_receive_mask);
			retval += eval_hwc_send_mask (hwcb->hwc_send_mask);
		}
	}
 
	hwc_data.current_servc = 0;
	hwc_data.current_hwcb = NULL;
 
	return retval;
}
 
static int 
set_hwc_ioctls (hwc_ioctls_t * ioctls, char correct)
{
	int retval = 0;
	hwc_ioctls_t tmp;
 
	if (ioctls->width_htab > MAX_MESSAGE_SIZE) {
		if (correct)
			tmp.width_htab = MAX_MESSAGE_SIZE;
		else
			retval = -EINVAL;
	} else
		tmp.width_htab = ioctls->width_htab;
 
	tmp.echo = ioctls->echo;
 
	if (ioctls->columns > MAX_MESSAGE_SIZE) {
		if (correct)
			tmp.columns = MAX_MESSAGE_SIZE;
		else
			retval = -EINVAL;
	} else
		tmp.columns = ioctls->columns;
 
	tmp.final_nl = ioctls->final_nl;
 
	if (ioctls->max_hwcb < 2) {
		if (correct)
			tmp.max_hwcb = 2;
		else
			retval = -EINVAL;
	} else
		tmp.max_hwcb = ioctls->max_hwcb;
 
	tmp.tolower = ioctls->tolower;
 
	if (ioctls->kmem_hwcb > ioctls->max_hwcb) {
		if (correct)
			tmp.kmem_hwcb = ioctls->max_hwcb;
		else
			retval = -EINVAL;
	} else
		tmp.kmem_hwcb = ioctls->kmem_hwcb;
 
	if (ioctls->kmem_hwcb > MAX_KMEM_PAGES) {
		if (correct)
			ioctls->kmem_hwcb = MAX_KMEM_PAGES;
		else
			retval = -EINVAL;
	}
	if (ioctls->kmem_hwcb < 2) {
		if (correct)
			ioctls->kmem_hwcb = 2;
		else
			retval = -EINVAL;
	}
	tmp.delim = ioctls->delim;
 
	if (!(retval < 0))
		hwc_data.ioctls = tmp;
 
	return retval;
}
 
int 
do_hwc_init (void)
{
	int retval;
 
	memcpy (hwc_data.page, &init_hwcb_template, sizeof (init_hwcb_t));
 
	do {
 
		retval = write_event_mask_1 ();
 
		if (retval == -EBUSY) {
 
			hwc_data.flags |= HWC_INIT;
 
			__ctl_store (cr0, 0, 0);
			cr0_save = cr0;
			cr0 |= 0x00000200;
			cr0 &= 0xFFFFF3AC;
			__ctl_load (cr0, 0, 0);
 
			asm volatile ("STOSM %0,0x01"
				      :"=m" (psw_mask)::"memory");
 
			while (!(hwc_data.flags & HWC_INTERRUPT))
				barrier ();
 
			asm volatile ("STNSM %0,0xFE"
				      :"=m" (psw_mask)::"memory");
 
			__ctl_load (cr0_save, 0, 0);
 
			hwc_data.flags &= ~HWC_INIT;
		}
	} while (retval == -EBUSY);
 
	if (retval == -EIO) {
		hwc_data.flags |= HWC_BROKEN;
		printk (HWC_RW_PRINT_HEADER "HWC not operational\n");
	}
	return retval;
}
 
void hwc_interrupt_handler (struct pt_regs *regs, __u16 code);
 
int 
hwc_init (void)
{
	int retval;
 
#ifdef BUFFER_STRESS_TEST
 
	init_hwcb_t *hwcb;
	int i;
 
#endif
 
	if (register_early_external_interrupt (0x2401, hwc_interrupt_handler,
					       &ext_int_info_hwc) != 0)
		panic ("Couldn't request external interrupts 0x2401");
 
	spin_lock_init (&hwc_data.lock);
 
#ifdef USE_VM_DETECTION
 
	if (MACHINE_IS_VM) {
 
		if (hwc_data.init_ioctls.columns > 76)
			hwc_data.init_ioctls.columns = 76;
		hwc_data.init_ioctls.tolower = 1;
		if (!hwc_data.init_ioctls.delim)
			hwc_data.init_ioctls.delim = DEFAULT_CASE_DELIMITER;
	} else {
		hwc_data.init_ioctls.tolower = 0;
		hwc_data.init_ioctls.delim = 0;
	}
#endif
	retval = set_hwc_ioctls (&hwc_data.init_ioctls, 1);
 
	hwc_data.kmem_start = (unsigned long)
	    alloc_bootmem_low_pages (hwc_data.ioctls.kmem_hwcb * PAGE_SIZE);
	hwc_data.kmem_end = hwc_data.kmem_start +
	    hwc_data.ioctls.kmem_hwcb * PAGE_SIZE - 1;
 
	retval = do_hwc_init ();
 
	ctl_set_bit (0, 9);
 
#ifdef BUFFER_STRESS_TEST
 
	internal_print (
			       DELAYED_WRITE,
			       HWC_RW_PRINT_HEADER
			       "use %i bytes for buffering.\n",
			       hwc_data.ioctls.kmem_hwcb * PAGE_SIZE);
	for (i = 0; i < 500; i++) {
		hwcb = (init_hwcb_t *) BUF_HWCB;
		internal_print (
				       DELAYED_WRITE,
				       HWC_RW_PRINT_HEADER
			  "This is stress test message #%i, free: %i bytes\n",
				       i,
			     MAX_HWCB_ROOM - (hwcb->length + sizeof (mto_t)));
	}
 
#endif
 
	return /*retval */ 0;
}
 
signed int 
hwc_register_calls (hwc_high_level_calls_t * calls)
{
	if (calls == NULL)
		return -EINVAL;
 
	if (hwc_data.calls != NULL)
		return -EBUSY;
 
	hwc_data.calls = calls;
	return 0;
}
 
signed int 
hwc_unregister_calls (hwc_high_level_calls_t * calls)
{
	if (hwc_data.calls == NULL)
		return -EINVAL;
 
	if (calls != hwc_data.calls)
		return -EINVAL;
 
	hwc_data.calls = NULL;
	return 0;
}
 
int 
hwc_send (hwc_request_t * req)
{
	unsigned long flags;
	int retval;
	int cc;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
	if (!req || !req->callback || !req->block) {
		retval = -EINVAL;
		goto unlock;
	}
	if (hwc_data.request) {
		retval = -ENOTSUPP;
		goto unlock;
	}
	cc = service_call (req->word, req->block);
	switch (cc) {
	case 0:
		hwc_data.request = req;
		hwc_data.current_servc = req->word;
		hwc_data.current_hwcb = req->block;
		retval = 0;
		break;
	case 2:
		retval = -EBUSY;
		break;
	default:
		retval = -ENOSYS;
 
	}
      unlock:
	spin_unlock_irqrestore (&hwc_data.lock, flags);
	return retval;
}
 
EXPORT_SYMBOL (hwc_send);
 
void 
do_hwc_callback (u32 ext_int_param)
{
	if (!hwc_data.request || !hwc_data.request->callback)
		return;
	if ((ext_int_param & HWC_EXT_INT_PARAM_ADDR)
	    != (unsigned long) hwc_data.request->block)
		return;
	hwc_data.request->callback (hwc_data.request);
	hwc_data.request = NULL;
	hwc_data.current_hwcb = NULL;
	hwc_data.current_servc = 0;
}
 
void 
hwc_do_interrupt (u32 ext_int_param)
{
	u32 finished_hwcb = ext_int_param & HWC_EXT_INT_PARAM_ADDR;
	u32 evbuf_pending = ext_int_param & HWC_EXT_INT_PARAM_PEND;
 
	if (hwc_data.flags & HWC_PTIMER_RUNS) {
		del_timer (&hwc_data.poll_timer);
		hwc_data.flags &= ~HWC_PTIMER_RUNS;
	}
	if (finished_hwcb) {
 
		if ((unsigned long) hwc_data.current_hwcb != finished_hwcb) {
			internal_print (
					       DELAYED_WRITE,
					       HWC_RW_PRINT_HEADER
					       "interrupt: mismatch: "
					       "ext. int param. (0x%x) vs. "
					       "current HWCB (0x%x)\n",
					       ext_int_param,
					       hwc_data.current_hwcb);
		} else {
			if (hwc_data.request) {
 
				do_hwc_callback (ext_int_param);
			} else {
 
				switch (hwc_data.current_servc) {
 
				case HWC_CMDW_WRITEMASK:
 
					write_event_mask_2 (ext_int_param);
					break;
 
				case HWC_CMDW_WRITEDATA:
 
					write_event_data_2 (ext_int_param);
					break;
 
				case HWC_CMDW_READDATA:
 
					unconditional_read_2 (ext_int_param);
					break;
				default:
				}
			}
		}
	} else {
 
		if (hwc_data.current_hwcb) {
			internal_print (
					       DELAYED_WRITE,
					       HWC_RW_PRINT_HEADER
					       "interrupt: mismatch: "
					       "ext. int. param. (0x%x) vs. "
					       "current HWCB (0x%x)\n",
					       ext_int_param,
					       hwc_data.current_hwcb);
		}
	}
 
	if (evbuf_pending) {
 
		unconditional_read_1 ();
	} else {
 
		write_event_data_1 ();
	}
 
	if (!hwc_data.calls || !hwc_data.calls->wake_up)
		return;
	(hwc_data.calls->wake_up) ();
}
 
void 
hwc_interrupt_handler (struct pt_regs *regs, __u16 code)
{
	int cpu = smp_processor_id ();
 
	u32 ext_int_param = hwc_ext_int_param ();
 
	irq_enter (cpu, 0x2401);
 
	if (hwc_data.flags & HWC_INIT) {
 
		hwc_data.flags |= HWC_INTERRUPT;
	} else if (hwc_data.flags & HWC_BROKEN) {
 
		if (!do_hwc_init ()) {
			hwc_data.flags &= ~HWC_BROKEN;
			internal_print (DELAYED_WRITE,
					HWC_RW_PRINT_HEADER
					"delayed HWC setup after"
					" temporary breakdown"
					" (ext. int. parameter=0x%x)\n",
					ext_int_param);
		}
	} else {
		spin_lock (&hwc_data.lock);
		hwc_do_interrupt (ext_int_param);
		spin_unlock (&hwc_data.lock);
	}
	irq_exit (cpu, 0x2401);
}
 
void 
hwc_unblank (void)
{
 
	spin_lock (&hwc_data.lock);
	spin_unlock (&hwc_data.lock);
 
	__ctl_store (cr0, 0, 0);
	cr0_save = cr0;
	cr0 |= 0x00000200;
	cr0 &= 0xFFFFF3AC;
	__ctl_load (cr0, 0, 0);
 
	asm volatile ("STOSM %0,0x01":"=m" (psw_mask)::"memory");
 
	while (ALL_HWCB_CHAR)
		barrier ();
 
	asm volatile ("STNSM %0,0xFE":"=m" (psw_mask)::"memory");
 
	__ctl_load (cr0_save, 0, 0);
}
 
int 
hwc_ioctl (unsigned int cmd, unsigned long arg)
{
	hwc_ioctls_t tmp = hwc_data.ioctls;
	int retval = 0;
	unsigned long flags;
	unsigned int obuf;
 
	spin_lock_irqsave (&hwc_data.lock, flags);
 
	switch (cmd) {
 
	case TIOCHWCSHTAB:
		if (get_user (tmp.width_htab, (ioctl_htab_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCSECHO:
		if (get_user (tmp.echo, (ioctl_echo_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCSCOLS:
		if (get_user (tmp.columns, (ioctl_cols_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCSNL:
		if (get_user (tmp.final_nl, (ioctl_nl_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCSOBUF:
		if (get_user (obuf, (unsigned int *) arg))
			goto fault;
		if (obuf & 0xFFF)
			tmp.max_hwcb = (((obuf | 0xFFF) + 1) >> 12);
		else
			tmp.max_hwcb = (obuf >> 12);
		break;
 
	case TIOCHWCSCASE:
		if (get_user (tmp.tolower, (ioctl_case_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCSDELIM:
		if (get_user (tmp.delim, (ioctl_delim_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCSINIT:
		retval = set_hwc_ioctls (&hwc_data.init_ioctls, 1);
		break;
 
	case TIOCHWCGHTAB:
		if (put_user (tmp.width_htab, (ioctl_htab_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGECHO:
		if (put_user (tmp.echo, (ioctl_echo_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGCOLS:
		if (put_user (tmp.columns, (ioctl_cols_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGNL:
		if (put_user (tmp.final_nl, (ioctl_nl_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGOBUF:
		if (put_user (tmp.max_hwcb, (ioctl_obuf_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGKBUF:
		if (put_user (tmp.kmem_hwcb, (ioctl_obuf_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGCASE:
		if (put_user (tmp.tolower, (ioctl_case_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGDELIM:
		if (put_user (tmp.delim, (ioctl_delim_t *) arg))
			goto fault;
		break;
#if 0
 
	case TIOCHWCGINIT:
		if (put_user (&hwc_data.init_ioctls, (hwc_ioctls_t *) arg))
			goto fault;
		break;
 
	case TIOCHWCGCURR:
		if (put_user (&hwc_data.ioctls, (hwc_ioctls_t *) arg))
			goto fault;
		break;
#endif
 
	default:
		goto noioctlcmd;
	}
 
	if (_IOC_DIR (cmd) == _IOC_WRITE)
		retval = set_hwc_ioctls (&tmp, 0);
 
	goto out;
 
      fault:
	retval = -EFAULT;
	goto out;
      noioctlcmd:
	retval = -ENOIOCTLCMD;
      out:
	spin_unlock_irqrestore (&hwc_data.lock, flags);
	return retval;
}
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.