aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio/itcw.c
blob: 358ee16d10a2e97375df34fd84fde7b404adcc78 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/*
 *  Functions for incremental construction of fcx enabled I/O control blocks.
 *
 *    Copyright IBM Corp. 2008
 *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/module.h>
#include <asm/fcx.h>
#include <asm/itcw.h>

/**
 * struct itcw - incremental tcw helper data type
 *
 * This structure serves as a handle for the incremental construction of a
 * tcw and associated tccb, tsb, data tidaw-list plus an optional interrogate
 * tcw and associated data. The data structures are contained inside a single
 * contiguous buffer provided by the user.
 *
 * The itcw construction functions take care of overall data integrity:
 * - reset unused fields to zero
 * - fill in required pointers
 * - ensure required alignment for data structures
 * - prevent data structures to cross 4k-byte boundary where required
 * - calculate tccb-related length fields
 * - optionally provide ready-made interrogate tcw and associated structures
 *
 * Restrictions apply to the itcws created with these construction functions:
 * - tida only supported for data address, not for tccb
 * - only contiguous tidaw-lists (no ttic)
 * - total number of bytes required per itcw may not exceed 4k bytes
 * - either read or write operation (may not work with r=0 and w=0)
 *
 * Example:
 * struct itcw *itcw;
 * void *buffer;
 * size_t size;
 *
 * size = itcw_calc_size(1, 2, 0);
 * buffer = kmalloc(size, GFP_KERNEL | GFP_DMA);
 * if (!buffer)
 *	return -ENOMEM;
 * itcw = itcw_init(buffer, size, ITCW_OP_READ, 1, 2, 0);
 * if (IS_ERR(itcw))
 *	return PTR_ER(itcw);
 * itcw_add_dcw(itcw, 0x2, 0, NULL, 0, 72);
 * itcw_add_tidaw(itcw, 0, 0x30000, 20);
 * itcw_add_tidaw(itcw, 0, 0x40000, 52);
 * itcw_finalize(itcw);
 *
 */
struct itcw {
	struct tcw *tcw;
	struct tcw *intrg_tcw;
	int num_tidaws;
	int max_tidaws;
	int intrg_num_tidaws;
	int intrg_max_tidaws;
};

/**
 * itcw_get_tcw - return pointer to tcw associated with the itcw
 * @itcw: address of the itcw
 *
 * Return pointer to the tcw associated with the itcw.
 */
struct tcw *itcw_get_tcw(struct itcw *itcw)
{
	return itcw->tcw;
}
EXPORT_SYMBOL(itcw_get_tcw);

/**
 * itcw_calc_size - return the size of an itcw with the given parameters
 * @intrg: if non-zero, add an interrogate tcw
 * @max_tidaws: maximum number of tidaws to be used for data addressing or zero
 * if no tida is to be used.
 * @intrg_max_tidaws: maximum number of tidaws to be used for data addressing
 * by the interrogate tcw, if specified
 *
 * Calculate and return the number of bytes required to hold an itcw with the
 * given parameters and assuming tccbs with maximum size.
 *
 * Note that the resulting size also contains bytes needed for alignment
 * padding as well as padding to ensure that data structures don't cross a
 * 4k-boundary where required.
 */
size_t itcw_calc_size(int intrg, int max_tidaws, int intrg_max_tidaws)
{
	size_t len;
	int cross_count;

	/* Main data. */
	len = sizeof(struct itcw);
	len += /* TCW */ sizeof(struct tcw) + /* TCCB */ TCCB_MAX_SIZE +
	       /* TSB */ sizeof(struct tsb) +
	       /* TIDAL */ max_tidaws * sizeof(struct tidaw);
	/* Interrogate data. */
	if (intrg) {
		len += /* TCW */ sizeof(struct tcw) + /* TCCB */ TCCB_MAX_SIZE +
		       /* TSB */ sizeof(struct tsb) +
		       /* TIDAL */ intrg_max_tidaws * sizeof(struct tidaw);
	}

	/* Maximum required alignment padding. */
	len += /* Initial TCW */ 63 + /* Interrogate TCCB */ 7;

	/* TIDAW lists may not cross a 4k boundary. To cross a
	 * boundary we need to add a TTIC TIDAW. We need to reserve
	 * one additional TIDAW for a TTIC that we may need to add due
	 * to the placement of the data chunk in memory, and a further
	 * TIDAW for each page boundary that the TIDAW list may cross
	 * due to it's own size.
	 */
	if (max_tidaws) {
		cross_count = 1 + ((max_tidaws * sizeof(struct tidaw) - 1)
				   >> PAGE_SHIFT);
		len += cross_count * sizeof(struct tidaw);
	}
	if (intrg_max_tidaws) {
		cross_count = 1 + ((intrg_max_tidaws * sizeof(struct tidaw) - 1)
				   >> PAGE_SHIFT);
		len += cross_count * sizeof(struct tidaw);
	}
	return len;
}
EXPORT_SYMBOL(itcw_calc_size);

#define CROSS4K(x, l)	(((x) & ~4095) != ((x + l) & ~4095))

static inline void *fit_chunk(addr_t *start, addr_t end, size_t len,
			      int align, int check_4k)
{
	addr_t addr;

	addr = ALIGN(*start, align);
	if (check_4k && CROSS4K(addr, len)) {
		addr = ALIGN(addr, 4096);
		addr = ALIGN(addr, align);
	}
	if (addr + len > end)
		return ERR_PTR(-ENOSPC);
	*start = addr + len;
	return (void *) addr;
}

/**
 * itcw_init - initialize incremental tcw data structure
 * @buffer: address of buffer to use for data structures
 * @size: number of bytes in buffer
 * @op: %ITCW_OP_READ for a read operation tcw, %ITCW_OP_WRITE for a write
 * operation tcw
 * @intrg: if non-zero, add and initialize an interrogate tcw
 * @max_tidaws: maximum number of tidaws to be used for data addressing or zero
 * if no tida is to be used.
 * @intrg_max_tidaws: maximum number of tidaws to be used for data addressing
 * by the interrogate tcw, if specified
 *
 * Prepare the specified buffer to be used as an incremental tcw, i.e. a
 * helper data structure that can be used to construct a valid tcw by
 * successive calls to other helper functions. Note: the buffer needs to be
 * located below the 2G address limit. The resulting tcw has the following
 * restrictions:
 *  - no tccb tidal
 *  - input/output tidal is contiguous (no ttic)
 *  - total data should not exceed 4k
 *  - tcw specifies either read or write operation
 *
 * On success, return pointer to the resulting incremental tcw data structure,
 * ERR_PTR otherwise.
 */
struct itcw *itcw_init(void *buffer, size_t size, int op, int intrg,
		       int max_tidaws, int intrg_max_tidaws)
{
	struct itcw *itcw;
	void *chunk;
	addr_t start;
	addr_t end;
	int cross_count;

	/* Check for 2G limit. */
	start = (addr_t) buffer;
	end = start + size;
	if (end > (1 << 31))
		return ERR_PTR(-EINVAL);
	memset(buffer, 0, size);
	/* ITCW. */
	chunk = fit_chunk(&start, end, sizeof(struct itcw), 1, 0);
	if (IS_ERR(chunk))
		return chunk;
	itcw = chunk;
	/* allow for TTIC tidaws that may be needed to cross a page boundary */
	cross_count = 0;
	if (max_tidaws)
		cross_count = 1 + ((max_tidaws * sizeof(struct tidaw) - 1)
				   >> PAGE_SHIFT);
	itcw->max_tidaws = max_tidaws + cross_count;
	cross_count = 0;
	if (intrg_max_tidaws)
		cross_count = 1 + ((intrg_max_tidaws * sizeof(struct tidaw) - 1)
				   >> PAGE_SHIFT);
	itcw->intrg_max_tidaws = intrg_max_tidaws + cross_count;
	/* Main TCW. */
	chunk = fit_chunk(&start, end, sizeof(struct tcw), 64, 0);
	if (IS_ERR(chunk))
		return chunk;
	itcw->tcw = chunk;
	tcw_init(itcw->tcw, (op == ITCW_OP_READ) ? 1 : 0,
		 (op == ITCW_OP_WRITE) ? 1 : 0);
	/* Interrogate TCW. */
	if (intrg) {
		chunk = fit_chunk(&start, end, sizeof(struct tcw), 64, 0);
		if (IS_ERR(chunk))
			return chunk;
		itcw->intrg_tcw = chunk;
		tcw_init(itcw->intrg_tcw, 1, 0);
		tcw_set_intrg(itcw->tcw, itcw->intrg_tcw);
	}
	/* Data TIDAL. */
	if (max_tidaws > 0) {
		chunk = fit_chunk(&start, end, sizeof(struct tidaw) *
				  itcw->max_tidaws, 16, 0);
		if (IS_ERR(chunk))
			return chunk;
		tcw_set_data(itcw->tcw, chunk, 1);
	}
	/* Interrogate data TIDAL. */
	if (intrg && (intrg_max_tidaws > 0)) {
		chunk = fit_chunk(&start, end, sizeof(struct tidaw) *
				  itcw->intrg_max_tidaws, 16, 0);
		if (IS_ERR(chunk))
			return chunk;
		tcw_set_data(itcw->intrg_tcw, chunk, 1);
	}
	/* TSB. */
	chunk = fit_chunk(&start, end, sizeof(struct tsb), 8, 0);
	if (IS_ERR(chunk))
		return chunk;
	tsb_init(chunk);
	tcw_set_tsb(itcw->tcw, chunk);
	/* Interrogate TSB. */
	if (intrg) {
		chunk = fit_chunk(&start, end, sizeof(struct tsb), 8, 0);
		if (IS_ERR(chunk))
			return chunk;
		tsb_init(chunk);
		tcw_set_tsb(itcw->intrg_tcw, chunk);
	}
	/* TCCB. */
	chunk = fit_chunk(&start, end, TCCB_MAX_SIZE, 8, 0);
	if (IS_ERR(chunk))
		return chunk;
	tccb_init(chunk, TCCB_MAX_SIZE, TCCB_SAC_DEFAULT);
	tcw_set_tccb(itcw->tcw, chunk);
	/* Interrogate TCCB. */
	if (intrg) {
		chunk = fit_chunk(&start, end, TCCB_MAX_SIZE, 8, 0);
		if (IS_ERR(chunk))
			return chunk;
		tccb_init(chunk, TCCB_MAX_SIZE, TCCB_SAC_INTRG);
		tcw_set_tccb(itcw->intrg_tcw, chunk);
		tccb_add_dcw(chunk, TCCB_MAX_SIZE, DCW_CMD_INTRG, 0, NULL,
			     sizeof(struct dcw_intrg_data), 0);
		tcw_finalize(itcw->intrg_tcw, 0);
	}
	return itcw;
}
EXPORT_SYMBOL(itcw_init);

/**
 * itcw_add_dcw - add a dcw to the itcw
 * @itcw: address of the itcw
 * @cmd: the dcw command
 * @flags: flags for the dcw
 * @cd: address of control data for this dcw or NULL if none is required
 * @cd_count: number of control data bytes for this dcw
 * @count: number of data bytes for this dcw
 *
 * Add a new dcw to the specified itcw by writing the dcw information specified
 * by @cmd, @flags, @cd, @cd_count and @count to the tca of the tccb. Return
 * a pointer to the newly added dcw on success or -%ENOSPC if the new dcw
 * would exceed the available space.
 *
 * Note: the tcal field of the tccb header will be updated to reflect added
 * content.
 */
struct dcw *itcw_add_dcw(struct itcw *itcw, u8 cmd, u8 flags, void *cd,
			 u8 cd_count, u32 count)
{
	return tccb_add_dcw(tcw_get_tccb(itcw->tcw), TCCB_MAX_SIZE, cmd,
			    flags, cd, cd_count, count);
}
EXPORT_SYMBOL(itcw_add_dcw);

/**
 * itcw_add_tidaw - add a tidaw to the itcw
 * @itcw: address of the itcw
 * @flags: flags for the new tidaw
 * @addr: address value for the new tidaw
 * @count: count value for the new tidaw
 *
 * Add a new tidaw to the input/output data tidaw-list of the specified itcw
 * (depending on the value of the r-flag and w-flag). Return a pointer to
 * the new tidaw on success or -%ENOSPC if the new tidaw would exceed the
 * available space.
 *
 * Note: TTIC tidaws are automatically added when needed, so explicitly calling
 * this interface with the TTIC flag is not supported. The last-tidaw flag
 * for the last tidaw in the list will be set by itcw_finalize.
 */
struct tidaw *itcw_add_tidaw(struct itcw *itcw, u8 flags, void *addr, u32 count)
{
	struct tidaw *following;

	if (itcw->num_tidaws >= itcw->max_tidaws)
		return ERR_PTR(-ENOSPC);
	/*
	 * Is the tidaw, which follows the one we are about to fill, on the next
	 * page? Then we have to insert a TTIC tidaw first, that points to the
	 * tidaw on the new page.
	 */
	following = ((struct tidaw *) tcw_get_data(itcw->tcw))
		+ itcw->num_tidaws + 1;
	if (itcw->num_tidaws && !((unsigned long) following & ~PAGE_MASK)) {
		tcw_add_tidaw(itcw->tcw, itcw->num_tidaws++,
			      TIDAW_FLAGS_TTIC, following, 0);
		if (itcw->num_tidaws >= itcw->max_tidaws)
			return ERR_PTR(-ENOSPC);
	}
	return tcw_add_tidaw(itcw->tcw, itcw->num_tidaws++, flags, addr, count);
}
EXPORT_SYMBOL(itcw_add_tidaw);

/**
 * itcw_set_data - set data address and tida flag of the itcw
 * @itcw: address of the itcw
 * @addr: the data address
 * @use_tidal: zero of the data address specifies a contiguous block of data,
 * non-zero if it specifies a list if tidaws.
 *
 * Set the input/output data address of the itcw (depending on the value of the
 * r-flag and w-flag). If @use_tidal is non-zero, the corresponding tida flag
 * is set as well.
 */
void itcw_set_data(struct itcw *itcw, void *addr, int use_tidal)
{
	tcw_set_data(itcw->tcw, addr, use_tidal);
}
EXPORT_SYMBOL(itcw_set_data);

/**
 * itcw_finalize - calculate length and count fields of the itcw
 * @itcw: address of the itcw
 *
 * Calculate tcw input-/output-count and tccbl fields and add a tcat the tccb.
 * In case input- or output-tida is used, the tidaw-list must be stored in
 * continuous storage (no ttic). The tcal field in the tccb must be
 * up-to-date.
 */
void itcw_finalize(struct itcw *itcw)
{
	tcw_finalize(itcw->tcw, itcw->num_tidaws);
}
EXPORT_SYMBOL(itcw_finalize);