aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ceph/export.c
blob: d3ef7ee429ec03d3a4209260df0c396ab433f77d (plain)
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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>

#include <linux/exportfs.h>
#include <linux/slab.h>
#include <asm/unaligned.h>

#include "super.h"
#include "mds_client.h"

/*
 * Basic fh
 */
struct ceph_nfs_fh {
	u64 ino;
} __attribute__ ((packed));

/*
 * Larger fh that includes parent ino.
 */
struct ceph_nfs_confh {
	u64 ino, parent_ino;
} __attribute__ ((packed));

/*
 * fh for snapped inode
 */
struct ceph_nfs_snapfh {
	u64 ino;
	u64 snapid;
	u64 parent_ino;
	u32 hash;
} __attribute__ ((packed));

static int ceph_encode_snapfh(struct inode *inode, u32 *rawfh, int *max_len,
			      struct inode *parent_inode)
{
	const static int snap_handle_length =
		sizeof(struct ceph_nfs_snapfh) >> 2;
	struct ceph_nfs_snapfh *sfh = (void *)rawfh;
	u64 snapid = ceph_snap(inode);
	int ret;
	bool no_parent = true;

	if (*max_len < snap_handle_length) {
		*max_len = snap_handle_length;
		ret = FILEID_INVALID;
		goto out;
	}

	ret =  -EINVAL;
	if (snapid != CEPH_SNAPDIR) {
		struct inode *dir;
		struct dentry *dentry = d_find_alias(inode);
		if (!dentry)
			goto out;

		rcu_read_lock();
		dir = d_inode_rcu(dentry->d_parent);
		if (ceph_snap(dir) != CEPH_SNAPDIR) {
			sfh->parent_ino = ceph_ino(dir);
			sfh->hash = ceph_dentry_hash(dir, dentry);
			no_parent = false;
		}
		rcu_read_unlock();
		dput(dentry);
	}

	if (no_parent) {
		if (!S_ISDIR(inode->i_mode))
			goto out;
		sfh->parent_ino = sfh->ino;
		sfh->hash = 0;
	}
	sfh->ino = ceph_ino(inode);
	sfh->snapid = snapid;

	*max_len = snap_handle_length;
	ret = FILEID_BTRFS_WITH_PARENT;
out:
	dout("encode_snapfh %llx.%llx ret=%d\n", ceph_vinop(inode), ret);
	return ret;
}

static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len,
			  struct inode *parent_inode)
{
	const static int handle_length =
		sizeof(struct ceph_nfs_fh) >> 2;
	const static int connected_handle_length =
		sizeof(struct ceph_nfs_confh) >> 2;
	int type;

	if (ceph_snap(inode) != CEPH_NOSNAP)
		return ceph_encode_snapfh(inode, rawfh, max_len, parent_inode);

	if (parent_inode && (*max_len < connected_handle_length)) {
		*max_len = connected_handle_length;
		return FILEID_INVALID;
	} else if (*max_len < handle_length) {
		*max_len = handle_length;
		return FILEID_INVALID;
	}

	if (parent_inode) {
		struct ceph_nfs_confh *cfh = (void *)rawfh;
		dout("encode_fh %llx with parent %llx\n",
		     ceph_ino(inode), ceph_ino(parent_inode));
		cfh->ino = ceph_ino(inode);
		cfh->parent_ino = ceph_ino(parent_inode);
		*max_len = connected_handle_length;
		type = FILEID_INO32_GEN_PARENT;
	} else {
		struct ceph_nfs_fh *fh = (void *)rawfh;
		dout("encode_fh %llx\n", ceph_ino(inode));
		fh->ino = ceph_ino(inode);
		*max_len = handle_length;
		type = FILEID_INO32_GEN;
	}
	return type;
}

static struct inode *__lookup_inode(struct super_block *sb, u64 ino)
{
	struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
	struct inode *inode;
	struct ceph_vino vino;
	int err;

	vino.ino = ino;
	vino.snap = CEPH_NOSNAP;
	inode = ceph_find_inode(sb, vino);
	if (!inode) {
		struct ceph_mds_request *req;
		int mask;

		req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPINO,
					       USE_ANY_MDS);
		if (IS_ERR(req))
			return ERR_CAST(req);

		mask = CEPH_STAT_CAP_INODE;
		if (ceph_security_xattr_wanted(d_inode(sb->s_root)))
			mask |= CEPH_CAP_XATTR_SHARED;
		req->r_args.lookupino.mask = cpu_to_le32(mask);

		req->r_ino1 = vino;
		req->r_num_caps = 1;
		err = ceph_mdsc_do_request(mdsc, NULL, req);
		inode = req->r_target_inode;
		if (inode)
			ihold(inode);
		ceph_mdsc_put_request(req);
		if (!inode)
			return err < 0 ? ERR_PTR(err) : ERR_PTR(-ESTALE);
	}
	return inode;
}

struct inode *ceph_lookup_inode(struct super_block *sb, u64 ino)
{
	struct inode *inode = __lookup_inode(sb, ino);
	if (IS_ERR(inode))
		return inode;
	if (inode->i_nlink == 0) {
		iput(inode);
		return ERR_PTR(-ESTALE);
	}
	return inode;
}

static struct dentry *__fh_to_dentry(struct super_block *sb, u64 ino)
{
	struct inode *inode = __lookup_inode(sb, ino);
	if (IS_ERR(inode))
		return ERR_CAST(inode);
	if (inode->i_nlink == 0) {
		iput(inode);
		return ERR_PTR(-ESTALE);
	}
	return d_obtain_alias(inode);
}

static struct dentry *__snapfh_to_dentry(struct super_block *sb,
					  struct ceph_nfs_snapfh *sfh,
					  bool want_parent)
{
	struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
	struct ceph_mds_request *req;
	struct inode *inode;
	struct ceph_vino vino;
	int mask;
	int err;
	bool unlinked = false;

	if (want_parent) {
		vino.ino = sfh->parent_ino;
		if (sfh->snapid == CEPH_SNAPDIR)
			vino.snap = CEPH_NOSNAP;
		else if (sfh->ino == sfh->parent_ino)
			vino.snap = CEPH_SNAPDIR;
		else
			vino.snap = sfh->snapid;
	} else {
		vino.ino = sfh->ino;
		vino.snap = sfh->snapid;
	}
	inode = ceph_find_inode(sb, vino);
	if (inode)
		return d_obtain_alias(inode);

	req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPINO,
				       USE_ANY_MDS);
	if (IS_ERR(req))
		return ERR_CAST(req);

	mask = CEPH_STAT_CAP_INODE;
	if (ceph_security_xattr_wanted(d_inode(sb->s_root)))
		mask |= CEPH_CAP_XATTR_SHARED;
	req->r_args.lookupino.mask = cpu_to_le32(mask);
	if (vino.snap < CEPH_NOSNAP) {
		req->r_args.lookupino.snapid = cpu_to_le64(vino.snap);
		if (!want_parent && sfh->ino != sfh->parent_ino) {
			req->r_args.lookupino.parent =
					cpu_to_le64(sfh->parent_ino);
			req->r_args.lookupino.hash =
					cpu_to_le32(sfh->hash);
		}
	}

	req->r_ino1 = vino;
	req->r_num_caps = 1;
	err = ceph_mdsc_do_request(mdsc, NULL, req);
	inode = req->r_target_inode;
	if (inode) {
		if (vino.snap == CEPH_SNAPDIR) {
			if (inode->i_nlink == 0)
				unlinked = true;
			inode = ceph_get_snapdir(inode);
		} else if (ceph_snap(inode) == vino.snap) {
			ihold(inode);
		} else {
			/* mds does not support lookup snapped inode */
			err = -EOPNOTSUPP;
			inode = NULL;
		}
	}
	ceph_mdsc_put_request(req);

	if (want_parent) {
		dout("snapfh_to_parent %llx.%llx\n err=%d\n",
		     vino.ino, vino.snap, err);
	} else {
		dout("snapfh_to_dentry %llx.%llx parent %llx hash %x err=%d",
		      vino.ino, vino.snap, sfh->parent_ino, sfh->hash, err);
	}
	if (!inode)
		return ERR_PTR(-ESTALE);
	/* see comments in ceph_get_parent() */
	return unlinked ? d_obtain_root(inode) : d_obtain_alias(inode);
}

/*
 * convert regular fh to dentry
 */
static struct dentry *ceph_fh_to_dentry(struct super_block *sb,
					struct fid *fid,
					int fh_len, int fh_type)
{
	struct ceph_nfs_fh *fh = (void *)fid->raw;

	if (fh_type == FILEID_BTRFS_WITH_PARENT) {
		struct ceph_nfs_snapfh *sfh = (void *)fid->raw;
		return __snapfh_to_dentry(sb, sfh, false);
	}

	if (fh_type != FILEID_INO32_GEN  &&
	    fh_type != FILEID_INO32_GEN_PARENT)
		return NULL;
	if (fh_len < sizeof(*fh) / 4)
		return NULL;

	dout("fh_to_dentry %llx\n", fh->ino);
	return __fh_to_dentry(sb, fh->ino);
}

static struct dentry *__get_parent(struct super_block *sb,
				   struct dentry *child, u64 ino)
{
	struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
	struct ceph_mds_request *req;
	struct inode *inode;
	int mask;
	int err;

	req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPPARENT,
				       USE_ANY_MDS);
	if (IS_ERR(req))
		return ERR_CAST(req);

	if (child) {
		req->r_inode = d_inode(child);
		ihold(d_inode(child));
	} else {
		req->r_ino1 = (struct ceph_vino) {
			.ino = ino,
			.snap = CEPH_NOSNAP,
		};
	}

	mask = CEPH_STAT_CAP_INODE;
	if (ceph_security_xattr_wanted(d_inode(sb->s_root)))
		mask |= CEPH_CAP_XATTR_SHARED;
	req->r_args.getattr.mask = cpu_to_le32(mask);

	req->r_num_caps = 1;
	err = ceph_mdsc_do_request(mdsc, NULL, req);
	inode = req->r_target_inode;
	if (inode)
		ihold(inode);
	ceph_mdsc_put_request(req);
	if (!inode)
		return ERR_PTR(-ENOENT);

	return d_obtain_alias(inode);
}

static struct dentry *ceph_get_parent(struct dentry *child)
{
	struct inode *inode = d_inode(child);
	struct dentry *dn;

	if (ceph_snap(inode) != CEPH_NOSNAP) {
		struct inode* dir;
		bool unlinked = false;
		/* do not support non-directory */
		if (!d_is_dir(child)) {
			dn = ERR_PTR(-EINVAL);
			goto out;
		}
		dir = __lookup_inode(inode->i_sb, ceph_ino(inode));
		if (IS_ERR(dir)) {
			dn = ERR_CAST(dir);
			goto out;
		}
		/* There can be multiple paths to access snapped inode.
		 * For simplicity, treat snapdir of head inode as parent */
		if (ceph_snap(inode) != CEPH_SNAPDIR) {
			struct inode *snapdir = ceph_get_snapdir(dir);
			if (dir->i_nlink == 0)
				unlinked = true;
			iput(dir);
			if (IS_ERR(snapdir)) {
				dn = ERR_CAST(snapdir);
				goto out;
			}
			dir = snapdir;
		}
		/* If directory has already been deleted, futher get_parent
		 * will fail. Do not mark snapdir dentry as disconnected,
		 * this prevent exportfs from doing futher get_parent. */
		if (unlinked)
			dn = d_obtain_root(dir);
		else
			dn = d_obtain_alias(dir);
	} else {
		dn = __get_parent(child->d_sb, child, 0);
	}
out:
	dout("get_parent %p ino %llx.%llx err=%ld\n",
	     child, ceph_vinop(inode), (IS_ERR(dn) ? PTR_ERR(dn) : 0));
	return dn;
}

/*
 * convert regular fh to parent
 */
static struct dentry *ceph_fh_to_parent(struct super_block *sb,
					struct fid *fid,
					int fh_len, int fh_type)
{
	struct ceph_nfs_confh *cfh = (void *)fid->raw;
	struct dentry *dentry;

	if (fh_type == FILEID_BTRFS_WITH_PARENT) {
		struct ceph_nfs_snapfh *sfh = (void *)fid->raw;
		return __snapfh_to_dentry(sb, sfh, true);
	}

	if (fh_type != FILEID_INO32_GEN_PARENT)
		return NULL;
	if (fh_len < sizeof(*cfh) / 4)
		return NULL;

	dout("fh_to_parent %llx\n", cfh->parent_ino);
	dentry = __get_parent(sb, NULL, cfh->ino);
	if (unlikely(dentry == ERR_PTR(-ENOENT)))
		dentry = __fh_to_dentry(sb, cfh->parent_ino);
	return dentry;
}

static int __get_snap_name(struct dentry *parent, char *name,
			   struct dentry *child)
{
	struct inode *inode = d_inode(child);
	struct inode *dir = d_inode(parent);
	struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
	struct ceph_mds_request *req = NULL;
	char *last_name = NULL;
	unsigned next_offset = 2;
	int err = -EINVAL;

	if (ceph_ino(inode) != ceph_ino(dir))
		goto out;
	if (ceph_snap(inode) == CEPH_SNAPDIR) {
		if (ceph_snap(dir) == CEPH_NOSNAP) {
			strcpy(name, fsc->mount_options->snapdir_name);
			err = 0;
		}
		goto out;
	}
	if (ceph_snap(dir) != CEPH_SNAPDIR)
		goto out;

	while (1) {
		struct ceph_mds_reply_info_parsed *rinfo;
		struct ceph_mds_reply_dir_entry *rde;
		int i;

		req = ceph_mdsc_create_request(fsc->mdsc, CEPH_MDS_OP_LSSNAP,
					       USE_AUTH_MDS);
		if (IS_ERR(req)) {
			err = PTR_ERR(req);
			req = NULL;
			goto out;
		}
		err = ceph_alloc_readdir_reply_buffer(req, inode);
		if (err)
			goto out;

		req->r_direct_mode = USE_AUTH_MDS;
		req->r_readdir_offset = next_offset;
		req->r_args.readdir.flags =
				cpu_to_le16(CEPH_READDIR_REPLY_BITFLAGS);
		if (last_name) {
			req->r_path2 = last_name;
			last_name = NULL;
		}

		req->r_inode = dir;
		ihold(dir);
		req->r_dentry = dget(parent);

		inode_lock(dir);
		err = ceph_mdsc_do_request(fsc->mdsc, NULL, req);
		inode_unlock(dir);

		if (err < 0)
			goto out;

		 rinfo = &req->r_reply_info;
		 for (i = 0; i < rinfo->dir_nr; i++) {
			 rde = rinfo->dir_entries + i;
			 BUG_ON(!rde->inode.in);
			 if (ceph_snap(inode) ==
			     le64_to_cpu(rde->inode.in->snapid)) {
				 memcpy(name, rde->name, rde->name_len);
				 name[rde->name_len] = '\0';
				 err = 0;
				 goto out;
			 }
		 }

		 if (rinfo->dir_end)
			 break;

		 BUG_ON(rinfo->dir_nr <= 0);
		 rde = rinfo->dir_entries + (rinfo->dir_nr - 1);
		 next_offset += rinfo->dir_nr;
		 last_name = kstrndup(rde->name, rde->name_len, GFP_KERNEL);
		 if (!last_name) {
			 err = -ENOMEM;
			 goto out;
		 }

		 ceph_mdsc_put_request(req);
		 req = NULL;
	}
	err = -ENOENT;
out:
	if (req)
		ceph_mdsc_put_request(req);
	kfree(last_name);
	dout("get_snap_name %p ino %llx.%llx err=%d\n",
	     child, ceph_vinop(inode), err);
	return err;
}

static int ceph_get_name(struct dentry *parent, char *name,
			 struct dentry *child)
{
	struct ceph_mds_client *mdsc;
	struct ceph_mds_request *req;
	struct inode *inode = d_inode(child);
	int err;

	if (ceph_snap(inode) != CEPH_NOSNAP)
		return __get_snap_name(parent, name, child);

	mdsc = ceph_inode_to_client(inode)->mdsc;
	req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPNAME,
				       USE_ANY_MDS);
	if (IS_ERR(req))
		return PTR_ERR(req);

	inode_lock(d_inode(parent));

	req->r_inode = inode;
	ihold(inode);
	req->r_ino2 = ceph_vino(d_inode(parent));
	req->r_parent = d_inode(parent);
	set_bit(CEPH_MDS_R_PARENT_LOCKED, &req->r_req_flags);
	req->r_num_caps = 2;
	err = ceph_mdsc_do_request(mdsc, NULL, req);

	inode_unlock(d_inode(parent));

	if (!err) {
		struct ceph_mds_reply_info_parsed *rinfo = &req->r_reply_info;
		memcpy(name, rinfo->dname, rinfo->dname_len);
		name[rinfo->dname_len] = 0;
		dout("get_name %p ino %llx.%llx name %s\n",
		     child, ceph_vinop(inode), name);
	} else {
		dout("get_name %p ino %llx.%llx err %d\n",
		     child, ceph_vinop(inode), err);
	}

	ceph_mdsc_put_request(req);
	return err;
}

const struct export_operations ceph_export_ops = {
	.encode_fh = ceph_encode_fh,
	.fh_to_dentry = ceph_fh_to_dentry,
	.fh_to_parent = ceph_fh_to_parent,
	.get_parent = ceph_get_parent,
	.get_name = ceph_get_name,
};
ch->length /* end of the current image */ - fc->cmp_len; /* where we are now */ /* * NOTE - curr_len is the exact count of bytes for the read * even when the end is read and its not a full buffer */ for (len = fc->curr_len; len; len--) if (*p++ != *q++) goto error; fc->cmp_len -= fc->curr_len; /* # left to compare */ /* Update fc and determine the length for the next upload */ if (fc->cmp_len > FM_BUF_SZ) fc->sgc.length = FM_BUF_SZ; else fc->sgc.length = fc->cmp_len; fc->sgc.cur_offset = fc->sgc_offset + ((u8 *)fc->scratch - (u8 *)fi); } /* * This code uses a 'while' statement since the next component may * have a length = zero. This can happen since some components are * not required. At the end of this 'while' we set up the length * for the next request and therefore sgc.length can be = 0. */ while (fc->sgc.length == 0) { ch = &fi->cmp_hdr[fc->comp_typ]; switch (fc->task) { case FMTSK_ERASE_BOOT: /* the BIOS image is written next */ ch = &fi->cmp_hdr[CH_IT_BIOS]; if (ch->length == 0) goto no_bios; fc->task = FMTSK_WRTBIOS; fc->func = VDA_FLASH_BEGINW; fc->comp_typ = CH_IT_BIOS; fc->flsh_addr = FLS_OFFSET_BOOT; fc->sgc.length = ch->length; fc->sgc.cur_offset = fc->sgc_offset + ch->image_offset; break; case FMTSK_WRTBIOS: /* * The BIOS image has been written - read it and * verify it */ fc->task = FMTSK_READBIOS; fc->func = VDA_FLASH_READ; fc->flsh_addr = FLS_OFFSET_BOOT; fc->cmp_len = ch->length; fc->sgc.length = FM_BUF_SZ; fc->sgc.cur_offset = fc->sgc_offset + ((u8 *)fc->scratch - (u8 *)fi); break; case FMTSK_READBIOS: no_bios: /* * Mark the component header status for the image * completed */ ch->status = CH_STAT_SUCCESS; /* The MAC image is written next */ ch = &fi->cmp_hdr[CH_IT_MAC]; if (ch->length == 0) goto no_mac; fc->task = FMTSK_WRTMAC; fc->func = VDA_FLASH_BEGINW; fc->comp_typ = CH_IT_MAC; fc->flsh_addr = FLS_OFFSET_BOOT + fi->cmp_hdr[CH_IT_BIOS].length; fc->sgc.length = ch->length; fc->sgc.cur_offset = fc->sgc_offset + ch->image_offset; break; case FMTSK_WRTMAC: /* The MAC image has been written - read and verify */ fc->task = FMTSK_READMAC; fc->func = VDA_FLASH_READ; fc->flsh_addr -= ch->length; fc->cmp_len = ch->length; fc->sgc.length = FM_BUF_SZ; fc->sgc.cur_offset = fc->sgc_offset + ((u8 *)fc->scratch - (u8 *)fi); break; case FMTSK_READMAC: no_mac: /* * Mark the component header status for the image * completed */ ch->status = CH_STAT_SUCCESS; /* The EFI image is written next */ ch = &fi->cmp_hdr[CH_IT_EFI]; if (ch->length == 0) goto no_efi; fc->task = FMTSK_WRTEFI; fc->func = VDA_FLASH_BEGINW; fc->comp_typ = CH_IT_EFI; fc->flsh_addr = FLS_OFFSET_BOOT + fi->cmp_hdr[CH_IT_BIOS].length + fi->cmp_hdr[CH_IT_MAC].length; fc->sgc.length = ch->length; fc->sgc.cur_offset = fc->sgc_offset + ch->image_offset; break; case FMTSK_WRTEFI: /* The EFI image has been written - read and verify */ fc->task = FMTSK_READEFI; fc->func = VDA_FLASH_READ; fc->flsh_addr -= ch->length; fc->cmp_len = ch->length; fc->sgc.length = FM_BUF_SZ; fc->sgc.cur_offset = fc->sgc_offset + ((u8 *)fc->scratch - (u8 *)fi); break; case FMTSK_READEFI: no_efi: /* * Mark the component header status for the image * completed */ ch->status = CH_STAT_SUCCESS; /* The CFG image is written next */ ch = &fi->cmp_hdr[CH_IT_CFG]; if (ch->length == 0) goto no_cfg; fc->task = FMTSK_WRTCFG; fc->func = VDA_FLASH_BEGINW; fc->comp_typ = CH_IT_CFG; fc->flsh_addr = FLS_OFFSET_CPYR - ch->length; fc->sgc.length = ch->length; fc->sgc.cur_offset = fc->sgc_offset + ch->image_offset; break; case FMTSK_WRTCFG: /* The CFG image has been written - read and verify */ fc->task = FMTSK_READCFG; fc->func = VDA_FLASH_READ; fc->flsh_addr = FLS_OFFSET_CPYR - ch->length; fc->cmp_len = ch->length; fc->sgc.length = FM_BUF_SZ; fc->sgc.cur_offset = fc->sgc_offset + ((u8 *)fc->scratch - (u8 *)fi); break; case FMTSK_READCFG: no_cfg: /* * Mark the component header status for the image * completed */ ch->status = CH_STAT_SUCCESS; /* * The download is complete. If in degraded mode, * attempt a chip reset. */ if (test_bit(AF_DEGRADED_MODE, &a->flags)) esas2r_local_reset_adapter(a); a->flash_ver = fi->cmp_hdr[CH_IT_BIOS].version; esas2r_print_flash_rev(a); /* Update the type of boot image on the card */ memcpy(a->image_type, fi->rel_version, sizeof(fi->rel_version)); complete_fmapi_req(a, rq, FI_STAT_SUCCESS); return; } /* If verifying, don't try reading more than what's there */ if (fc->func == VDA_FLASH_READ && fc->sgc.length > fc->cmp_len) fc->sgc.length = fc->cmp_len; } /* Build the request to perform the next action */ if (!load_image(a, rq)) { error: if (fc->comp_typ < fi->num_comps) { ch = &fi->cmp_hdr[fc->comp_typ]; ch->status = CH_STAT_FAILED; } complete_fmapi_req(a, rq, FI_STAT_FAILED); } } /* Determine the flash image adaptyp for this adapter */ static u8 get_fi_adap_type(struct esas2r_adapter *a) { u8 type; /* use the device ID to get the correct adap_typ for this HBA */ switch (a->pcid->device) { case ATTO_DID_INTEL_IOP348: type = FI_AT_SUN_LAKE; break; case ATTO_DID_MV_88RC9580: case ATTO_DID_MV_88RC9580TS: case ATTO_DID_MV_88RC9580TSE: case ATTO_DID_MV_88RC9580TL: type = FI_AT_MV_9580; break; default: type = FI_AT_UNKNWN; break; } return type; } /* Size of config + copyright + flash_ver images, 0 for failure. */ static u32 chk_cfg(u8 *cfg, u32 length, u32 *flash_ver) { u16 *pw = (u16 *)cfg - 1; u32 sz = 0; u32 len = length; if (len == 0) len = FM_BUF_SZ; if (flash_ver) *flash_ver = 0; while (true) { u16 type; u16 size; type = le16_to_cpu(*pw--); size = le16_to_cpu(*pw--); if (type != FBT_CPYR && type != FBT_SETUP && type != FBT_FLASH_VER) break; if (type == FBT_FLASH_VER && flash_ver) *flash_ver = le32_to_cpu(*(u32 *)(pw - 1)); sz += size + (2 * sizeof(u16)); pw -= size / sizeof(u16); if (sz > len - (2 * sizeof(u16))) break; } /* See if we are comparing the size to the specified length */ if (length && sz != length) return 0; return sz; } /* Verify that the boot image is valid */ static u8 chk_boot(u8 *boot_img, u32 length) { struct esas2r_boot_image *bi = (struct esas2r_boot_image *)boot_img; u16 hdroffset = le16_to_cpu(bi->header_offset); struct esas2r_boot_header *bh; if (bi->signature != le16_to_cpu(0xaa55) || (long)hdroffset > (long)(65536L - sizeof(struct esas2r_boot_header)) || (hdroffset & 3) || (hdroffset < sizeof(struct esas2r_boot_image)) || ((u32)hdroffset + sizeof(struct esas2r_boot_header) > length)) return 0xff; bh = (struct esas2r_boot_header *)((char *)bi + hdroffset); if (bh->signature[0] != 'P' || bh->signature[1] != 'C' || bh->signature[2] != 'I' || bh->signature[3] != 'R' || le16_to_cpu(bh->struct_length) < (u16)sizeof(struct esas2r_boot_header) || bh->class_code[2] != 0x01 || bh->class_code[1] != 0x04 || bh->class_code[0] != 0x00 || (bh->code_type != CODE_TYPE_PC && bh->code_type != CODE_TYPE_OPEN && bh->code_type != CODE_TYPE_EFI)) return 0xff; return bh->code_type; } /* The sum of all the WORDS of the image */ static u16 calc_fi_checksum(struct esas2r_flash_context *fc) { struct esas2r_flash_img *fi = fc->fi; u16 cksum; u32 len; u16 *pw; for (len = (fi->length - fc->fi_hdr_len) / 2, pw = (u16 *)((u8 *)fi + fc->fi_hdr_len), cksum = 0; len; len--, pw++) cksum = cksum + le16_to_cpu(*pw); return cksum; } /* * Verify the flash image structure. The following verifications will * be performed: * 1) verify the fi_version is correct * 2) verify the checksum of the entire image. * 3) validate the adap_typ, action and length fields. * 4) validate each component header. check the img_type and * length fields * 5) validate each component image. validate signatures and * local checksums */ static bool verify_fi(struct esas2r_adapter *a, struct esas2r_flash_context *fc) { struct esas2r_flash_img *fi = fc->fi; u8 type; bool imgerr; u16 i; u32 len; struct esas2r_component_header *ch; /* Verify the length - length must even since we do a word checksum */ len = fi->length; if ((len & 1) || len < fc->fi_hdr_len) { fi->status = FI_STAT_LENGTH; return false; } /* Get adapter type and verify type in flash image */ type = get_fi_adap_type(a); if ((type == FI_AT_UNKNWN) || (fi->adap_typ != type)) { fi->status = FI_STAT_ADAPTYP; return false; } /* * Loop through each component and verify the img_type and length * fields. Keep a running count of the sizes sooze we can verify total * size to additive size. */ imgerr = false; for (i = 0, len = 0, ch = fi->cmp_hdr; i < fi->num_comps; i++, ch++) { bool cmperr = false; /* * Verify that the component header has the same index as the * image type. The headers must be ordered correctly */ if (i != ch->img_type) { imgerr = true; ch->status = CH_STAT_INVALID; continue; } switch (ch->img_type) { case CH_IT_BIOS: type = CODE_TYPE_PC; break; case CH_IT_MAC: type = CODE_TYPE_OPEN; break; case CH_IT_EFI: type = CODE_TYPE_EFI; break; } switch (ch->img_type) { case CH_IT_FW: case CH_IT_NVR: break; case CH_IT_BIOS: case CH_IT_MAC: case CH_IT_EFI: if (ch->length & 0x1ff) cmperr = true; /* Test if component image is present */ if (ch->length == 0) break; /* Image is present - verify the image */ if (chk_boot((u8 *)fi + ch->image_offset, ch->length) != type) cmperr = true; break; case CH_IT_CFG: /* Test if component image is present */ if (ch->length == 0) { cmperr = true; break; } /* Image is present - verify the image */ if (!chk_cfg((u8 *)fi + ch->image_offset + ch->length, ch->length, NULL)) cmperr = true; break; default: fi->status = FI_STAT_UNKNOWN; return false; } if (cmperr) { imgerr = true; ch->status = CH_STAT_INVALID; } else { ch->status = CH_STAT_PENDING; len += ch->length; } } if (imgerr) { fi->status = FI_STAT_MISSING; return false; } /* Compare fi->length to the sum of ch->length fields */ if (len != fi->length - fc->fi_hdr_len) { fi->status = FI_STAT_LENGTH; return false; } /* Compute the checksum - it should come out zero */ if (fi->checksum != calc_fi_checksum(fc)) { fi->status = FI_STAT_CHKSUM; return false; } return true; } /* Fill in the FS IOCTL response data from a completed request. */ static void esas2r_complete_fs_ioctl(struct esas2r_adapter *a, struct esas2r_request *rq) { struct esas2r_ioctl_fs *fs = (struct esas2r_ioctl_fs *)rq->interrupt_cx; if (rq->vrq->flash.sub_func == VDA_FLASH_COMMIT) esas2r_enable_heartbeat(a); fs->driver_error = rq->req_stat; if (fs->driver_error == RS_SUCCESS) fs->status = ATTO_STS_SUCCESS; else fs->status = ATTO_STS_FAILED; } /* Prepare an FS IOCTL request to be sent to the firmware. */ bool esas2r_process_fs_ioctl(struct esas2r_adapter *a, struct esas2r_ioctl_fs *fs, struct esas2r_request *rq, struct esas2r_sg_context *sgc) { u8 cmdcnt = (u8)ARRAY_SIZE(cmd_to_fls_func); struct esas2r_ioctlfs_command *fsc = &fs->command; u8 func = 0; u32 datalen; fs->status = ATTO_STS_FAILED; fs->driver_error = RS_PENDING; if (fs->version > ESAS2R_FS_VER) { fs->status = ATTO_STS_INV_VERSION; return false; } if (fsc->command >= cmdcnt) { fs->status = ATTO_STS_INV_FUNC; return false; } func = cmd_to_fls_func[fsc->command]; if (func == 0xFF) { fs->status = ATTO_STS_INV_FUNC; return false; } if (fsc->command != ESAS2R_FS_CMD_CANCEL) { if ((a->pcid->device != ATTO_DID_MV_88RC9580 || fs->adap_type != ESAS2R_FS_AT_ESASRAID2) && (a->pcid->device != ATTO_DID_MV_88RC9580TS || fs->adap_type != ESAS2R_FS_AT_TSSASRAID2) && (a->pcid->device != ATTO_DID_MV_88RC9580TSE || fs->adap_type != ESAS2R_FS_AT_TSSASRAID2E) && (a->pcid->device != ATTO_DID_MV_88RC9580TL || fs->adap_type != ESAS2R_FS_AT_TLSASHBA)) { fs->status = ATTO_STS_INV_ADAPTER; return false; } if (fs->driver_ver > ESAS2R_FS_DRVR_VER) { fs->status = ATTO_STS_INV_DRVR_VER; return false; } } if (test_bit(AF_DEGRADED_MODE, &a->flags)) { fs->status = ATTO_STS_DEGRADED; return false; } rq->interrupt_cb = esas2r_complete_fs_ioctl; rq->interrupt_cx = fs; datalen = le32_to_cpu(fsc->length); esas2r_build_flash_req(a, rq, func, fsc->checksum, le32_to_cpu(fsc->flash_addr), datalen); if (func == VDA_FLASH_WRITE || func == VDA_FLASH_READ) { if (datalen == 0) { fs->status = ATTO_STS_INV_FUNC; return false; } esas2r_sgc_init(sgc, a, rq, rq->vrq->flash.data.sge); sgc->length = datalen; if (!esas2r_build_sg_list(a, rq, sgc)) { fs->status = ATTO_STS_OUT_OF_RSRC; return false; } } if (func == VDA_FLASH_COMMIT) esas2r_disable_heartbeat(a); esas2r_start_request(a, rq); return true; } static bool esas2r_flash_access(struct esas2r_adapter *a, u32 function) { u32 starttime; u32 timeout; u32 intstat; u32 doorbell; /* Disable chip interrupts awhile */ if (function == DRBL_FLASH_REQ) esas2r_disable_chip_interrupts(a); /* Issue the request to the firmware */ esas2r_write_register_dword(a, MU_DOORBELL_IN, function); /* Now wait for the firmware to process it */ starttime = jiffies_to_msecs(jiffies); if (test_bit(AF_CHPRST_PENDING, &a->flags) || test_bit(AF_DISC_PENDING, &a->flags)) timeout = 40000; else timeout = 5000; while (true) { intstat = esas2r_read_register_dword(a, MU_INT_STATUS_OUT); if (intstat & MU_INTSTAT_DRBL) { /* Got a doorbell interrupt. Check for the function */ doorbell = esas2r_read_register_dword(a, MU_DOORBELL_OUT); esas2r_write_register_dword(a, MU_DOORBELL_OUT, doorbell); if (doorbell & function) break; } schedule_timeout_interruptible(msecs_to_jiffies(100)); if ((jiffies_to_msecs(jiffies) - starttime) > timeout) { /* * Iimeout. If we were requesting flash access, * indicate we are done so the firmware knows we gave * up. If this was a REQ, we also need to re-enable * chip interrupts. */ if (function == DRBL_FLASH_REQ) { esas2r_hdebug("flash access timeout"); esas2r_write_register_dword(a, MU_DOORBELL_IN, DRBL_FLASH_DONE); esas2r_enable_chip_interrupts(a); } else { esas2r_hdebug("flash release timeout"); } return false; } } /* if we're done, re-enable chip interrupts */ if (function == DRBL_FLASH_DONE) esas2r_enable_chip_interrupts(a); return true; } #define WINDOW_SIZE ((signed int)MW_DATA_WINDOW_SIZE) bool esas2r_read_flash_block(struct esas2r_adapter *a, void *to, u32 from, u32 size) { u8 *end = (u8 *)to; /* Try to acquire access to the flash */ if (!esas2r_flash_access(a, DRBL_FLASH_REQ)) return false; while (size) { u32 len; u32 offset; u32 iatvr; if (test_bit(AF2_SERIAL_FLASH, &a->flags2)) iatvr = MW_DATA_ADDR_SER_FLASH + (from & -WINDOW_SIZE); else iatvr = MW_DATA_ADDR_PAR_FLASH + (from & -WINDOW_SIZE); esas2r_map_data_window(a, iatvr); offset = from & (WINDOW_SIZE - 1); len = size; if (len > WINDOW_SIZE - offset) len = WINDOW_SIZE - offset; from += len; size -= len; while (len--) { *end++ = esas2r_read_data_byte(a, offset); offset++; } } /* Release flash access */ esas2r_flash_access(a, DRBL_FLASH_DONE); return true; } bool esas2r_read_flash_rev(struct esas2r_adapter *a) { u8 bytes[256]; u16 *pw; u16 *pwstart; u16 type; u16 size; u32 sz; sz = sizeof(bytes); pw = (u16 *)(bytes + sz); pwstart = (u16 *)bytes + 2; if (!esas2r_read_flash_block(a, bytes, FLS_OFFSET_CPYR - sz, sz)) goto invalid_rev; while (pw >= pwstart) { pw--; type = le16_to_cpu(*pw); pw--; size = le16_to_cpu(*pw); pw -= size / 2; if (type == FBT_CPYR || type == FBT_SETUP || pw < pwstart) continue; if (type == FBT_FLASH_VER) a->flash_ver = le32_to_cpu(*(u32 *)pw); break; } invalid_rev: return esas2r_print_flash_rev(a); } bool esas2r_print_flash_rev(struct esas2r_adapter *a) { u16 year = LOWORD(a->flash_ver); u8 day = LOBYTE(HIWORD(a->flash_ver)); u8 month = HIBYTE(HIWORD(a->flash_ver)); if (day == 0 || month == 0 || day > 31 || month > 12 || year < 2006 || year > 9999) { strcpy(a->flash_rev, "not found"); a->flash_ver = 0; return false; } sprintf(a->flash_rev, "%02d/%02d/%04d", month, day, year); esas2r_hdebug("flash version: %s", a->flash_rev); return true; } /* * Find the type of boot image type that is currently in the flash. * The chip only has a 64 KB PCI-e expansion ROM * size so only one image can be flashed at a time. */ bool esas2r_read_image_type(struct esas2r_adapter *a) { u8 bytes[256]; struct esas2r_boot_image *bi; struct esas2r_boot_header *bh; u32 sz; u32 len; u32 offset; /* Start at the base of the boot images and look for a valid image */ sz = sizeof(bytes); len = FLS_LENGTH_BOOT; offset = 0; while (true) { if (!esas2r_read_flash_block(a, bytes, FLS_OFFSET_BOOT + offset, sz)) goto invalid_rev; bi = (struct esas2r_boot_image *)bytes; bh = (struct esas2r_boot_header *)((u8 *)bi + le16_to_cpu( bi->header_offset)); if (bi->signature != cpu_to_le16(0xAA55)) goto invalid_rev; if (bh->code_type == CODE_TYPE_PC) { strcpy(a->image_type, "BIOS"); return true; } else if (bh->code_type == CODE_TYPE_EFI) { struct esas2r_efi_image *ei; /* * So we have an EFI image. There are several types * so see which architecture we have. */ ei = (struct esas2r_efi_image *)bytes; switch (le16_to_cpu(ei->machine_type)) { case EFI_MACHINE_IA32: strcpy(a->image_type, "EFI 32-bit"); return true; case EFI_MACHINE_IA64: strcpy(a->image_type, "EFI itanium"); return true; case EFI_MACHINE_X64: strcpy(a->image_type, "EFI 64-bit"); return true; case EFI_MACHINE_EBC: strcpy(a->image_type, "EFI EBC"); return true; default: goto invalid_rev; } } else { u32 thislen; /* jump to the next image */ thislen = (u32)le16_to_cpu(bh->image_length) * 512; if (thislen == 0 || thislen + offset > len || bh->indicator == INDICATOR_LAST) break; offset += thislen; } } invalid_rev: strcpy(a->image_type, "no boot images"); return false; } /* * Read and validate current NVRAM parameters by accessing * physical NVRAM directly. if currently stored parameters are * invalid, use the defaults. */ bool esas2r_nvram_read_direct(struct esas2r_adapter *a) { bool result; if (down_interruptible(&a->nvram_semaphore)) return false; if (!esas2r_read_flash_block(a, a->nvram, FLS_OFFSET_NVR, sizeof(struct esas2r_sas_nvram))) { esas2r_hdebug("NVRAM read failed, using defaults"); return false; } result = esas2r_nvram_validate(a); up(&a->nvram_semaphore); return result; } /* Interrupt callback to process NVRAM completions. */ static void esas2r_nvram_callback(struct esas2r_adapter *a, struct esas2r_request *rq) { struct atto_vda_flash_req *vrq = &rq->vrq->flash; if (rq->req_stat == RS_SUCCESS) { /* last request was successful. see what to do now. */ switch (vrq->sub_func) { case VDA_FLASH_BEGINW: vrq->sub_func = VDA_FLASH_WRITE; rq->req_stat = RS_PENDING; break; case VDA_FLASH_WRITE: vrq->sub_func = VDA_FLASH_COMMIT; rq->req_stat = RS_PENDING; break; case VDA_FLASH_READ: esas2r_nvram_validate(a); break; case VDA_FLASH_COMMIT: default: break; } } if (rq->req_stat != RS_PENDING) { /* update the NVRAM state */ if (rq->req_stat == RS_SUCCESS) set_bit(AF_NVR_VALID, &a->flags); else clear_bit(AF_NVR_VALID, &a->flags); esas2r_enable_heartbeat(a); up(&a->nvram_semaphore); } } /* * Write the contents of nvram to the adapter's physical NVRAM. * The cached copy of the NVRAM is also updated. */ bool esas2r_nvram_write(struct esas2r_adapter *a, struct esas2r_request *rq, struct esas2r_sas_nvram *nvram) { struct esas2r_sas_nvram *n = nvram; u8 sas_address_bytes[8]; u32 *sas_address_dwords = (u32 *)&sas_address_bytes[0]; struct atto_vda_flash_req *vrq = &rq->vrq->flash; if (test_bit(AF_DEGRADED_MODE, &a->flags)) return false; if (down_interruptible(&a->nvram_semaphore)) return false; if (n == NULL) n = a->nvram; /* check the validity of the settings */ if (n->version > SASNVR_VERSION) { up(&a->nvram_semaphore); return false; } memcpy(&sas_address_bytes[0], n->sas_addr, 8); if (sas_address_bytes[0] != 0x50 || sas_address_bytes[1] != 0x01 || sas_address_bytes[2] != 0x08 || (sas_address_bytes[3] & 0xF0) != 0x60 || ((sas_address_bytes[3] & 0x0F) | sas_address_dwords[1]) == 0) { up(&a->nvram_semaphore); return false; } if (n->spin_up_delay > SASNVR_SPINUP_MAX) n->spin_up_delay = SASNVR_SPINUP_MAX; n->version = SASNVR_VERSION; n->checksum = n->checksum - esas2r_nvramcalc_cksum(n); memcpy(a->nvram, n, sizeof(struct esas2r_sas_nvram)); /* write the NVRAM */ n = a->nvram; esas2r_disable_heartbeat(a); esas2r_build_flash_req(a, rq, VDA_FLASH_BEGINW, esas2r_nvramcalc_xor_cksum(n), FLS_OFFSET_NVR, sizeof(struct esas2r_sas_nvram)); if (test_bit(AF_LEGACY_SGE_MODE, &a->flags)) { vrq->data.sge[0].length = cpu_to_le32(SGE_LAST | sizeof(struct esas2r_sas_nvram)); vrq->data.sge[0].address = cpu_to_le64( a->uncached_phys + (u64)((u8 *)n - a->uncached)); } else { vrq->data.prde[0].ctl_len = cpu_to_le32(sizeof(struct esas2r_sas_nvram)); vrq->data.prde[0].address = cpu_to_le64( a->uncached_phys + (u64)((u8 *)n - a->uncached)); } rq->interrupt_cb = esas2r_nvram_callback; esas2r_start_request(a, rq); return true; } /* Validate the cached NVRAM. if the NVRAM is invalid, load the defaults. */ bool esas2r_nvram_validate(struct esas2r_adapter *a) { struct esas2r_sas_nvram *n = a->nvram; bool rslt = false; if (n->signature[0] != 'E' || n->signature[1] != 'S' || n->signature[2] != 'A' || n->signature[3] != 'S') { esas2r_hdebug("invalid NVRAM signature"); } else if (esas2r_nvramcalc_cksum(n)) { esas2r_hdebug("invalid NVRAM checksum"); } else if (n->version > SASNVR_VERSION) { esas2r_hdebug("invalid NVRAM version"); } else { set_bit(AF_NVR_VALID, &a->flags); rslt = true; } if (rslt == false) { esas2r_hdebug("using defaults"); esas2r_nvram_set_defaults(a); } return rslt; } /* * Set the cached NVRAM to defaults. note that this function sets the default * NVRAM when it has been determined that the physical NVRAM is invalid. * In this case, the SAS address is fabricated. */ void esas2r_nvram_set_defaults(struct esas2r_adapter *a) { struct esas2r_sas_nvram *n = a->nvram; u32 time = jiffies_to_msecs(jiffies); clear_bit(AF_NVR_VALID, &a->flags); *n = default_sas_nvram; n->sas_addr[3] |= 0x0F; n->sas_addr[4] = HIBYTE(LOWORD(time)); n->sas_addr[5] = LOBYTE(LOWORD(time)); n->sas_addr[6] = a->pcid->bus->number; n->sas_addr[7] = a->pcid->devfn; } void esas2r_nvram_get_defaults(struct esas2r_adapter *a, struct esas2r_sas_nvram *nvram) { u8 sas_addr[8]; /* * in case we are copying the defaults into the adapter, copy the SAS * address out first. */ memcpy(&sas_addr[0], a->nvram->sas_addr, 8); *nvram = default_sas_nvram; memcpy(&nvram->sas_addr[0], &sas_addr[0], 8); } bool esas2r_fm_api(struct esas2r_adapter *a, struct esas2r_flash_img *fi, struct esas2r_request *rq, struct esas2r_sg_context *sgc) { struct esas2r_flash_context *fc = &a->flash_context; u8 j; struct esas2r_component_header *ch; if (test_and_set_bit(AF_FLASH_LOCK, &a->flags)) { /* flag was already set */ fi->status = FI_STAT_BUSY; return false; } memcpy(&fc->sgc, sgc, sizeof(struct esas2r_sg_context)); sgc = &fc->sgc; fc->fi = fi; fc->sgc_offset = sgc->cur_offset; rq->req_stat = RS_SUCCESS; rq->interrupt_cx = fc; switch (fi->fi_version) { case FI_VERSION_1: fc->scratch = ((struct esas2r_flash_img *)fi)->scratch_buf; fc->num_comps = FI_NUM_COMPS_V1; fc->fi_hdr_len = sizeof(struct esas2r_flash_img); break; default: return complete_fmapi_req(a, rq, FI_STAT_IMG_VER); } if (test_bit(AF_DEGRADED_MODE, &a->flags)) return complete_fmapi_req(a, rq, FI_STAT_DEGRADED); switch (fi->action) { case FI_ACT_DOWN: /* Download the components */ /* Verify the format of the flash image */ if (!verify_fi(a, fc)) return complete_fmapi_req(a, rq, fi->status); /* Adjust the BIOS fields that are dependent on the HBA */ ch = &fi->cmp_hdr[CH_IT_BIOS]; if (ch->length) fix_bios(a, fi); /* Adjust the EFI fields that are dependent on the HBA */ ch = &fi->cmp_hdr[CH_IT_EFI]; if (ch->length) fix_efi(a, fi); /* * Since the image was just modified, compute the checksum on * the modified image. First update the CRC for the composite * expansion ROM image. */ fi->checksum = calc_fi_checksum(fc); /* Disable the heartbeat */ esas2r_disable_heartbeat(a); /* Now start up the download sequence */ fc->task = FMTSK_ERASE_BOOT; fc->func = VDA_FLASH_BEGINW; fc->comp_typ = CH_IT_CFG; fc->flsh_addr = FLS_OFFSET_BOOT; fc->sgc.length = FLS_LENGTH_BOOT; fc->sgc.cur_offset = NULL; /* Setup the callback address */ fc->interrupt_cb = fw_download_proc; break; case FI_ACT_UPSZ: /* Get upload sizes */ fi->adap_typ = get_fi_adap_type(a); fi->flags = 0; fi->num_comps = fc->num_comps; fi->length = fc->fi_hdr_len; /* Report the type of boot image in the rel_version string */ memcpy(fi->rel_version, a->image_type, sizeof(fi->rel_version)); /* Build the component headers */ for (j = 0, ch = fi->cmp_hdr; j < fi->num_comps; j++, ch++) { ch->img_type = j; ch->status = CH_STAT_PENDING; ch->length = 0; ch->version = 0xffffffff; ch->image_offset = 0; ch->pad[0] = 0; ch->pad[1] = 0; } if (a->flash_ver != 0) { fi->cmp_hdr[CH_IT_BIOS].version = fi->cmp_hdr[CH_IT_MAC].version = fi->cmp_hdr[CH_IT_EFI].version = fi->cmp_hdr[CH_IT_CFG].version = a->flash_ver; fi->cmp_hdr[CH_IT_BIOS].status = fi->cmp_hdr[CH_IT_MAC].status = fi->cmp_hdr[CH_IT_EFI].status = fi->cmp_hdr[CH_IT_CFG].status = CH_STAT_SUCCESS; return complete_fmapi_req(a, rq, FI_STAT_SUCCESS); } /* fall through */ case FI_ACT_UP: /* Upload the components */ default: return complete_fmapi_req(a, rq, FI_STAT_INVALID); } /* * If we make it here, fc has been setup to do the first task. Call * load_image to format the request, start it, and get out. The * interrupt code will call the callback when the first message is * complete. */ if (!load_image(a, rq)) return complete_fmapi_req(a, rq, FI_STAT_FAILED); esas2r_start_request(a, rq); return true; }