Purpose of this patch: establish binary compatibility with 32 bit x86 code on
AMD64 64 bit kernels, in the use of struct sg_io_hdr via write()/read() calls.
Currently this binary compatibility only exists with ioctl().
32bit applications which use or are linked with sanei_scsi.c of the SANE project
(sane-project.org) need this patch.

Tested: on AMD64, SUSE Linux 10.0 kernel, with
	a commercial 32 bit scanning application
	64 bit cdrecord

Thanks are due to: Abel Deuring, Henning Meier-Geinitz, Dieter Jurzitza - all
sane-devel mailing list.

TODO:
	remove debugging code (currently #ifdef'ed out)
	optimise implementation?
	how many other 32/64 bit architectures does this apply to?

Volker Kuhlmann, <VolkerKuhlmann,gmx.de>, 13 Jan 2006


SCSI SG DRIVER
P:	Doug Gilbert
M:	dgilbert@interlog.com
L:	linux-scsi@vger.kernel.org
W:	http://www.torque.net/sg
S:	Maintained


--- linux-2.6.13-15.7/drivers/scsi/sg.c.orig	2005-11-30 09:35:27.000000000 +1300
+++ linux-2.6.13-15.7/drivers/scsi/sg.c	2006-01-13 11:17:50.000000000 +1300
@@ -11,6 +11,9 @@
  *
  *  Modified  19-JAN-1998  Richard Gooch <rgooch@atnf.csiro.au>  Devfs support
  *
+ *  Modified 13 Jan 2006 Volker Kuhlmann <coding,top.geek.nz>
+ *    - 32 bit binary compatibility on AMD64 for read()/write() sg_io_hdr.
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
@@ -18,8 +21,8 @@
  *
  */
 
-static int sg_version_num = 30533;	/* 2 digits for each component */
-#define SG_VERSION_STR "3.5.33"
+static int sg_version_num = 30534;	/* 2 digits for each component */
+#define SG_VERSION_STR "3.5.34"
 
 /*
  *  D. P. Gilbert (dgilbert@interlog.com, dougg@triode.net.au), notes:
@@ -49,6 +52,7 @@ static int sg_version_num = 30533;	/* 2 
 #include <linux/seq_file.h>
 #include <linux/blkdev.h>
 #include <linux/delay.h>
+#include <linux/compat.h>
 
 #include "scsi.h"
 #include <scsi/scsi_dbg.h>
@@ -61,7 +65,7 @@ static int sg_version_num = 30533;	/* 2 
 
 #ifdef CONFIG_SCSI_PROC_FS
 #include <linux/proc_fs.h>
-static char *sg_version_date = "20050328";
+static char *sg_version_date = "20060113";
 
 static int sg_proc_init(void);
 static void sg_proc_cleanup(void);
@@ -176,6 +180,45 @@ typedef struct sg_device { /* holds the 
 	struct cdev * cdev;	/* char_dev [sysfs: /sys/cdev/major/sg<n>] */
 } Sg_device;
 
+/* Define this for debugging output of the 32 bit emulation of sg_io_hdr on
+   AMD64. Logs to syslog. */
+//#define DBG_IO32_NEW 1
+
+/* Replication of struct sg_io_hdr from scsi/sg.h, but with the memory layout
+   of a x86 32 bit architecture, even when compiled on AMD64 64 bit.
+   THIS MUST ALWAYS MATCH a (32 bit architecture) struct sg_io_hdr!!!
+*/
+typedef struct sg_io_hdr32
+{
+    int interface_id;           /* [i] 'S' for SCSI generic (required) */
+    int dxfer_direction;        /* [i] data transfer direction  */
+    unsigned char cmd_len;      /* [i] SCSI command length ( <= 16 bytes) */
+    unsigned char mx_sb_len;    /* [i] max length to write to sbp */
+    unsigned short iovec_count; /* [i] 0 implies no scatter gather */
+    unsigned int dxfer_len;     /* [i] byte count of data transfer */
+    // 16 bytes
+    signed int dxferp; /* pointers are only 4 bytes */
+    signed int cmdp;   /* " */
+    signed int sbp;    /* " */
+    // 12 bytes
+    unsigned int timeout;       /* [i] MAX_UINT->no timeout (unit: millisec) */
+    unsigned int flags;         /* [i] 0 -> default, see SG_FLAG... */
+    int pack_id;                /* [i->o] unused internally (normally) */
+    // 12 bytes
+    signed int usr_ptr; /* pointers are only 4 bytes */
+    // 4 bytes
+    unsigned char status;       /* [o] scsi status */
+    unsigned char masked_status;/* [o] shifted, masked scsi status */
+    unsigned char msg_status;   /* [o] messaging level data (optional) */
+    unsigned char sb_len_wr;    /* [o] byte count actually written to sbp */
+    unsigned short host_status; /* [o] errors from host adapter */
+    unsigned short driver_status;/* [o] errors from software driver */
+    int resid;                  /* [o] dxfer_len - actual_transferred */
+    unsigned int duration;      /* [o] time taken by cmd (unit: millisec) */
+    unsigned int info;          /* [o] auxiliary information */
+    // 20 bytes
+} sg_io_hdr32_t;  /* 64 bytes long (on x86_64!) */
+
 static int sg_fasync(int fd, struct file *filp, int mode);
 static void sg_cmd_done(Scsi_Cmnd * SCpnt);	/* tasklet or soft irq callback */
 static int sg_start_req(Sg_request * srp);
@@ -214,6 +257,8 @@ static inline unsigned char *sg_scatg2vi
 #ifdef CONFIG_SCSI_PROC_FS
 static int sg_last_dev(void);
 #endif
+static inline void copy_hdr32_64 (sg_io_hdr_t * new, sg_io_hdr32_t * hdr);
+static inline void copy_64_hdr32 (sg_io_hdr32_t * new, sg_io_hdr_t * hdr);
 
 static Sg_device **sg_dev_arr = NULL;
 static int sg_dev_max;
@@ -221,6 +266,7 @@ static int sg_nr_dev;
 
 #define SZ_SG_HEADER sizeof(struct sg_header)
 #define SZ_SG_IO_HDR sizeof(sg_io_hdr_t)
+#define SZ_SG_IO32_HDR sizeof(sg_io_hdr32_t)
 #define SZ_SG_IOVEC sizeof(sg_iovec_t)
 #define SZ_SG_REQ_INFO sizeof(sg_req_info_t)
 
@@ -480,6 +526,55 @@ free_old_hdr:
 	return retval;
 }
 
+#ifdef DBG_IO32_NEW
+/* Print out memory ranges hexadecimal */
+static void
+print_mem(const char * str, const void * mem, int lines)
+{
+    const unsigned int * p;
+    int i;
+    
+    p = mem;
+    printk(KERN_WARNING
+    	"%s : %08x %08x %08x %08x %08x %08x %08x %08x\n",
+    	str,
+	*p, *(p+1), *(p+2), *(p+3), *(p+4), *(p+5), *(p+6), *(p+7));
+    for (i = 2; i <= lines; i++) {
+    p += 8;
+    printk(KERN_WARNING
+    	"%s%X: %08x %08x %08x %08x %08x %08x %08x %08x\n",
+    	str, i,
+	*p, *(p+1), *(p+2), *(p+3), *(p+4), *(p+5), *(p+6), *(p+7));
+    }
+}
+#endif
+
+/* Copy a sg_io_hdr32_t (32 bit) to a sg_io_hdr_t (64 bit layout).
+   This must always match struct sg_io_hdr! */
+static inline void
+copy_hdr32_64 (sg_io_hdr_t * new, sg_io_hdr32_t * hdr) {
+    memcpy(new, hdr, 16);
+    new->dxferp = compat_ptr(hdr->dxferp);
+    new->cmdp = compat_ptr(hdr->cmdp);
+    new->sbp = compat_ptr(hdr->sbp);
+    memcpy(&(new->timeout), &(hdr->timeout), 12);
+    new->usr_ptr = compat_ptr(hdr->usr_ptr);
+    memcpy(&(new->status), &(hdr->status), 20);
+}
+
+/* Copy a sg_io_hdr_t (64 bit layout) to a sg_io_hdr32_t (32 bit).
+   This must always match struct sg_io_hdr! */
+static inline void
+copy_64_hdr32 (sg_io_hdr32_t * new, sg_io_hdr_t * hdr) {
+    memcpy(new, hdr, 16);
+    new->dxferp = ptr_to_compat(hdr->dxferp);
+    new->cmdp = ptr_to_compat(hdr->cmdp);
+    new->sbp = ptr_to_compat(hdr->sbp);
+    memcpy(&(new->timeout), &(hdr->timeout), 12);
+    new->usr_ptr = ptr_to_compat(hdr->usr_ptr);
+    memcpy(&(new->status), &(hdr->status), 20);
+}
+
 static ssize_t
 sg_new_read(Sg_fd * sfp, char __user *buf, size_t count, Sg_request * srp)
 {
@@ -488,8 +583,17 @@ sg_new_read(Sg_fd * sfp, char __user *bu
 	int len;
 
 	if (count < SZ_SG_IO_HDR) {
+	    if (count != SZ_SG_IO32_HDR) {
+	    	printk(KERN_WARNING "sg_new_read: count %d != %zu\n",
+		    (int) count, SZ_SG_IO32_HDR);
 		err = -EINVAL;
 		goto err_out;
+	    } else {
+    	    #ifdef DBG_IO32_NEW
+    		printk(KERN_WARNING "sg_new_read: size is %zu, should be %zu\n",
+		    SZ_SG_IO32_HDR, SZ_SG_IO_HDR);
+    	    #endif
+	    }
 	}
 	hp->sb_len_wr = 0;
 	if ((hp->mx_sb_len > 0) && hp->sbp) {
@@ -508,9 +612,32 @@ sg_new_read(Sg_fd * sfp, char __user *bu
 	}
 	if (hp->masked_status || hp->host_status || hp->driver_status)
 		hp->info |= SG_INFO_CHECK;
-	if (copy_to_user(buf, hp, SZ_SG_IO_HDR)) {
-		err = -EFAULT;
-		goto err_out;
+
+	if (count == SZ_SG_IO_HDR) {
+		if (copy_to_user(buf, hp, SZ_SG_IO_HDR)) {
+			err = -EFAULT;
+			goto err_out;
+		}
+	} else {
+		if (copy_to_user(buf, hp, 4)) {/* copy 4 bytes for error checking */
+			err = -EFAULT;
+			goto err_out;
+		}
+	#ifdef DBG_IO32_NEW
+		memset(buf, 0x22, SZ_SG_IO32_HDR);
+    	    	print_mem("sg_r   ", hp, 3);
+	#endif
+		copy_64_hdr32((sg_io_hdr32_t *) buf, hp);
+	#ifdef DBG_IO32_NEW
+    	    	print_mem("sg_rout", buf, 2);
+	#endif
+#if 0
+		if (&(sfp->headrp->header) != NULL) /*holds hdr from write()*/
+		    print_mem("sg_r1  ", &(sfp->headrp->header), 3);
+    	    	if (hp != &sfp->headrp->header) 
+		    print_mem("sg_r2  ", hp, 3);
+		//hdrbuf_r = *((sg_io_hdr_t *) hp);
+#endif
 	}
 	err = sg_read_xfer(srp);
       err_out:
@@ -632,8 +759,18 @@ sg_new_write(Sg_fd * sfp, const char __u
 	int timeout;
 	unsigned long ul_timeout;
 
-	if (count < SZ_SG_IO_HDR)
-		return -EINVAL;
+	if (count < SZ_SG_IO_HDR) {
+	    if (count != SZ_SG_IO32_HDR) {
+	    	printk(KERN_WARNING "sg_new_write: count %d != %zu\n",
+		    (int) count, SZ_SG_IO32_HDR);
+	    	return -EINVAL;
+	    } else {
+    	    #ifdef DBG_IO32_NEW
+    	    	printk(KERN_WARNING "sg_new_write: size is %zu, should be %zu\n",
+		    SZ_SG_IO32_HDR, SZ_SG_IO_HDR);
+	    #endif
+	    }
+	}
 	if (!access_ok(VERIFY_READ, buf, count))
 		return -EFAULT; /* protects following copy_from_user()s + get_user()s */
 
@@ -643,10 +780,31 @@ sg_new_write(Sg_fd * sfp, const char __u
 		return -EDOM;
 	}
 	hp = &srp->header;
-	if (__copy_from_user(hp, buf, SZ_SG_IO_HDR)) {
-		sg_remove_request(sfp, srp);
-		return -EFAULT;
-	}
+	if (count == SZ_SG_IO_HDR) {
+		if (__copy_from_user(hp, buf, SZ_SG_IO_HDR)) {
+			sg_remove_request(sfp, srp);
+			return -EFAULT;
+		}
+    	} else {
+		if (__copy_from_user(hp, buf, 4)) {/* copy 4 bytes for error checking */
+			sg_remove_request(sfp, srp);
+			return -EFAULT;
+		}
+	#ifdef DBG_IO32_NEW
+    		memset(hp, 0x11, sizeof(*hp));
+    		//print_mem("sg_wset", hp, 3);
+    		print_mem("sg_w   ", buf, 2);
+    		//print_mem("", &(sfp->headrp->header), 0); // null deref!
+	#endif
+		copy_hdr32_64(hp, (sg_io_hdr32_t *) buf);
+	#ifdef DBG_IO32_NEW
+		//printk("%016llx, %016llx\n", (long long int) &buf, (long long int) buf);
+		//buf = (const char *) &hdrbuf_w;
+		//printk("%016llx, %016llx\n", (long long int) &buf, (long long int) buf);
+    		print_mem("sg_wnew", hp, 3);
+	#endif
+    	}
+
 	if (hp->interface_id != 'S') {
 		sg_remove_request(sfp, srp);
 		return -ENOSYS;
