aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio/itcw.c
blob: a0ae29564774c7267654138e7a32beab87201f44 (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
/*
 *  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;

	/* 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;
	/* Maximum padding for structures that may not cross 4k boundary. */
	if ((max_tidaws > 0) || (intrg_max_tidaws > 0))
		len += max(max_tidaws, intrg_max_tidaws) *
		       sizeof(struct tidaw) - 1;
	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;

	/* 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;
	itcw->max_tidaws = max_tidaws;
	itcw->intrg_max_tidaws = intrg_max_tidaws;
	/* 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) *
				  max_tidaws, 16, 1);
		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) *
				  intrg_max_tidaws, 16, 1);
		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: the tidaw-list is assumed to be contiguous with no ttics. 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)
{
	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);