aoeblk.c 11 KB
Newer Older
Ed Cashin's avatar
Ed Cashin committed
1
/* Copyright (c) 2013 Coraid, Inc.  See COPYING for GPL terms. */
Linus Torvalds's avatar
Linus Torvalds committed
2 3 4 5 6
/*
 * aoeblk.c
 * block device routines
 */

7
#include <linux/kernel.h>
Linus Torvalds's avatar
Linus Torvalds committed
8 9
#include <linux/hdreg.h>
#include <linux/blkdev.h>
10
#include <linux/backing-dev.h>
Linus Torvalds's avatar
Linus Torvalds committed
11 12
#include <linux/fs.h>
#include <linux/ioctl.h>
13
#include <linux/slab.h>
14
#include <linux/ratelimit.h>
Linus Torvalds's avatar
Linus Torvalds committed
15 16
#include <linux/genhd.h>
#include <linux/netdevice.h>
17
#include <linux/mutex.h>
18
#include <linux/export.h>
19
#include <linux/moduleparam.h>
20
#include <linux/debugfs.h>
21
#include <scsi/sg.h>
Linus Torvalds's avatar
Linus Torvalds committed
22 23
#include "aoe.h"

24
static DEFINE_MUTEX(aoeblk_mutex);
25
static struct kmem_cache *buf_pool_cache;
26
static struct dentry *aoe_debugfs_dir;
Linus Torvalds's avatar
Linus Torvalds committed
27

28 29 30 31 32 33
/* GPFS needs a larger value than the default. */
static int aoe_maxsectors;
module_param(aoe_maxsectors, int, 0644);
MODULE_PARM_DESC(aoe_maxsectors,
	"When nonzero, set the maximum number of sectors per I/O request");

34 35
static ssize_t aoedisk_show_state(struct device *dev,
				  struct device_attribute *attr, char *page)
Linus Torvalds's avatar
Linus Torvalds committed
36
{
37
	struct gendisk *disk = dev_to_disk(dev);
Linus Torvalds's avatar
Linus Torvalds committed
38 39 40 41 42
	struct aoedev *d = disk->private_data;

	return snprintf(page, PAGE_SIZE,
			"%s%s\n",
			(d->flags & DEVFL_UP) ? "up" : "down",
43
			(d->flags & DEVFL_KICKME) ? ",kickme" :
44 45
			(d->nopen && !(d->flags & DEVFL_UP)) ? ",closewait" : "");
	/* I'd rather see nopen exported so we can ditch closewait */
Linus Torvalds's avatar
Linus Torvalds committed
46
}
47 48
static ssize_t aoedisk_show_mac(struct device *dev,
				struct device_attribute *attr, char *page)
Linus Torvalds's avatar
Linus Torvalds committed
49
{
50
	struct gendisk *disk = dev_to_disk(dev);
Linus Torvalds's avatar
Linus Torvalds committed
51
	struct aoedev *d = disk->private_data;
52
	struct aoetgt *t = d->targets[0];
Linus Torvalds's avatar
Linus Torvalds committed
53

54 55
	if (t == NULL)
		return snprintf(page, PAGE_SIZE, "none\n");
56
	return snprintf(page, PAGE_SIZE, "%pm\n", t->addr);
Linus Torvalds's avatar
Linus Torvalds committed
57
}
58 59
static ssize_t aoedisk_show_netif(struct device *dev,
				  struct device_attribute *attr, char *page)
Linus Torvalds's avatar
Linus Torvalds committed
60
{
61
	struct gendisk *disk = dev_to_disk(dev);
Linus Torvalds's avatar
Linus Torvalds committed
62
	struct aoedev *d = disk->private_data;
63 64 65 66 67 68 69 70 71
	struct net_device *nds[8], **nd, **nnd, **ne;
	struct aoetgt **t, **te;
	struct aoeif *ifp, *e;
	char *p;

	memset(nds, 0, sizeof nds);
	nd = nds;
	ne = nd + ARRAY_SIZE(nds);
	t = d->targets;
72
	te = t + d->ntargets;
73 74 75 76 77 78 79 80 81 82 83
	for (; t < te && *t; t++) {
		ifp = (*t)->ifs;
		e = ifp + NAOEIFS;
		for (; ifp < e && ifp->nd; ifp++) {
			for (nnd = nds; nnd < nd; nnd++)
				if (*nnd == ifp->nd)
					break;
			if (nnd == nd && nd != ne)
				*nd++ = ifp->nd;
		}
	}
Linus Torvalds's avatar
Linus Torvalds committed
84

85 86 87 88 89 90 91 92 93
	ne = nd;
	nd = nds;
	if (*nd == NULL)
		return snprintf(page, PAGE_SIZE, "none\n");
	for (p = page; nd < ne; nd++)
		p += snprintf(p, PAGE_SIZE - (p-page), "%s%s",
			p == page ? "" : ",", (*nd)->name);
	p += snprintf(p, PAGE_SIZE - (p-page), "\n");
	return p-page;
Linus Torvalds's avatar
Linus Torvalds committed
94
}
95
/* firmware version */
96 97
static ssize_t aoedisk_show_fwver(struct device *dev,
				  struct device_attribute *attr, char *page)
98
{
99
	struct gendisk *disk = dev_to_disk(dev);
100 101 102 103
	struct aoedev *d = disk->private_data;

	return snprintf(page, PAGE_SIZE, "0x%04x\n", (unsigned int) d->fw_ver);
}
104 105 106 107 108 109 110 111
static ssize_t aoedisk_show_payload(struct device *dev,
				    struct device_attribute *attr, char *page)
{
	struct gendisk *disk = dev_to_disk(dev);
	struct aoedev *d = disk->private_data;

	return snprintf(page, PAGE_SIZE, "%lu\n", d->maxbcnt);
}
Linus Torvalds's avatar
Linus Torvalds committed
112

113 114 115
static int aoedisk_debugfs_show(struct seq_file *s, void *ignored)
{
	struct aoedev *d;
116 117
	struct aoetgt **t, **te;
	struct aoeif *ifp, *ife;
118
	unsigned long flags;
119
	char c;
120 121

	d = s->private;
122 123 124 125 126 127 128 129
	seq_printf(s, "rttavg: %d rttdev: %d\n",
		d->rttavg >> RTTSCALE,
		d->rttdev >> RTTDSCALE);
	seq_printf(s, "nskbpool: %d\n", skb_queue_len(&d->skbpool));
	seq_printf(s, "kicked: %ld\n", d->kicked);
	seq_printf(s, "maxbcnt: %ld\n", d->maxbcnt);
	seq_printf(s, "ref: %ld\n", d->ref);

130
	spin_lock_irqsave(&d->lock, flags);
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
	t = d->targets;
	te = t + d->ntargets;
	for (; t < te && *t; t++) {
		c = '\t';
		seq_printf(s, "falloc: %ld\n", (*t)->falloc);
		seq_printf(s, "ffree: %p\n",
			list_empty(&(*t)->ffree) ? NULL : (*t)->ffree.next);
		seq_printf(s, "%pm:%d:%d:%d\n", (*t)->addr, (*t)->nout,
			(*t)->maxout, (*t)->nframes);
		seq_printf(s, "\tssthresh:%d\n", (*t)->ssthresh);
		seq_printf(s, "\ttaint:%d\n", (*t)->taint);
		seq_printf(s, "\tr:%d\n", (*t)->rpkts);
		seq_printf(s, "\tw:%d\n", (*t)->wpkts);
		ifp = (*t)->ifs;
		ife = ifp + ARRAY_SIZE((*t)->ifs);
		for (; ifp->nd && ifp < ife; ifp++) {
			seq_printf(s, "%c%s", c, ifp->nd->name);
			c = ',';
		}
		seq_puts(s, "\n");
	}
152 153 154 155 156 157 158 159 160 161
	spin_unlock_irqrestore(&d->lock, flags);

	return 0;
}

static int aoe_debugfs_open(struct inode *inode, struct file *file)
{
	return single_open(file, aoedisk_debugfs_show, inode->i_private);
}

162 163 164 165
static DEVICE_ATTR(state, S_IRUGO, aoedisk_show_state, NULL);
static DEVICE_ATTR(mac, S_IRUGO, aoedisk_show_mac, NULL);
static DEVICE_ATTR(netif, S_IRUGO, aoedisk_show_netif, NULL);
static struct device_attribute dev_attr_firmware_version = {
166
	.attr = { .name = "firmware-version", .mode = S_IRUGO },
167
	.show = aoedisk_show_fwver,
168
};
169
static DEVICE_ATTR(payload, S_IRUGO, aoedisk_show_payload, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
170

171
static struct attribute *aoe_attrs[] = {
172 173 174 175
	&dev_attr_state.attr,
	&dev_attr_mac.attr,
	&dev_attr_netif.attr,
	&dev_attr_firmware_version.attr,
176
	&dev_attr_payload.attr,
177
	NULL,
178 179 180 181 182 183
};

static const struct attribute_group attr_group = {
	.attrs = aoe_attrs,
};

184 185 186 187 188 189
static const struct file_operations aoe_debugfs_fops = {
	.open = aoe_debugfs_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};
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

static void
aoedisk_add_debugfs(struct aoedev *d)
{
	struct dentry *entry;
	char *p;

	if (aoe_debugfs_dir == NULL)
		return;
	p = strchr(d->gd->disk_name, '/');
	if (p == NULL)
		p = d->gd->disk_name;
	else
		p++;
	BUG_ON(*p == '\0');
	entry = debugfs_create_file(p, 0444, aoe_debugfs_dir, d,
				    &aoe_debugfs_fops);
	if (IS_ERR_OR_NULL(entry)) {
		pr_info("aoe: cannot create debugfs file for %s\n",
			d->gd->disk_name);
		return;
	}
	BUG_ON(d->debugfs);
	d->debugfs = entry;
}
void
aoedisk_rm_debugfs(struct aoedev *d)
{
	debugfs_remove(d->debugfs);
	d->debugfs = NULL;
}

222
static int
Linus Torvalds's avatar
Linus Torvalds committed
223 224
aoedisk_add_sysfs(struct aoedev *d)
{
225
	return sysfs_create_group(&disk_to_dev(d->gd)->kobj, &attr_group);
Linus Torvalds's avatar
Linus Torvalds committed
226 227 228 229
}
void
aoedisk_rm_sysfs(struct aoedev *d)
{
230
	sysfs_remove_group(&disk_to_dev(d->gd)->kobj, &attr_group);
Linus Torvalds's avatar
Linus Torvalds committed
231 232 233
}

static int
Al Viro's avatar
Al Viro committed
234
aoeblk_open(struct block_device *bdev, fmode_t mode)
Linus Torvalds's avatar
Linus Torvalds committed
235
{
Al Viro's avatar
Al Viro committed
236
	struct aoedev *d = bdev->bd_disk->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
237 238
	ulong flags;

239 240 241 242 243 244 245 246 247
	if (!virt_addr_valid(d)) {
		pr_crit("aoe: invalid device pointer in %s\n",
			__func__);
		WARN_ON(1);
		return -ENODEV;
	}
	if (!(d->flags & DEVFL_UP) || d->flags & DEVFL_TKILL)
		return -ENODEV;

248
	mutex_lock(&aoeblk_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
249
	spin_lock_irqsave(&d->lock, flags);
250
	if (d->flags & DEVFL_UP && !(d->flags & DEVFL_TKILL)) {
Linus Torvalds's avatar
Linus Torvalds committed
251 252
		d->nopen++;
		spin_unlock_irqrestore(&d->lock, flags);
253
		mutex_unlock(&aoeblk_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
254 255 256
		return 0;
	}
	spin_unlock_irqrestore(&d->lock, flags);
257
	mutex_unlock(&aoeblk_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
258 259 260
	return -ENODEV;
}

261
static void
Al Viro's avatar
Al Viro committed
262
aoeblk_release(struct gendisk *disk, fmode_t mode)
Linus Torvalds's avatar
Linus Torvalds committed
263
{
Al Viro's avatar
Al Viro committed
264
	struct aoedev *d = disk->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
265 266 267 268
	ulong flags;

	spin_lock_irqsave(&d->lock, flags);

269
	if (--d->nopen == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
270 271
		spin_unlock_irqrestore(&d->lock, flags);
		aoecmd_cfg(d->aoemajor, d->aoeminor);
272
		return;
Linus Torvalds's avatar
Linus Torvalds committed
273 274 275 276
	}
	spin_unlock_irqrestore(&d->lock, flags);
}

277
static void
278
aoeblk_request(struct request_queue *q)
Linus Torvalds's avatar
Linus Torvalds committed
279 280
{
	struct aoedev *d;
281
	struct request *rq;
Linus Torvalds's avatar
Linus Torvalds committed
282

283
	d = q->queuedata;
Linus Torvalds's avatar
Linus Torvalds committed
284
	if ((d->flags & DEVFL_UP) == 0) {
285
		pr_info_ratelimited("aoe: device %ld.%d is not up\n",
Ed L. Cashin's avatar
Ed L. Cashin committed
286
			d->aoemajor, d->aoeminor);
287 288 289 290
		while ((rq = blk_peek_request(q))) {
			blk_start_request(rq);
			aoe_end_request(d, rq, 1);
		}
291
		return;
Linus Torvalds's avatar
Linus Torvalds committed
292
	}
293
	aoecmd_work(d);
Linus Torvalds's avatar
Linus Torvalds committed
294 295 296
}

static int
297
aoeblk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
Linus Torvalds's avatar
Linus Torvalds committed
298
{
299
	struct aoedev *d = bdev->bd_disk->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
300 301

	if ((d->flags & DEVFL_UP) == 0) {
Ed L. Cashin's avatar
Ed L. Cashin committed
302
		printk(KERN_ERR "aoe: disk not up\n");
Linus Torvalds's avatar
Linus Torvalds committed
303 304 305
		return -ENODEV;
	}

306 307 308 309
	geo->cylinders = d->geo.cylinders;
	geo->heads = d->geo.heads;
	geo->sectors = d->geo.sectors;
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
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
static int
aoeblk_ioctl(struct block_device *bdev, fmode_t mode, uint cmd, ulong arg)
{
	struct aoedev *d;

	if (!arg)
		return -EINVAL;

	d = bdev->bd_disk->private_data;
	if ((d->flags & DEVFL_UP) == 0) {
		pr_err("aoe: disk not up\n");
		return -ENODEV;
	}

	if (cmd == HDIO_GET_IDENTITY) {
		if (!copy_to_user((void __user *) arg, &d->ident,
			sizeof(d->ident)))
			return 0;
		return -EFAULT;
	}

	/* udev calls scsi_id, which uses SG_IO, resulting in noise */
	if (cmd != SG_IO)
		pr_info("aoe: unknown ioctl 0x%x\n", cmd);

	return -ENOTTY;
}

340
static const struct block_device_operations aoe_bdops = {
Al Viro's avatar
Al Viro committed
341 342
	.open = aoeblk_open,
	.release = aoeblk_release,
343
	.ioctl = aoeblk_ioctl,
344
	.getgeo = aoeblk_getgeo,
Linus Torvalds's avatar
Linus Torvalds committed
345 346 347 348 349 350 351 352 353
	.owner = THIS_MODULE,
};

/* alloc_disk and add_disk can sleep */
void
aoeblk_gdalloc(void *vp)
{
	struct aoedev *d = vp;
	struct gendisk *gd;
354 355 356
	mempool_t *mp;
	struct request_queue *q;
	enum { KB = 1024, MB = KB * KB, READ_AHEAD = 2 * MB, };
Linus Torvalds's avatar
Linus Torvalds committed
357
	ulong flags;
358 359 360 361 362 363 364 365 366 367 368 369
	int late = 0;

	spin_lock_irqsave(&d->lock, flags);
	if (d->flags & DEVFL_GDALLOC
	&& !(d->flags & DEVFL_TKILL)
	&& !(d->flags & DEVFL_GD_NOW))
		d->flags |= DEVFL_GD_NOW;
	else
		late = 1;
	spin_unlock_irqrestore(&d->lock, flags);
	if (late)
		return;
Linus Torvalds's avatar
Linus Torvalds committed
370 371 372

	gd = alloc_disk(AOE_PARTITIONS);
	if (gd == NULL) {
373
		pr_err("aoe: cannot allocate disk structure for %ld.%d\n",
374
			d->aoemajor, d->aoeminor);
375
		goto err;
Linus Torvalds's avatar
Linus Torvalds committed
376 377
	}

378 379 380
	mp = mempool_create(MIN_BUFS, mempool_alloc_slab, mempool_free_slab,
		buf_pool_cache);
	if (mp == NULL) {
381
		printk(KERN_ERR "aoe: cannot allocate bufpool for %ld.%d\n",
382
			d->aoemajor, d->aoeminor);
383
		goto err_disk;
Linus Torvalds's avatar
Linus Torvalds committed
384
	}
385 386 387 388
	q = blk_init_queue(aoeblk_request, &d->lock);
	if (q == NULL) {
		pr_err("aoe: cannot allocate block queue for %ld.%d\n",
			d->aoemajor, d->aoeminor);
389
		goto err_mempool;
390
	}
Linus Torvalds's avatar
Linus Torvalds committed
391

392
	spin_lock_irqsave(&d->lock, flags);
393 394 395 396 397
	WARN_ON(!(d->flags & DEVFL_GD_NOW));
	WARN_ON(!(d->flags & DEVFL_GDALLOC));
	WARN_ON(d->flags & DEVFL_TKILL);
	WARN_ON(d->gd);
	WARN_ON(d->flags & DEVFL_UP);
398
	blk_queue_max_hw_sectors(q, BLK_DEF_MAX_SECTORS);
399
	q->backing_dev_info.name = "aoe";
400
	q->backing_dev_info.ra_pages = READ_AHEAD / PAGE_SIZE;
401 402 403 404
	d->bufpool = mp;
	d->blkq = gd->queue = q;
	q->queuedata = d;
	d->gd = gd;
405 406
	if (aoe_maxsectors)
		blk_queue_max_hw_sectors(q, aoe_maxsectors);
Linus Torvalds's avatar
Linus Torvalds committed
407
	gd->major = AOE_MAJOR;
408
	gd->first_minor = d->sysminor;
Linus Torvalds's avatar
Linus Torvalds committed
409 410
	gd->fops = &aoe_bdops;
	gd->private_data = d;
411
	set_capacity(gd, d->ssize);
412
	snprintf(gd->disk_name, sizeof gd->disk_name, "etherd/e%ld.%d",
Linus Torvalds's avatar
Linus Torvalds committed
413 414
		d->aoemajor, d->aoeminor);

415
	d->flags &= ~DEVFL_GDALLOC;
Linus Torvalds's avatar
Linus Torvalds committed
416 417 418 419 420 421
	d->flags |= DEVFL_UP;

	spin_unlock_irqrestore(&d->lock, flags);

	add_disk(gd);
	aoedisk_add_sysfs(d);
422
	aoedisk_add_debugfs(d);
423 424 425 426 427

	spin_lock_irqsave(&d->lock, flags);
	WARN_ON(!(d->flags & DEVFL_GD_NOW));
	d->flags &= ~DEVFL_GD_NOW;
	spin_unlock_irqrestore(&d->lock, flags);
428 429 430
	return;

err_mempool:
431
	mempool_destroy(mp);
432 433 434 435
err_disk:
	put_disk(gd);
err:
	spin_lock_irqsave(&d->lock, flags);
436 437
	d->flags &= ~DEVFL_GD_NOW;
	schedule_work(&d->work);
438
	spin_unlock_irqrestore(&d->lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed
439 440 441 442 443
}

void
aoeblk_exit(void)
{
444 445
	debugfs_remove_recursive(aoe_debugfs_dir);
	aoe_debugfs_dir = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
446 447 448 449 450 451
	kmem_cache_destroy(buf_pool_cache);
}

int __init
aoeblk_init(void)
{
452
	buf_pool_cache = kmem_cache_create("aoe_bufs",
Linus Torvalds's avatar
Linus Torvalds committed
453
					   sizeof(struct buf),
454
					   0, 0, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
455 456
	if (buf_pool_cache == NULL)
		return -ENOMEM;
457 458 459 460 461
	aoe_debugfs_dir = debugfs_create_dir("aoe", NULL);
	if (IS_ERR_OR_NULL(aoe_debugfs_dir)) {
		pr_info("aoe: cannot create debugfs directory\n");
		aoe_debugfs_dir = NULL;
	}
Linus Torvalds's avatar
Linus Torvalds committed
462 463 464
	return 0;
}