Revision 475049809977bf3975d78f2d2fd992e19ce2d59e authored by Roel Kluin on 10 March 2009, 19:55:45 UTC, committed by Linus Torvalds on 10 March 2009, 22:55:10 UTC
get_nid_for_pfn() returns int

Presumably the (nid < 0) case has never happened.

We do know that it is happening on one system while creating a symlink for
a memory section so it should also happen on the same system if
unregister_mem_sect_under_nodes() were called to remove the same symlink.

The test was actually added in response to a problem with an earlier
version reported by Yasunori Goto where one or more of the leading pages
of a memory section on the 2nd node of one of his systems was
uninitialized because I believe they coincided with a memory hole.

That earlier version did not ignore uninitialized pages and determined
the nid by considering only the 1st page of each memory section.  This
caused the symlink to the 1st memory section on the 2nd node to be
incorrectly created in /sys/devices/system/node/node0 instead of
/sys/devices/system/node/node1.  The problem was fixed by adding the
test to skip over uninitialized pages.

I suspect we have not seen any reports of the non-removal
of a symlink due to the incorrect declaration of the nid
variable in unregister_mem_sect_under_nodes() because
  - systems where a memory section could have an uninitialized
    range of leading pages are probably rare.
  - memory remove is probably not done very frequently on the
    systems that are capable of demonstrating the problem.
  - lingering symlink(s) that should have been removed may
    have simply gone unnoticed.

[garyhade@us.ibm.com: wrote changelog]
Signed-off-by: Roel Kluin <roel.kluin@gmail.com>
Cc: Gary Hade <garyhade@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 1abaf33
Raw File
ps3flash.c
/*
 * PS3 FLASH ROM Storage Driver
 *
 * Copyright (C) 2007 Sony Computer Entertainment Inc.
 * Copyright 2007 Sony Corp.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published
 * by the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>

#include <asm/lv1call.h>
#include <asm/ps3stor.h>


#define DEVICE_NAME		"ps3flash"

#define FLASH_BLOCK_SIZE	(256*1024)


struct ps3flash_private {
	struct mutex mutex;	/* Bounce buffer mutex */
};

static struct ps3_storage_device *ps3flash_dev;

static ssize_t ps3flash_read_write_sectors(struct ps3_storage_device *dev,
					   u64 lpar, u64 start_sector,
					   u64 sectors, int write)
{
	u64 res = ps3stor_read_write_sectors(dev, lpar, start_sector, sectors,
					     write);
	if (res) {
		dev_err(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__,
			__LINE__, write ? "write" : "read", res);
		return -EIO;
	}
	return sectors;
}

static ssize_t ps3flash_read_sectors(struct ps3_storage_device *dev,
				     u64 start_sector, u64 sectors,
				     unsigned int sector_offset)
{
	u64 max_sectors, lpar;

	max_sectors = dev->bounce_size / dev->blk_size;
	if (sectors > max_sectors) {
		dev_dbg(&dev->sbd.core, "%s:%u Limiting sectors to %llu\n",
			__func__, __LINE__, max_sectors);
		sectors = max_sectors;
	}

	lpar = dev->bounce_lpar + sector_offset * dev->blk_size;
	return ps3flash_read_write_sectors(dev, lpar, start_sector, sectors,
					   0);
}

static ssize_t ps3flash_write_chunk(struct ps3_storage_device *dev,
				    u64 start_sector)
{
       u64 sectors = dev->bounce_size / dev->blk_size;
       return ps3flash_read_write_sectors(dev, dev->bounce_lpar, start_sector,
					  sectors, 1);
}

static loff_t ps3flash_llseek(struct file *file, loff_t offset, int origin)
{
	struct ps3_storage_device *dev = ps3flash_dev;
	loff_t res;

	mutex_lock(&file->f_mapping->host->i_mutex);
	switch (origin) {
	case 1:
		offset += file->f_pos;
		break;
	case 2:
		offset += dev->regions[dev->region_idx].size*dev->blk_size;
		break;
	}
	if (offset < 0) {
		res = -EINVAL;
		goto out;
	}

	file->f_pos = offset;
	res = file->f_pos;

out:
	mutex_unlock(&file->f_mapping->host->i_mutex);
	return res;
}

static ssize_t ps3flash_read(struct file *file, char __user *buf, size_t count,
			     loff_t *pos)
{
	struct ps3_storage_device *dev = ps3flash_dev;
	struct ps3flash_private *priv = dev->sbd.core.driver_data;
	u64 size, start_sector, end_sector, offset;
	ssize_t sectors_read;
	size_t remaining, n;

	dev_dbg(&dev->sbd.core,
		"%s:%u: Reading %zu bytes at position %lld to user 0x%p\n",
		__func__, __LINE__, count, *pos, buf);

	size = dev->regions[dev->region_idx].size*dev->blk_size;
	if (*pos >= size || !count)
		return 0;

	if (*pos + count > size) {
		dev_dbg(&dev->sbd.core,
			"%s:%u Truncating count from %zu to %llu\n", __func__,
			__LINE__, count, size - *pos);
		count = size - *pos;
	}

	start_sector = *pos / dev->blk_size;
	offset = *pos % dev->blk_size;
	end_sector = DIV_ROUND_UP(*pos + count, dev->blk_size);

	remaining = count;
	do {
		mutex_lock(&priv->mutex);

		sectors_read = ps3flash_read_sectors(dev, start_sector,
						     end_sector-start_sector,
						     0);
		if (sectors_read < 0) {
			mutex_unlock(&priv->mutex);
			goto fail;
		}

		n = min_t(u64, remaining, sectors_read*dev->blk_size-offset);
		dev_dbg(&dev->sbd.core,
			"%s:%u: copy %lu bytes from 0x%p to user 0x%p\n",
			__func__, __LINE__, n, dev->bounce_buf+offset, buf);
		if (copy_to_user(buf, dev->bounce_buf+offset, n)) {
			mutex_unlock(&priv->mutex);
			sectors_read = -EFAULT;
			goto fail;
		}

		mutex_unlock(&priv->mutex);

		*pos += n;
		buf += n;
		remaining -= n;
		start_sector += sectors_read;
		offset = 0;
	} while (remaining > 0);

	return count;

fail:
	return sectors_read;
}

static ssize_t ps3flash_write(struct file *file, const char __user *buf,
			      size_t count, loff_t *pos)
{
	struct ps3_storage_device *dev = ps3flash_dev;
	struct ps3flash_private *priv = dev->sbd.core.driver_data;
	u64 size, chunk_sectors, start_write_sector, end_write_sector,
	    end_read_sector, start_read_sector, head, tail, offset;
	ssize_t res;
	size_t remaining, n;
	unsigned int sec_off;

	dev_dbg(&dev->sbd.core,
		"%s:%u: Writing %zu bytes at position %lld from user 0x%p\n",
		__func__, __LINE__, count, *pos, buf);

	size = dev->regions[dev->region_idx].size*dev->blk_size;
	if (*pos >= size || !count)
		return 0;

	if (*pos + count > size) {
		dev_dbg(&dev->sbd.core,
			"%s:%u Truncating count from %zu to %llu\n", __func__,
			__LINE__, count, size - *pos);
		count = size - *pos;
	}

	chunk_sectors = dev->bounce_size / dev->blk_size;

	start_write_sector = *pos / dev->bounce_size * chunk_sectors;
	offset = *pos % dev->bounce_size;
	end_write_sector = DIV_ROUND_UP(*pos + count, dev->bounce_size) *
			   chunk_sectors;

	end_read_sector = DIV_ROUND_UP(*pos, dev->blk_size);
	start_read_sector = (*pos + count) / dev->blk_size;

	/*
	 * As we have to write in 256 KiB chunks, while we can read in blk_size
	 * (usually 512 bytes) chunks, we perform the following steps:
	 *   1. Read from start_write_sector to end_read_sector ("head")
	 *   2. Read from start_read_sector to end_write_sector ("tail")
	 *   3. Copy data to buffer
	 *   4. Write from start_write_sector to end_write_sector
	 * All of this is complicated by using only one 256 KiB bounce buffer.
	 */

	head = end_read_sector - start_write_sector;
	tail = end_write_sector - start_read_sector;

	remaining = count;
	do {
		mutex_lock(&priv->mutex);

		if (end_read_sector >= start_read_sector) {
			/* Merge head and tail */
			dev_dbg(&dev->sbd.core,
				"Merged head and tail: %llu sectors at %llu\n",
				chunk_sectors, start_write_sector);
			res = ps3flash_read_sectors(dev, start_write_sector,
						    chunk_sectors, 0);
			if (res < 0)
				goto fail;
		} else {
			if (head) {
				/* Read head */
				dev_dbg(&dev->sbd.core,
					"head: %llu sectors at %llu\n", head,
					start_write_sector);
				res = ps3flash_read_sectors(dev,
							    start_write_sector,
							    head, 0);
				if (res < 0)
					goto fail;
			}
			if (start_read_sector <
			    start_write_sector+chunk_sectors) {
				/* Read tail */
				dev_dbg(&dev->sbd.core,
					"tail: %llu sectors at %llu\n", tail,
					start_read_sector);
				sec_off = start_read_sector-start_write_sector;
				res = ps3flash_read_sectors(dev,
							    start_read_sector,
							    tail, sec_off);
				if (res < 0)
					goto fail;
			}
		}

		n = min_t(u64, remaining, dev->bounce_size-offset);
		dev_dbg(&dev->sbd.core,
			"%s:%u: copy %lu bytes from user 0x%p to 0x%p\n",
			__func__, __LINE__, n, buf, dev->bounce_buf+offset);
		if (copy_from_user(dev->bounce_buf+offset, buf, n)) {
			res = -EFAULT;
			goto fail;
		}

		res = ps3flash_write_chunk(dev, start_write_sector);
		if (res < 0)
			goto fail;

		mutex_unlock(&priv->mutex);

		*pos += n;
		buf += n;
		remaining -= n;
		start_write_sector += chunk_sectors;
		head = 0;
		offset = 0;
	} while (remaining > 0);

	return count;

fail:
	mutex_unlock(&priv->mutex);
	return res;
}


static irqreturn_t ps3flash_interrupt(int irq, void *data)
{
	struct ps3_storage_device *dev = data;
	int res;
	u64 tag, status;

	res = lv1_storage_get_async_status(dev->sbd.dev_id, &tag, &status);

	if (tag != dev->tag)
		dev_err(&dev->sbd.core,
			"%s:%u: tag mismatch, got %llx, expected %llx\n",
			__func__, __LINE__, tag, dev->tag);

	if (res) {
		dev_err(&dev->sbd.core, "%s:%u: res=%d status=0x%llx\n",
			__func__, __LINE__, res, status);
	} else {
		dev->lv1_status = status;
		complete(&dev->done);
	}
	return IRQ_HANDLED;
}


static const struct file_operations ps3flash_fops = {
	.owner	= THIS_MODULE,
	.llseek	= ps3flash_llseek,
	.read	= ps3flash_read,
	.write	= ps3flash_write,
};

static struct miscdevice ps3flash_misc = {
	.minor	= MISC_DYNAMIC_MINOR,
	.name	= DEVICE_NAME,
	.fops	= &ps3flash_fops,
};

static int __devinit ps3flash_probe(struct ps3_system_bus_device *_dev)
{
	struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core);
	struct ps3flash_private *priv;
	int error;
	unsigned long tmp;

	tmp = dev->regions[dev->region_idx].start*dev->blk_size;
	if (tmp % FLASH_BLOCK_SIZE) {
		dev_err(&dev->sbd.core,
			"%s:%u region start %lu is not aligned\n", __func__,
			__LINE__, tmp);
		return -EINVAL;
	}
	tmp = dev->regions[dev->region_idx].size*dev->blk_size;
	if (tmp % FLASH_BLOCK_SIZE) {
		dev_err(&dev->sbd.core,
			"%s:%u region size %lu is not aligned\n", __func__,
			__LINE__, tmp);
		return -EINVAL;
	}

	/* use static buffer, kmalloc cannot allocate 256 KiB */
	if (!ps3flash_bounce_buffer.address)
		return -ENODEV;

	if (ps3flash_dev) {
		dev_err(&dev->sbd.core,
			"Only one FLASH device is supported\n");
		return -EBUSY;
	}

	ps3flash_dev = dev;

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		error = -ENOMEM;
		goto fail;
	}

	dev->sbd.core.driver_data = priv;
	mutex_init(&priv->mutex);

	dev->bounce_size = ps3flash_bounce_buffer.size;
	dev->bounce_buf = ps3flash_bounce_buffer.address;

	error = ps3stor_setup(dev, ps3flash_interrupt);
	if (error)
		goto fail_free_priv;

	ps3flash_misc.parent = &dev->sbd.core;
	error = misc_register(&ps3flash_misc);
	if (error) {
		dev_err(&dev->sbd.core, "%s:%u: misc_register failed %d\n",
			__func__, __LINE__, error);
		goto fail_teardown;
	}

	dev_info(&dev->sbd.core, "%s:%u: registered misc device %d\n",
		 __func__, __LINE__, ps3flash_misc.minor);
	return 0;

fail_teardown:
	ps3stor_teardown(dev);
fail_free_priv:
	kfree(priv);
	dev->sbd.core.driver_data = NULL;
fail:
	ps3flash_dev = NULL;
	return error;
}

static int ps3flash_remove(struct ps3_system_bus_device *_dev)
{
	struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core);

	misc_deregister(&ps3flash_misc);
	ps3stor_teardown(dev);
	kfree(dev->sbd.core.driver_data);
	dev->sbd.core.driver_data = NULL;
	ps3flash_dev = NULL;
	return 0;
}


static struct ps3_system_bus_driver ps3flash = {
	.match_id	= PS3_MATCH_ID_STOR_FLASH,
	.core.name	= DEVICE_NAME,
	.core.owner	= THIS_MODULE,
	.probe		= ps3flash_probe,
	.remove		= ps3flash_remove,
	.shutdown	= ps3flash_remove,
};


static int __init ps3flash_init(void)
{
	return ps3_system_bus_driver_register(&ps3flash);
}

static void __exit ps3flash_exit(void)
{
	ps3_system_bus_driver_unregister(&ps3flash);
}

module_init(ps3flash_init);
module_exit(ps3flash_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PS3 FLASH ROM Storage Driver");
MODULE_AUTHOR("Sony Corporation");
MODULE_ALIAS(PS3_MODULE_ALIAS_STOR_FLASH);
back to top