patch-2.4.21 linux-2.4.21/include/asm-s390x/idals.h

Next file: linux-2.4.21/include/asm-s390x/ide.h
Previous file: linux-2.4.21/include/asm-s390x/ebcdic.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.20/include/asm-s390x/idals.h linux-2.4.21/include/asm-s390x/idals.h
@@ -1,74 +1,258 @@
 /* 
-   * File...........: linux/include/asm-s390x/idals.h
-   * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
-   * Bugreports.to..: <Linux390@de.ibm.com>
-   * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000a
-   
-   * History of changes
-   * 07/24/00 new file
+ * File...........: linux/include/asm-s390x/idals.h
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000a
+ 
+ * History of changes
+ * 07/24/00 new file
+ * 05/04/02 code restructuring.
  */
+
+#ifndef _S390_IDALS_H
+#define _S390_IDALS_H
+
 #include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
 #include <asm/irq.h>
+#include <asm/uaccess.h>
 
+#ifdef CONFIG_ARCH_S390X
 #define IDA_SIZE_LOG 12 /* 11 for 2k , 12 for 4k */
+#else
+#define IDA_SIZE_LOG 11 /* 11 for 2k , 12 for 4k */
+#endif
 #define IDA_BLOCK_SIZE (1L<<IDA_SIZE_LOG)
 
-static inline addr_t *
-idal_alloc ( int nridaws )
+/*
+ * Test if an address/length pair needs an idal list.
+ */
+static inline int
+idal_is_needed(void *vaddr, unsigned int length)
 {
-	if ( nridaws > 33 )
-		BUG();
-	return kmalloc(nridaws * sizeof(addr_t), GFP_ATOMIC | GFP_DMA );
+#if defined(CONFIG_ARCH_S390X)
+	return ((__pa(vaddr) + length) >> 31) != 0;
+#else
+	return 0;
+#endif
 }
 
-static inline void 
-idal_free ( addr_t *idal )
+
+/*
+ * Return the number of idal words needed for an address/length pair.
+ */
+static inline unsigned int
+idal_nr_words(void *vaddr, unsigned int length)
 {
-	kfree (idal);
+#if defined(CONFIG_ARCH_S390X)
+	if (idal_is_needed(vaddr, length))
+		return ((__pa(vaddr) & (IDA_BLOCK_SIZE-1)) + length + 
+			(IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG;
+#endif
+	return 0;
 }
 
+/*
+ * Create the list of idal words for an address/length pair.
+ */
+static inline unsigned long *
+idal_create_words(unsigned long *idaws, void *vaddr, unsigned int length)
+{
 #if defined(CONFIG_ARCH_S390X)
-extern unsigned long __create_idal(unsigned long address, int count);
+	unsigned long paddr;
+	unsigned int cidaw;
+
+	paddr = __pa(vaddr);
+	cidaw = ((paddr & (IDA_BLOCK_SIZE-1)) + length + 
+		 (IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG;
+	*idaws++ = paddr;
+	paddr &= -IDA_BLOCK_SIZE;
+	while (--cidaw > 0) {
+		paddr += IDA_BLOCK_SIZE;
+		*idaws++ = paddr;
+	}
 #endif
+	return idaws;
+}
 
 /*
- * Function: set_normalized_cda
- * sets the address of the data in CCW
- * if necessary it allocates an IDAL and sets sthe appropriate flags
+ * Sets the address of the data in CCW.
+ * If necessary it allocates an IDAL and sets the appropriate flags.
  */
 static inline int
-set_normalized_cda(ccw1_t * ccw, unsigned long address)
+set_normalized_cda(ccw1_t * ccw, void *vaddr)
 {
-	int ret = 0;
-
 #if defined (CONFIG_ARCH_S390X)
-	if (((address + ccw->count) >> 31) != 0) {
-		if (ccw->flags & CCW_FLAG_IDA)
-			BUG();
-		address = __create_idal(address, ccw->count);
-		if (address)
-			ccw->flags |= CCW_FLAG_IDA;
-		else
-			ret = -ENOMEM;
+	unsigned int nridaws;
+	unsigned long *idal;
+
+	if (ccw->flags & CCW_FLAG_IDA)
+		return -EINVAL;
+	nridaws = idal_nr_words(vaddr, ccw->count);
+	if (nridaws > 0) {
+		idal = kmalloc(nridaws * sizeof(unsigned long),
+			       GFP_ATOMIC | GFP_DMA );
+		if (idal == NULL)
+			return -ENOMEM;
+		idal_create_words(idal, vaddr, ccw->count);
+		ccw->flags |= CCW_FLAG_IDA;
+		vaddr = idal;
 	}
 #endif
-	ccw->cda = (__u32) address;
-	return ret;
+	ccw->cda = (__u32)(unsigned long) vaddr;
+	return 0;
 }
 
 /*
- * Function: clear_normalized_cda
- * releases any allocated IDAL related to the CCW
+ * Releases any allocated IDAL related to the CCW.
  */
 static inline void
-clear_normalized_cda ( ccw1_t * ccw ) 
+clear_normalized_cda(ccw1_t * ccw)
 {
 #if defined(CONFIG_ARCH_S390X)
-	if ( ccw -> flags & CCW_FLAG_IDA ) {
-		idal_free ( (addr_t *)(unsigned long) (ccw -> cda ));
-		ccw -> flags &= ~CCW_FLAG_IDA;
+	if (ccw->flags & CCW_FLAG_IDA) {
+		kfree((void *)(unsigned long) ccw->cda);
+		ccw->flags &= ~CCW_FLAG_IDA;
 	}
 #endif
-	ccw -> cda = 0;
+	ccw->cda = 0;
 }
 
+/*
+ * Idal buffer extension
+ */
+struct idal_buffer {
+	size_t size;
+	size_t page_order;
+	void *data[0];
+};
+
+/*
+ * Allocate an idal buffer
+ */
+static inline struct idal_buffer *
+idal_buffer_alloc(size_t size, int page_order)
+{
+	struct idal_buffer *ib;
+	int nr_chunks, nr_ptrs, i;
+
+	nr_ptrs = (size + IDA_BLOCK_SIZE - 1) >> IDA_SIZE_LOG;
+	nr_chunks = (4096 << page_order) >> IDA_SIZE_LOG;
+	ib = kmalloc(sizeof(struct idal_buffer) + nr_ptrs*sizeof(void *),
+		     GFP_DMA | GFP_KERNEL);
+	if (ib == NULL)
+		return ERR_PTR(-ENOMEM);
+	ib->size = size;
+	ib->page_order = page_order;
+	for (i = 0; i < nr_ptrs; i++) {
+		if ((i & (nr_chunks - 1)) != 0) {
+			ib->data[i] = ib->data[i-1] + IDA_BLOCK_SIZE;
+			continue;
+		}
+		ib->data[i] = (void *)
+			__get_free_pages(GFP_KERNEL, page_order);
+		if (ib->data[i] != NULL)
+			continue;
+		// Not enough memory
+		while (i >= nr_chunks) {
+			i -= nr_chunks;
+			free_pages((unsigned long) ib->data[i],
+				   ib->page_order);
+		}
+		kfree(ib);
+		return ERR_PTR(-ENOMEM);
+	}
+	return ib;
+}
+
+/*
+ * Free an idal buffer.
+ */
+static inline void
+idal_buffer_free(struct idal_buffer *ib)
+{
+	int nr_chunks, nr_ptrs, i;
+
+	nr_ptrs = (ib->size + IDA_BLOCK_SIZE - 1) >> IDA_SIZE_LOG;
+	nr_chunks = (4096 << ib->page_order) >> IDA_SIZE_LOG;
+	for (i = 0; i < nr_ptrs; i += nr_chunks)
+		free_pages((unsigned long) ib->data[i], ib->page_order);
+	kfree(ib);
+}
+
+/*
+ * Test if a idal list is really needed.
+ */
+static inline int
+__idal_buffer_is_needed(struct idal_buffer *ib)
+{
+#ifdef CONFIG_ARCH_S390X
+	return ib->size > (4096 << ib->page_order) ||
+		idal_is_needed(ib->data[0], ib->size);
+#else
+	return ib->size > (4096 << ib->page_order);
+#endif
+}
+
+/*
+ * Set channel data address to idal buffer.
+ */
+static inline void
+idal_buffer_set_cda(struct idal_buffer *ib, ccw1_t *ccw)
+{
+	if (__idal_buffer_is_needed(ib)) {
+		// setup idals;
+		ccw->cda = (u32)(addr_t) ib->data;
+		ccw->flags |= CCW_FLAG_IDA;
+	} else
+		// we do not need idals - use direct addressing
+		ccw->cda = (u32)(addr_t) ib->data[0];
+	ccw->count = ib->size;
+}
+
+/*
+ * Copy count bytes from an idal buffer to user memory
+ */
+static inline size_t
+idal_buffer_to_user(struct idal_buffer *ib, void *to, size_t count)
+{
+	size_t left;
+	int i;
+
+	if (count > ib->size)
+		BUG();
+	for (i = 0; count > IDA_BLOCK_SIZE; i++) {
+		left = copy_to_user(to, ib->data[i], IDA_BLOCK_SIZE);
+		if (left)
+			return left + count - IDA_BLOCK_SIZE;
+		(addr_t) to += IDA_BLOCK_SIZE;
+		count -= IDA_BLOCK_SIZE;
+	}
+	return copy_to_user(to, ib->data[i], count);
+}
+
+/*
+ * Copy count bytes from user memory to an idal buffer
+ */
+static inline size_t
+idal_buffer_from_user(struct idal_buffer *ib, void *from, size_t count)
+{
+	size_t left;
+	int i;
+
+	if (count > ib->size)
+		BUG();
+	for (i = 0; count > IDA_BLOCK_SIZE; i++) {
+		left = copy_from_user(ib->data[i], from, IDA_BLOCK_SIZE);
+		if (left)
+			return left + count - IDA_BLOCK_SIZE;
+		(addr_t) from += IDA_BLOCK_SIZE;
+		count -= IDA_BLOCK_SIZE;
+	}
+	return copy_from_user(ib->data[i], from, count);
+}
+
+#endif

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