18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> 48c2ecf20Sopenharmony_ci * Martin Schwidefsky <schwidefsky@de.ibm.com> 58c2ecf20Sopenharmony_ci * Bugreports.to..: <Linux390@de.ibm.com> 68c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2000 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * History of changes 98c2ecf20Sopenharmony_ci * 07/24/00 new file 108c2ecf20Sopenharmony_ci * 05/04/02 code restructuring. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#ifndef _S390_IDALS_H 148c2ecf20Sopenharmony_ci#define _S390_IDALS_H 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/errno.h> 178c2ecf20Sopenharmony_ci#include <linux/err.h> 188c2ecf20Sopenharmony_ci#include <linux/types.h> 198c2ecf20Sopenharmony_ci#include <linux/slab.h> 208c2ecf20Sopenharmony_ci#include <asm/cio.h> 218c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define IDA_SIZE_LOG 12 /* 11 for 2k , 12 for 4k */ 248c2ecf20Sopenharmony_ci#define IDA_BLOCK_SIZE (1L<<IDA_SIZE_LOG) 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci/* 278c2ecf20Sopenharmony_ci * Test if an address/length pair needs an idal list. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_cistatic inline int 308c2ecf20Sopenharmony_ciidal_is_needed(void *vaddr, unsigned int length) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci return ((__pa(vaddr) + length - 1) >> 31) != 0; 338c2ecf20Sopenharmony_ci} 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci/* 378c2ecf20Sopenharmony_ci * Return the number of idal words needed for an address/length pair. 388c2ecf20Sopenharmony_ci */ 398c2ecf20Sopenharmony_cistatic inline unsigned int idal_nr_words(void *vaddr, unsigned int length) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci return ((__pa(vaddr) & (IDA_BLOCK_SIZE-1)) + length + 428c2ecf20Sopenharmony_ci (IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG; 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* 468c2ecf20Sopenharmony_ci * Create the list of idal words for an address/length pair. 478c2ecf20Sopenharmony_ci */ 488c2ecf20Sopenharmony_cistatic inline unsigned long *idal_create_words(unsigned long *idaws, 498c2ecf20Sopenharmony_ci void *vaddr, unsigned int length) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci unsigned long paddr; 528c2ecf20Sopenharmony_ci unsigned int cidaw; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci paddr = __pa(vaddr); 558c2ecf20Sopenharmony_ci cidaw = ((paddr & (IDA_BLOCK_SIZE-1)) + length + 568c2ecf20Sopenharmony_ci (IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG; 578c2ecf20Sopenharmony_ci *idaws++ = paddr; 588c2ecf20Sopenharmony_ci paddr &= -IDA_BLOCK_SIZE; 598c2ecf20Sopenharmony_ci while (--cidaw > 0) { 608c2ecf20Sopenharmony_ci paddr += IDA_BLOCK_SIZE; 618c2ecf20Sopenharmony_ci *idaws++ = paddr; 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci return idaws; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci/* 678c2ecf20Sopenharmony_ci * Sets the address of the data in CCW. 688c2ecf20Sopenharmony_ci * If necessary it allocates an IDAL and sets the appropriate flags. 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_cistatic inline int 718c2ecf20Sopenharmony_ciset_normalized_cda(struct ccw1 * ccw, void *vaddr) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci unsigned int nridaws; 748c2ecf20Sopenharmony_ci unsigned long *idal; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (ccw->flags & CCW_FLAG_IDA) 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci nridaws = idal_nr_words(vaddr, ccw->count); 798c2ecf20Sopenharmony_ci if (nridaws > 0) { 808c2ecf20Sopenharmony_ci idal = kmalloc(nridaws * sizeof(unsigned long), 818c2ecf20Sopenharmony_ci GFP_ATOMIC | GFP_DMA ); 828c2ecf20Sopenharmony_ci if (idal == NULL) 838c2ecf20Sopenharmony_ci return -ENOMEM; 848c2ecf20Sopenharmony_ci idal_create_words(idal, vaddr, ccw->count); 858c2ecf20Sopenharmony_ci ccw->flags |= CCW_FLAG_IDA; 868c2ecf20Sopenharmony_ci vaddr = idal; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci ccw->cda = (__u32)(unsigned long) vaddr; 898c2ecf20Sopenharmony_ci return 0; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci/* 938c2ecf20Sopenharmony_ci * Releases any allocated IDAL related to the CCW. 948c2ecf20Sopenharmony_ci */ 958c2ecf20Sopenharmony_cistatic inline void 968c2ecf20Sopenharmony_ciclear_normalized_cda(struct ccw1 * ccw) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci if (ccw->flags & CCW_FLAG_IDA) { 998c2ecf20Sopenharmony_ci kfree((void *)(unsigned long) ccw->cda); 1008c2ecf20Sopenharmony_ci ccw->flags &= ~CCW_FLAG_IDA; 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci ccw->cda = 0; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci/* 1068c2ecf20Sopenharmony_ci * Idal buffer extension 1078c2ecf20Sopenharmony_ci */ 1088c2ecf20Sopenharmony_cistruct idal_buffer { 1098c2ecf20Sopenharmony_ci size_t size; 1108c2ecf20Sopenharmony_ci size_t page_order; 1118c2ecf20Sopenharmony_ci void *data[0]; 1128c2ecf20Sopenharmony_ci}; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci/* 1158c2ecf20Sopenharmony_ci * Allocate an idal buffer 1168c2ecf20Sopenharmony_ci */ 1178c2ecf20Sopenharmony_cistatic inline struct idal_buffer * 1188c2ecf20Sopenharmony_ciidal_buffer_alloc(size_t size, int page_order) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci struct idal_buffer *ib; 1218c2ecf20Sopenharmony_ci int nr_chunks, nr_ptrs, i; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci nr_ptrs = (size + IDA_BLOCK_SIZE - 1) >> IDA_SIZE_LOG; 1248c2ecf20Sopenharmony_ci nr_chunks = (4096 << page_order) >> IDA_SIZE_LOG; 1258c2ecf20Sopenharmony_ci ib = kmalloc(struct_size(ib, data, nr_ptrs), GFP_DMA | GFP_KERNEL); 1268c2ecf20Sopenharmony_ci if (ib == NULL) 1278c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1288c2ecf20Sopenharmony_ci ib->size = size; 1298c2ecf20Sopenharmony_ci ib->page_order = page_order; 1308c2ecf20Sopenharmony_ci for (i = 0; i < nr_ptrs; i++) { 1318c2ecf20Sopenharmony_ci if ((i & (nr_chunks - 1)) != 0) { 1328c2ecf20Sopenharmony_ci ib->data[i] = ib->data[i-1] + IDA_BLOCK_SIZE; 1338c2ecf20Sopenharmony_ci continue; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci ib->data[i] = (void *) 1368c2ecf20Sopenharmony_ci __get_free_pages(GFP_KERNEL, page_order); 1378c2ecf20Sopenharmony_ci if (ib->data[i] != NULL) 1388c2ecf20Sopenharmony_ci continue; 1398c2ecf20Sopenharmony_ci // Not enough memory 1408c2ecf20Sopenharmony_ci while (i >= nr_chunks) { 1418c2ecf20Sopenharmony_ci i -= nr_chunks; 1428c2ecf20Sopenharmony_ci free_pages((unsigned long) ib->data[i], 1438c2ecf20Sopenharmony_ci ib->page_order); 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci kfree(ib); 1468c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci return ib; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci/* 1528c2ecf20Sopenharmony_ci * Free an idal buffer. 1538c2ecf20Sopenharmony_ci */ 1548c2ecf20Sopenharmony_cistatic inline void 1558c2ecf20Sopenharmony_ciidal_buffer_free(struct idal_buffer *ib) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci int nr_chunks, nr_ptrs, i; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci nr_ptrs = (ib->size + IDA_BLOCK_SIZE - 1) >> IDA_SIZE_LOG; 1608c2ecf20Sopenharmony_ci nr_chunks = (4096 << ib->page_order) >> IDA_SIZE_LOG; 1618c2ecf20Sopenharmony_ci for (i = 0; i < nr_ptrs; i += nr_chunks) 1628c2ecf20Sopenharmony_ci free_pages((unsigned long) ib->data[i], ib->page_order); 1638c2ecf20Sopenharmony_ci kfree(ib); 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci/* 1678c2ecf20Sopenharmony_ci * Test if a idal list is really needed. 1688c2ecf20Sopenharmony_ci */ 1698c2ecf20Sopenharmony_cistatic inline int 1708c2ecf20Sopenharmony_ci__idal_buffer_is_needed(struct idal_buffer *ib) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci return ib->size > (4096ul << ib->page_order) || 1738c2ecf20Sopenharmony_ci idal_is_needed(ib->data[0], ib->size); 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci/* 1778c2ecf20Sopenharmony_ci * Set channel data address to idal buffer. 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_cistatic inline void 1808c2ecf20Sopenharmony_ciidal_buffer_set_cda(struct idal_buffer *ib, struct ccw1 *ccw) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci if (__idal_buffer_is_needed(ib)) { 1838c2ecf20Sopenharmony_ci // setup idals; 1848c2ecf20Sopenharmony_ci ccw->cda = (u32)(addr_t) ib->data; 1858c2ecf20Sopenharmony_ci ccw->flags |= CCW_FLAG_IDA; 1868c2ecf20Sopenharmony_ci } else 1878c2ecf20Sopenharmony_ci // we do not need idals - use direct addressing 1888c2ecf20Sopenharmony_ci ccw->cda = (u32)(addr_t) ib->data[0]; 1898c2ecf20Sopenharmony_ci ccw->count = ib->size; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci/* 1938c2ecf20Sopenharmony_ci * Copy count bytes from an idal buffer to user memory 1948c2ecf20Sopenharmony_ci */ 1958c2ecf20Sopenharmony_cistatic inline size_t 1968c2ecf20Sopenharmony_ciidal_buffer_to_user(struct idal_buffer *ib, void __user *to, size_t count) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci size_t left; 1998c2ecf20Sopenharmony_ci int i; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci BUG_ON(count > ib->size); 2028c2ecf20Sopenharmony_ci for (i = 0; count > IDA_BLOCK_SIZE; i++) { 2038c2ecf20Sopenharmony_ci left = copy_to_user(to, ib->data[i], IDA_BLOCK_SIZE); 2048c2ecf20Sopenharmony_ci if (left) 2058c2ecf20Sopenharmony_ci return left + count - IDA_BLOCK_SIZE; 2068c2ecf20Sopenharmony_ci to = (void __user *) to + IDA_BLOCK_SIZE; 2078c2ecf20Sopenharmony_ci count -= IDA_BLOCK_SIZE; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci return copy_to_user(to, ib->data[i], count); 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci/* 2138c2ecf20Sopenharmony_ci * Copy count bytes from user memory to an idal buffer 2148c2ecf20Sopenharmony_ci */ 2158c2ecf20Sopenharmony_cistatic inline size_t 2168c2ecf20Sopenharmony_ciidal_buffer_from_user(struct idal_buffer *ib, const void __user *from, size_t count) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci size_t left; 2198c2ecf20Sopenharmony_ci int i; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci BUG_ON(count > ib->size); 2228c2ecf20Sopenharmony_ci for (i = 0; count > IDA_BLOCK_SIZE; i++) { 2238c2ecf20Sopenharmony_ci left = copy_from_user(ib->data[i], from, IDA_BLOCK_SIZE); 2248c2ecf20Sopenharmony_ci if (left) 2258c2ecf20Sopenharmony_ci return left + count - IDA_BLOCK_SIZE; 2268c2ecf20Sopenharmony_ci from = (void __user *) from + IDA_BLOCK_SIZE; 2278c2ecf20Sopenharmony_ci count -= IDA_BLOCK_SIZE; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci return copy_from_user(ib->data[i], from, count); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci#endif 233