aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio/fcx.c
blob: 99c900cc3e5b5323ee40f10733f6c95693e2c458 (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
// SPDX-License-Identifier: GPL-2.0
/*
 *  Functions for assembling 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 "cio.h"

/**
 * tcw_get_intrg - return pointer to associated interrogate tcw
 * @tcw: pointer to the original tcw
 *
 * Return a pointer to the interrogate tcw associated with the specified tcw
 * or %NULL if there is no associated interrogate tcw.
 */
struct tcw *tcw_get_intrg(struct tcw *tcw)
{
	return (struct tcw *) ((addr_t) tcw->intrg);
}
EXPORT_SYMBOL(tcw_get_intrg);

/**
 * tcw_get_data - return pointer to input/output data associated with tcw
 * @tcw: pointer to the tcw
 *
 * Return the input or output data address specified in the tcw depending
 * on whether the r-bit or the w-bit is set. If neither bit is set, return
 * %NULL.
 */
void *tcw_get_data(struct tcw *tcw)
{
	if (tcw->r)
		return (void *) ((addr_t) tcw->input);
	if (tcw->w)
		return (void *) ((addr_t) tcw->output);
	return NULL;
}
EXPORT_SYMBOL(tcw_get_data);

/**
 * tcw_get_tccb - return pointer to tccb associated with tcw
 * @tcw: pointer to the tcw
 *
 * Return pointer to the tccb associated with this tcw.
 */
struct tccb *tcw_get_tccb(struct tcw *tcw)
{
	return (struct tccb *) ((addr_t) tcw->tccb);
}
EXPORT_SYMBOL(tcw_get_tccb);

/**
 * tcw_get_tsb - return pointer to tsb associated with tcw
 * @tcw: pointer to the tcw
 *
 * Return pointer to the tsb associated with this tcw.
 */
struct tsb *tcw_get_tsb(struct tcw *tcw)
{
	return (struct tsb *) ((addr_t) tcw->tsb);
}
EXPORT_SYMBOL(tcw_get_tsb);

/**
 * tcw_init - initialize tcw data structure
 * @tcw: pointer to the tcw to be initialized
 * @r: initial value of the r-bit
 * @w: initial value of the w-bit
 *
 * Initialize all fields of the specified tcw data structure with zero and
 * fill in the format, flags, r and w fields.
 */
void tcw_init(struct tcw *tcw, int r, int w)
{
	memset(tcw, 0, sizeof(struct tcw));
	tcw->format = TCW_FORMAT_DEFAULT;
	tcw->flags = TCW_FLAGS_TIDAW_FORMAT(TCW_TIDAW_FORMAT_DEFAULT);
	if (r)
		tcw->r = 1;
	if (w)
		tcw->w = 1;
}
EXPORT_SYMBOL(tcw_init);

static inline size_t tca_size(struct tccb *tccb)
{
	return tccb->tcah.tcal - 12;
}

static u32 calc_dcw_count(struct tccb *tccb)
{
	int offset;
	struct dcw *dcw;
	u32 count = 0;
	size_t size;

	size = tca_size(tccb);
	for (offset = 0; offset < size;) {
		dcw = (struct dcw *) &tccb->tca[offset];
		count += dcw->count;
		if (!(dcw->flags & DCW_FLAGS_CC))
			break;
		offset += sizeof(struct dcw) + ALIGN((int) dcw->cd_count, 4);
	}
	return count;
}

static u32 calc_cbc_size(struct tidaw *tidaw, int num)
{
	int i;
	u32 cbc_data;
	u32 cbc_count = 0;
	u64 data_count = 0;

	for (i = 0; i < num; i++) {
		if (tidaw[i].flags & TIDAW_FLAGS_LAST)
			break;
		/* TODO: find out if padding applies to total of data
		 * transferred or data transferred by this tidaw. Assumption:
		 * applies to total. */
		data_count += tidaw[i].count;
		if (tidaw[i].flags & TIDAW_FLAGS_INSERT_CBC) {
			cbc_data = 4 + ALIGN(data_count, 4) - data_count;
			cbc_count += cbc_data;
			data_count += cbc_data;
		}
	}
	return cbc_count;
}

/**
 * tcw_finalize - finalize tcw length fields and tidaw list
 * @tcw: pointer to the tcw
 * @num_tidaws: the number of tidaws used to address input/output data or zero
 * if no tida is used
 *
 * Calculate the input-/output-count and tccbl field in the tcw, add a
 * tcat the tccb and terminate the data tidaw list if used.
 *
 * Note: in case input- or output-tida is used, the tidaw-list must be stored
 * in contiguous storage (no ttic). The tcal field in the tccb must be
 * up-to-date.
 */
void tcw_finalize(struct tcw *tcw, int num_tidaws)
{
	struct tidaw *tidaw;
	struct tccb *tccb;
	struct tccb_tcat *tcat;
	u32 count;

	/* Terminate tidaw list. */
	tidaw = tcw_get_data(tcw);
	if (num_tidaws > 0)
		tidaw[num_tidaws - 1].flags |= TIDAW_FLAGS_LAST;
	/* Add tcat to tccb. */
	tccb = tcw_get_tccb(tcw);
	tcat = (struct tccb_tcat *) &tccb->tca[tca_size(tccb)];
	memset(tcat, 0, sizeof(*tcat));
	/* Calculate tcw input/output count and tcat transport count. */
	count = calc_dcw_count(tccb);
	if (tcw->w && (tcw->flags & TCW_FLAGS_OUTPUT_TIDA))
		count += calc_cbc_size(tidaw, num_tidaws);
	if (tcw->r)
		tcw->input_count = count;
	else if (tcw->w)
		tcw->output_count = count;
	tcat->count = ALIGN(count, 4) + 4;
	/* Calculate tccbl. */
	tcw->tccbl = (sizeof(struct tccb) + tca_size(tccb) +
		      sizeof(struct tccb_tcat) - 20) >> 2;
}
EXPORT_SYMBOL(tcw_finalize);

/**
 * tcw_set_intrg - set the interrogate tcw address of a tcw
 * @tcw: the tcw address
 * @intrg_tcw: the address of the interrogate tcw
 *
 * Set the address of the interrogate tcw in the specified tcw.
 */
void tcw_set_intrg(struct tcw *tcw, struct tcw *intrg_tcw)
{
	tcw->intrg = (u32) ((addr_t) intrg_tcw);
}
EXPORT_SYMBOL(tcw_set_intrg);

/**
 * tcw_set_data - set data address and tida flag of a tcw
 * @tcw: the tcw address
 * @data: 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 a tcw (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 tcw_set_data(struct tcw *tcw, void *data, int use_tidal)
{
	if (tcw->r) {
		tcw->input = (u64) ((addr_t) data);
		if (use_tidal)
			tcw->flags |= TCW_FLAGS_INPUT_TIDA;
	} else if (tcw->w) {
		tcw->output = (u64) ((addr_t) data);
		if (use_tidal)
			tcw->flags |= TCW_FLAGS_OUTPUT_TIDA;
	}
}
EXPORT_SYMBOL(tcw_set_data);

/**
 * tcw_set_tccb - set tccb address of a tcw
 * @tcw: the tcw address
 * @tccb: the tccb address
 *
 * Set the address of the tccb in the specified tcw.
 */
void tcw_set_tccb(struct tcw *tcw, struct tccb *tccb)
{
	tcw->tccb = (u64) ((addr_t) tccb);
}
EXPORT_SYMBOL(tcw_set_tccb);

/**
 * tcw_set_tsb - set tsb address of a tcw
 * @tcw: the tcw address
 * @tsb: the tsb address
 *
 * Set the address of the tsb in the specified tcw.
 */
void tcw_set_tsb(struct tcw *tcw, struct tsb *tsb)
{
	tcw->tsb = (u64) ((addr_t) tsb);
}
EXPORT_SYMBOL(tcw_set_tsb);

/**
 * tccb_init - initialize tccb
 * @tccb: the tccb address
 * @size: the maximum size of the tccb
 * @sac: the service-action-code to be user
 *
 * Initialize the header of the specified tccb by resetting all values to zero
 * and filling in defaults for format, sac and initial tcal fields.
 */
void tccb_init(struct tccb *tccb, size_t size, u32 sac)
{
	memset(tccb, 0, size);
	tccb->tcah.format = TCCB_FORMAT_DEFAULT;
	tccb->tcah.sac = sac;
	tccb->tcah.tcal = 12;
}
EXPORT_SYMBOL(tccb_init);

/**
 * tsb_init - initialize tsb
 * @tsb: the tsb address
 *
 * Initialize the specified tsb by resetting all values to zero.
 */
void tsb_init(struct tsb *tsb)
{
	memset(tsb, 0, sizeof(*tsb));
}
EXPORT_SYMBOL(tsb_init);

/**
 * tccb_add_dcw - add a dcw to the tccb
 * @tccb: the tccb address
 * @tccb_size: the maximum tccb size
 * @cmd: the dcw command
 * @flags: flags for the dcw
 * @cd: pointer to 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 tccb 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 as defined by @tccb_size.
 *
 * Note: the tcal field of the tccb header will be updates to reflect added
 * content.
 */
struct dcw *tccb_add_dcw(struct tccb *tccb, size_t tccb_size, u8 cmd, u8 flags,
			 void *cd, u8 cd_count, u32 count)
{
	struct dcw *dcw;
	int size;
	int tca_offset;

	/* Check for space. */
	tca_offset = tca_size(tccb);
	size = ALIGN(sizeof(struct dcw) + cd_count, 4);
	if (sizeof(struct tccb_tcah) + tca_offset + size +
	    sizeof(struct tccb_tcat) > tccb_size)
		return ERR_PTR(-ENOSPC);
	/* Add dcw to tca. */
	dcw = (struct dcw *) &tccb->tca[tca_offset];
	memset(dcw, 0, size);
	dcw->cmd = cmd;
	dcw->flags = flags;
	dcw->count = count;
	dcw->cd_count = cd_count;
	if (cd)
		memcpy(&dcw->cd[0], cd, cd_count);
	tccb->tcah.tcal += size;
	return dcw;
}
EXPORT_SYMBOL(tccb_add_dcw);

/**
 * tcw_add_tidaw - add a tidaw to a tcw
 * @tcw: the tcw address
 * @num_tidaws: the current number of tidaws
 * @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 tcw
 * (depending on the value of the r-flag and w-flag) and return a pointer to
 * the new tidaw.
 *
 * Note: the tidaw-list is assumed to be contiguous with no ttics. The caller
 * must ensure that there is enough space for the new tidaw. The last-tidaw
 * flag for the last tidaw in the list will be set by tcw_finalize.
 */
struct tidaw *tcw_add_tidaw(struct tcw *tcw, int num_tidaws, u8 flags,
			    void *addr, u32 count)
{
	struct tidaw *tidaw;

	/* Add tidaw to tidaw-list. */
	tidaw = ((struct tidaw *) tcw_get_data(tcw)) + num_tidaws;
	memset(tidaw, 0, sizeof(struct tidaw));
	tidaw->flags = flags;
	tidaw->count = count;
	tidaw->addr = (u64) ((addr_t) addr);
	return tidaw;
}
EXPORT_SYMBOL(tcw_add_tidaw);