linux/arch/s390/include/asm/idals.h
Heiko Carstens a53c8fab3f s390/comments: unify copyright messages and remove file names
Remove the file name from the comment at top of many files. In most
cases the file name was wrong anyway, so it's rather pointless.

Also unify the IBM copyright statement. We did have a lot of sightly
different statements and wanted to change them one after another
whenever a file gets touched. However that never happened. Instead
people start to take the old/"wrong" statements to use as a template
for new files.
So unify all of them in one go.

Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
2012-07-20 11:15:04 +02:00

249 lines
5.4 KiB
C

/*
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* Copyright IBM Corp. 2000
*
* History of changes
* 07/24/00 new file
* 05/04/02 code restructuring.
*/
#ifndef _S390_IDALS_H
#define _S390_IDALS_H
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <asm/cio.h>
#include <asm/uaccess.h>
#ifdef CONFIG_64BIT
#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)
/*
* Test if an address/length pair needs an idal list.
*/
static inline int
idal_is_needed(void *vaddr, unsigned int length)
{
#ifdef CONFIG_64BIT
return ((__pa(vaddr) + length - 1) >> 31) != 0;
#else
return 0;
#endif
}
/*
* Return the number of idal words needed for an address/length pair.
*/
static inline unsigned int idal_nr_words(void *vaddr, unsigned int length)
{
return ((__pa(vaddr) & (IDA_BLOCK_SIZE-1)) + length +
(IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG;
}
/*
* 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)
{
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;
}
return idaws;
}
/*
* 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(struct ccw1 * ccw, void *vaddr)
{
#ifdef CONFIG_64BIT
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)(unsigned long) vaddr;
return 0;
}
/*
* Releases any allocated IDAL related to the CCW.
*/
static inline void
clear_normalized_cda(struct ccw1 * ccw)
{
#ifdef CONFIG_64BIT
if (ccw->flags & CCW_FLAG_IDA) {
kfree((void *)(unsigned long) ccw->cda);
ccw->flags &= ~CCW_FLAG_IDA;
}
#endif
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_64BIT
return ib->size > (4096ul << ib->page_order) ||
idal_is_needed(ib->data[0], ib->size);
#else
return ib->size > (4096ul << ib->page_order);
#endif
}
/*
* Set channel data address to idal buffer.
*/
static inline void
idal_buffer_set_cda(struct idal_buffer *ib, struct ccw1 *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 __user *to, size_t count)
{
size_t left;
int i;
BUG_ON(count > ib->size);
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;
to = (void __user *) 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, const void __user *from, size_t count)
{
size_t left;
int i;
BUG_ON(count > ib->size);
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;
from = (void __user *) from + IDA_BLOCK_SIZE;
count -= IDA_BLOCK_SIZE;
}
return copy_from_user(ib->data[i], from, count);
}
#endif