patch-2.2.14 linux/drivers/s390/char/hwc_rw.c

Next file: linux/drivers/s390/char/hwc_rw.h
Previous file: linux/drivers/s390/char/hwc_con.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.13/linux/drivers/s390/char/hwc_rw.c linux/drivers/s390/char/hwc_rw.c
@@ -0,0 +1,1821 @@
+/*
+ *  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 <peschke@fh-brandenburg.de>
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+#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 <asm/ebcdic.h>
+#include <asm/uaccess.h>
+#include <asm/types.h>
+#include <asm/bitops.h>
+#include <asm/setup.h>
+#include <asm/page.h>
+
+#ifndef MIN
+#define MIN(a,b) ((a<b) ? a : b)
+#endif
+
+#define HWC_RW_PRINT_HEADER "hwc low level driver: "
+#define  USE_VM_DETECTION
+#define  DEFAULT_CASE_DELIMITER '%'
+
+#define DUMP_HWC_INIT_ERROR
+#define DUMP_HWC_WRITE_ERROR
+#define DUMP_HWC_WRITE_LIST_ERROR
+#define 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 u32 kmem_pages_t;
+
+#define MAX_KMEM_PAGES (sizeof(kmem_pages_t) << 3)
+#define HWC_TIMER_RUNS	1
+#define FLUSH_HWCBS	2
+
+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 flags;
+	spinlock_t lock;
+	struct timer_list write_timer;
+} hwc_data = {
+	{ },		
+	{		
+		8,		
+		0,		
+		80,		
+		CODE_ASCII,	
+		1,		
+		50,		
+		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		
+};
+
+#define DELAYED_WRITE 0
+#define IMMEDIATE_WRITE 1
+
+static signed int do_hwc_write(int from_user, const unsigned char *,
+					unsigned int,
+					unsigned char,
+					unsigned char);
+
+
+static asmlinkage int internal_print(char write_time, const char *fmt, ...)
+{
+	va_list args;
+	int i;
+	unsigned char buf[512];
+
+	va_start(args, fmt);
+	i = vsprintf(buf, fmt, args);
+	va_end(args);
+	return do_hwc_write(0, buf, i, CODE_ASCII, write_time);
+}
+
+
+int hwc_printk(const char *fmt, ...)
+{
+	va_list args;
+	int i;
+	unsigned char buf[512];
+	unsigned long flags;
+	int retval;
+	
+	spin_lock_irqsave(&hwc_data.lock, flags);
+
+        i = vsprintf(buf, fmt, args);
+	va_end(args);
+	retval = do_hwc_write(0, buf, i, CODE_ASCII, 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,%0) \n\t"
+			     "LRA 2, 0(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 char * ext_int_param(void)
+{
+	u32 param;
+
+	__asm__ __volatile__("L %0,128(0,0)\n\t"
+			     :"=r"(param));
+
+	return ((unsigned char *) 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));
+	
+	if (!hwc_data.write_nonprio && hwc_data.write_prio)
+		hwcb->msgbuf.type = ET_PMsgCmd;
+
+	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,%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%x. 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 int)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,%0)\n\t"
+			"LRA 3,0(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);
+	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)
+{
+	unsigned short int condition_code;
+	int retval;
+
+	if ((!hwc_data.write_prio) && (!hwc_data.write_nonprio))
+		return -EPERM;
+
+	if (hwc_data.current_servc)
+		return -EBUSY;
+	
+	retval = sane_write_hwcb();
+	if (retval < 0)
+		return retval;
+	if (!OUT_HWCB_MTO)
+		return -ENODATA;
+	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,%0)\n\t"
+			"LRA 3,0(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;
+		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 &= ~FLUSH_HWCBS;
+}
+
+static int write_event_data_2(void)
+{
+	write_hwcb_t *hwcb;
+	int retval;
+	unsigned char *param;
+
+	param = ext_int_param();
+	if (param != hwc_data.current_hwcb)
+		return -EINVAL;
+
+	hwcb = (write_hwcb_t *) OUT_HWCB;
+
+#ifdef DUMP_HWC_WRITE_ERROR 
+#if 0
+	if (((unsigned char *) hwcb) != param)
+		__asm__("LHI 1,0xe22\n\t"
+			"LRA 2,0(0,%0)\n\t"
+			"LRA 3,0(0,%1)\n\t"
+			"LRA 4,0(0,%2)\n\t"
+			"LRA 5,0(0,%3)\n\t"
+			"J .+0 \n\t"
+			:
+			:"a"(OUT_HWCB),
+			 "a"(hwc_data.current_hwcb),
+			 "a"(BUF_HWCB),
+			 "a"(param)
+			:"1", "2", "3", "4", "5");
+#endif
+	if (hwcb->response_code != 0x0020)
+#if 0
+		internal_print(DELAYED_WRITE, HWC_RW_PRINT_HEADER
+		"\n************************ error in write_event_data_2()\n"
+		"OUT_HWCB:                    0x%x\n"
+		"BUF_HWCB:                    0x%x\n"
+		"response_code:               0x%x\n"
+		"hwc_data.hwcb_count:        %d\n"
+		"hwc_data.kmem_pages:        0x%x\n"
+		"hwc_data.ioctls.kmem_hwcb:   %d\n"
+		"hwc_data.ioctls.max_hwcb:    %d\n"
+		"hwc_data.kmem_start:        0x%x\n"
+		"hwc_data.kmem_end:          0x%x\n"
+		"*****************************************************\n",
+		OUT_HWCB,
+		BUF_HWCB,
+		hwcb->response_code,
+		hwc_data.hwcb_count,
+		hwc_data.kmem_pages,
+		hwc_data.ioctls.kmem_hwcb,
+		hwc_data.ioctls.max_hwcb,
+		hwc_data.kmem_start,
+		hwc_data.kmem_end);
+#endif
+		__asm__("LHI 1,0xe21\n\t"
+			"LRA 2,0(0,%0)\n\t"
+			"LRA 3,0(0,%1)\n\t"
+			"LRA 4,0(0,%2)\n\t"
+			"LH 5,0(0,%3)\n\t"
+			"SRL 5,8(0)\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	
+
+	
+	if (hwcb->response_code == 0x0020) {
+		retval = OUT_HWCB_CHAR;
+		release_write_hwcb();
+	} else retval = -EIO;	
+
+	hwc_data.current_servc = 0;
+	hwc_data.current_hwcb = NULL;
+
+	if (hwc_data.flags & FLUSH_HWCBS)
+		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,%0)\n\t"
+				"L 3,0(0,%1)\n\t"
+				"LRA 4,0(0,%2)\n\t"
+				"LRA 5,0(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_TIMER_RUNS)) {
+		del_timer(&hwc_data.write_timer);
+		hwc_data.flags &= ~HWC_TIMER_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,
+	const unsigned char *msg,
+	unsigned int count,
+	unsigned char code,
+	unsigned char write_time)
+{
+	unsigned int i_msg = 0;
+	unsigned short int spaces = 0;
+	unsigned int processed_characters = 0;
+	unsigned char ch, orig_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(orig_ch, msg + i_msg);
+		else
+			orig_ch = msg[i_msg];
+		if (code == CODE_EBCDIC)
+			ch = _ebcasc[orig_ch];
+		else	ch = orig_ch;
+
+		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]
+						= 0x20;  
+					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]
+					= 0x20;  
+				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++]
+					 = (code == CODE_ASCII) ?
+					   _ascebc[orig_ch]:orig_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_TIMER_RUNS) {
+					hwc_data.write_timer.expires =
+						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_TIMER_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, msg, count, hwc_data.ioctls.code,
+							 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 |= FLUSH_HWCBS;
+	}
+	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);
+	if (hwc_data.ioctls.echo)
+		do_hwc_write(0, start, count, CODE_EBCDIC, IMMEDIATE_WRITE);
+	if (hwc_data.ioctls.code == CODE_ASCII)
+		EBCASC(start, count);
+	store_hwc_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;
+}
+
+
+inline 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 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 = -ENOSYS;
+				break;
+			default :
+				printk(
+					KERN_WARNING
+					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 ((!hwc_data.read_prio) && (!hwc_data.read_nonprio))
+		return -EOPNOTSUPP;
+	if (hwc_data.current_servc)
+		return -EBUSY;
+	
+	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,%0)\n\t"
+			"LRA 3,0(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(void)
+{
+	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,%0)\n\t"
+			"L 3,0(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 : 
+		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 - this "
+			"must not occur in a correct driver, please contact "
+			"author\n");
+		return -EIO;
+
+	   case 0x70F0 :
+		internal_print(
+			IMMEDIATE_WRITE,
+			HWC_RW_PRINT_HEADER
+			"unconditional read: invalid selection mask - this "
+			"must not occur in a correct driver, please contact "
+			"author\n");
+		return -EIO;
+
+	   case 0x0040 :
+		internal_print(
+			IMMEDIATE_WRITE,
+			HWC_RW_PRINT_HEADER
+			"unconditional read: HWC equipment check - don't "
+			"know how to handle this case\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;
+	
+	memcpy(hwc_data.page, &init_hwcb_template, sizeof(init_hwcb_t));
+	condition_code = service_call(HWC_CMDW_WRITEMASK, hwc_data.page);
+#ifdef DUMP_HWC_INIT_ERROR
+	if (condition_code != HWC_COMMAND_INITIATED)
+		__asm__("LHI 1,0xe10\n\t"
+			"L 2,0(0,%0)\n\t"
+			"LRA 3,0(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(void)
+{
+	init_hwcb_t *hwcb = (init_hwcb_t *) hwc_data.page;
+	int retval = 0;
+
+	
+	if (hwcb->hwc_receive_mask & ET_Msg_Mask)
+		hwc_data.write_nonprio = 1;
+	
+	if (hwcb->hwc_receive_mask & ET_PMsgCmd_Mask)
+		hwc_data.write_prio = 1;
+	
+	if (hwcb->hwc_send_mask & ET_OpCmd_Mask)
+		hwc_data.read_nonprio = 1;
+	
+	if (hwcb->hwc_send_mask & ET_PMsgCmd_Mask)
+		hwc_data.read_nonprio = 1;
+
+	if ((hwcb->response_code != 0x0020) ||
+	    (!hwc_data.write_nonprio) ||
+	    ((!hwc_data.read_nonprio) && (!hwc_data.read_prio)))
+#ifdef DUMP_HWC_INIT_ERROR
+		__asm__("LHI 1,0xe11\n\t"
+			"LRA 2,0(0,%0)\n\t"
+			"L 3,0(0,%1)\n\t"
+			"J .+0\n\t"
+			:
+			:"a"(hwcb), "a"(&(hwcb->response_code))
+			:"1", "2", "3");
+#else
+		retval = -EIO
+#endif	
+
+	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;
+
+	
+	switch (ioctls->code) {
+		case CODE_EBCDIC :
+		case CODE_ASCII :
+			tmp.code = ioctls->code;
+			break;
+		default :
+			if (correct)
+				tmp.code = CODE_ASCII;
+			else	retval = -EINVAL;
+	}
+				
+	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 hwc_init(unsigned long *kmem_start)
+{
+	int retval;
+#ifdef BUFFER_STRESS_TEST
+
+	init_hwcb_t *hwcb;
+	int i;
+
+#endif
+#ifdef CONFIG_3215
+        if (MACHINE_IS_VM)
+                return kmem_start;
+#endif
+	spin_lock_init(&hwc_data.lock);
+	retval = write_event_mask_1();
+	if (retval < 0)
+		return retval;
+#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);
+
+	*kmem_start = (*kmem_start + PAGE_SIZE - 1) & -4096L;
+	hwc_data.kmem_start = *kmem_start;
+	*kmem_start += hwc_data.ioctls.kmem_hwcb * PAGE_SIZE;
+	hwc_data.kmem_end = *kmem_start - 1;
+
+        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;
+}
+
+void do_hwc_interrupt(void)
+{
+	spin_lock(&hwc_data.lock);
+	if (!hwc_data.current_servc) {
+		unconditional_read_1();
+	} else {
+		switch (hwc_data.current_servc) {
+			case HWC_CMDW_WRITEMASK :
+				write_event_mask_2();
+				break;
+			
+			case HWC_CMDW_WRITEDATA :
+				write_event_data_2();
+				break;
+
+			case HWC_CMDW_READDATA :
+				unconditional_read_2();
+				break;
+		}		
+		write_event_data_1();
+	}
+	wake_up_hwc_tty();
+	spin_unlock(&hwc_data.lock);
+}
+
+
+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 TIOCHWCSCODE :
+                if (get_user(tmp.code, (ioctl_code_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 TIOCHWCGCODE :
+                if (put_user(tmp.code, (ioctl_code_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;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)