f1d0b063d9
Devices which share the same queue, like floppies and mtd devices, get registered multiple times in the bdi interface, but bdi accounts only the last registered device of the devices sharing one queue. On remove, all earlier registered devices leak, stay around in sysfs, and cause "duplicate filename" errors if the devices are re-created. This prevents the creation of multiple bdi interfaces per queue, and the bdi device will carry the dev_t name of the block device which is the first one registered, of the pool of devices using the same queue. [akpm@linux-foundation.org: add a WARN_ON so we know which drivers are misbehaving] Tested-by: Peter Korsgaard <jacmet@sunsite.dk> Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Signed-off-by: Kay Sievers <kay.sievers@vrfy.org> Cc: David Woodhouse <dwmw2@infradead.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
306 lines
6.9 KiB
C
306 lines
6.9 KiB
C
|
|
#include <linux/wait.h>
|
|
#include <linux/backing-dev.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/module.h>
|
|
#include <linux/writeback.h>
|
|
#include <linux/device.h>
|
|
|
|
|
|
static struct class *bdi_class;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
static struct dentry *bdi_debug_root;
|
|
|
|
static void bdi_debug_init(void)
|
|
{
|
|
bdi_debug_root = debugfs_create_dir("bdi", NULL);
|
|
}
|
|
|
|
static int bdi_debug_stats_show(struct seq_file *m, void *v)
|
|
{
|
|
struct backing_dev_info *bdi = m->private;
|
|
long background_thresh;
|
|
long dirty_thresh;
|
|
long bdi_thresh;
|
|
|
|
get_dirty_limits(&background_thresh, &dirty_thresh, &bdi_thresh, bdi);
|
|
|
|
#define K(x) ((x) << (PAGE_SHIFT - 10))
|
|
seq_printf(m,
|
|
"BdiWriteback: %8lu kB\n"
|
|
"BdiReclaimable: %8lu kB\n"
|
|
"BdiDirtyThresh: %8lu kB\n"
|
|
"DirtyThresh: %8lu kB\n"
|
|
"BackgroundThresh: %8lu kB\n",
|
|
(unsigned long) K(bdi_stat(bdi, BDI_WRITEBACK)),
|
|
(unsigned long) K(bdi_stat(bdi, BDI_RECLAIMABLE)),
|
|
K(bdi_thresh),
|
|
K(dirty_thresh),
|
|
K(background_thresh));
|
|
#undef K
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdi_debug_stats_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, bdi_debug_stats_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations bdi_debug_stats_fops = {
|
|
.open = bdi_debug_stats_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void bdi_debug_register(struct backing_dev_info *bdi, const char *name)
|
|
{
|
|
bdi->debug_dir = debugfs_create_dir(name, bdi_debug_root);
|
|
bdi->debug_stats = debugfs_create_file("stats", 0444, bdi->debug_dir,
|
|
bdi, &bdi_debug_stats_fops);
|
|
}
|
|
|
|
static void bdi_debug_unregister(struct backing_dev_info *bdi)
|
|
{
|
|
debugfs_remove(bdi->debug_stats);
|
|
debugfs_remove(bdi->debug_dir);
|
|
}
|
|
#else
|
|
static inline void bdi_debug_init(void)
|
|
{
|
|
}
|
|
static inline void bdi_debug_register(struct backing_dev_info *bdi,
|
|
const char *name)
|
|
{
|
|
}
|
|
static inline void bdi_debug_unregister(struct backing_dev_info *bdi)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static ssize_t read_ahead_kb_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct backing_dev_info *bdi = dev_get_drvdata(dev);
|
|
char *end;
|
|
unsigned long read_ahead_kb;
|
|
ssize_t ret = -EINVAL;
|
|
|
|
read_ahead_kb = simple_strtoul(buf, &end, 10);
|
|
if (*buf && (end[0] == '\0' || (end[0] == '\n' && end[1] == '\0'))) {
|
|
bdi->ra_pages = read_ahead_kb >> (PAGE_SHIFT - 10);
|
|
ret = count;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define K(pages) ((pages) << (PAGE_SHIFT - 10))
|
|
|
|
#define BDI_SHOW(name, expr) \
|
|
static ssize_t name##_show(struct device *dev, \
|
|
struct device_attribute *attr, char *page) \
|
|
{ \
|
|
struct backing_dev_info *bdi = dev_get_drvdata(dev); \
|
|
\
|
|
return snprintf(page, PAGE_SIZE-1, "%lld\n", (long long)expr); \
|
|
}
|
|
|
|
BDI_SHOW(read_ahead_kb, K(bdi->ra_pages))
|
|
|
|
static ssize_t min_ratio_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct backing_dev_info *bdi = dev_get_drvdata(dev);
|
|
char *end;
|
|
unsigned int ratio;
|
|
ssize_t ret = -EINVAL;
|
|
|
|
ratio = simple_strtoul(buf, &end, 10);
|
|
if (*buf && (end[0] == '\0' || (end[0] == '\n' && end[1] == '\0'))) {
|
|
ret = bdi_set_min_ratio(bdi, ratio);
|
|
if (!ret)
|
|
ret = count;
|
|
}
|
|
return ret;
|
|
}
|
|
BDI_SHOW(min_ratio, bdi->min_ratio)
|
|
|
|
static ssize_t max_ratio_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct backing_dev_info *bdi = dev_get_drvdata(dev);
|
|
char *end;
|
|
unsigned int ratio;
|
|
ssize_t ret = -EINVAL;
|
|
|
|
ratio = simple_strtoul(buf, &end, 10);
|
|
if (*buf && (end[0] == '\0' || (end[0] == '\n' && end[1] == '\0'))) {
|
|
ret = bdi_set_max_ratio(bdi, ratio);
|
|
if (!ret)
|
|
ret = count;
|
|
}
|
|
return ret;
|
|
}
|
|
BDI_SHOW(max_ratio, bdi->max_ratio)
|
|
|
|
#define __ATTR_RW(attr) __ATTR(attr, 0644, attr##_show, attr##_store)
|
|
|
|
static struct device_attribute bdi_dev_attrs[] = {
|
|
__ATTR_RW(read_ahead_kb),
|
|
__ATTR_RW(min_ratio),
|
|
__ATTR_RW(max_ratio),
|
|
__ATTR_NULL,
|
|
};
|
|
|
|
static __init int bdi_class_init(void)
|
|
{
|
|
bdi_class = class_create(THIS_MODULE, "bdi");
|
|
bdi_class->dev_attrs = bdi_dev_attrs;
|
|
bdi_debug_init();
|
|
return 0;
|
|
}
|
|
|
|
postcore_initcall(bdi_class_init);
|
|
|
|
int bdi_register(struct backing_dev_info *bdi, struct device *parent,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int ret = 0;
|
|
struct device *dev;
|
|
|
|
if (WARN_ON(bdi->dev))
|
|
goto exit;
|
|
|
|
va_start(args, fmt);
|
|
dev = device_create_vargs(bdi_class, parent, MKDEV(0, 0), bdi, fmt, args);
|
|
va_end(args);
|
|
if (IS_ERR(dev)) {
|
|
ret = PTR_ERR(dev);
|
|
goto exit;
|
|
}
|
|
|
|
bdi->dev = dev;
|
|
bdi_debug_register(bdi, dev_name(dev));
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(bdi_register);
|
|
|
|
int bdi_register_dev(struct backing_dev_info *bdi, dev_t dev)
|
|
{
|
|
return bdi_register(bdi, NULL, "%u:%u", MAJOR(dev), MINOR(dev));
|
|
}
|
|
EXPORT_SYMBOL(bdi_register_dev);
|
|
|
|
void bdi_unregister(struct backing_dev_info *bdi)
|
|
{
|
|
if (bdi->dev) {
|
|
bdi_debug_unregister(bdi);
|
|
device_unregister(bdi->dev);
|
|
bdi->dev = NULL;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(bdi_unregister);
|
|
|
|
int bdi_init(struct backing_dev_info *bdi)
|
|
{
|
|
int i;
|
|
int err;
|
|
|
|
bdi->dev = NULL;
|
|
|
|
bdi->min_ratio = 0;
|
|
bdi->max_ratio = 100;
|
|
bdi->max_prop_frac = PROP_FRAC_BASE;
|
|
|
|
for (i = 0; i < NR_BDI_STAT_ITEMS; i++) {
|
|
err = percpu_counter_init_irq(&bdi->bdi_stat[i], 0);
|
|
if (err)
|
|
goto err;
|
|
}
|
|
|
|
bdi->dirty_exceeded = 0;
|
|
err = prop_local_init_percpu(&bdi->completions);
|
|
|
|
if (err) {
|
|
err:
|
|
while (i--)
|
|
percpu_counter_destroy(&bdi->bdi_stat[i]);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(bdi_init);
|
|
|
|
void bdi_destroy(struct backing_dev_info *bdi)
|
|
{
|
|
int i;
|
|
|
|
bdi_unregister(bdi);
|
|
|
|
for (i = 0; i < NR_BDI_STAT_ITEMS; i++)
|
|
percpu_counter_destroy(&bdi->bdi_stat[i]);
|
|
|
|
prop_local_destroy_percpu(&bdi->completions);
|
|
}
|
|
EXPORT_SYMBOL(bdi_destroy);
|
|
|
|
static wait_queue_head_t congestion_wqh[2] = {
|
|
__WAIT_QUEUE_HEAD_INITIALIZER(congestion_wqh[0]),
|
|
__WAIT_QUEUE_HEAD_INITIALIZER(congestion_wqh[1])
|
|
};
|
|
|
|
|
|
void clear_bdi_congested(struct backing_dev_info *bdi, int rw)
|
|
{
|
|
enum bdi_state bit;
|
|
wait_queue_head_t *wqh = &congestion_wqh[rw];
|
|
|
|
bit = (rw == WRITE) ? BDI_write_congested : BDI_read_congested;
|
|
clear_bit(bit, &bdi->state);
|
|
smp_mb__after_clear_bit();
|
|
if (waitqueue_active(wqh))
|
|
wake_up(wqh);
|
|
}
|
|
EXPORT_SYMBOL(clear_bdi_congested);
|
|
|
|
void set_bdi_congested(struct backing_dev_info *bdi, int rw)
|
|
{
|
|
enum bdi_state bit;
|
|
|
|
bit = (rw == WRITE) ? BDI_write_congested : BDI_read_congested;
|
|
set_bit(bit, &bdi->state);
|
|
}
|
|
EXPORT_SYMBOL(set_bdi_congested);
|
|
|
|
/**
|
|
* congestion_wait - wait for a backing_dev to become uncongested
|
|
* @rw: READ or WRITE
|
|
* @timeout: timeout in jiffies
|
|
*
|
|
* Waits for up to @timeout jiffies for a backing_dev (any backing_dev) to exit
|
|
* write congestion. If no backing_devs are congested then just wait for the
|
|
* next write to be completed.
|
|
*/
|
|
long congestion_wait(int rw, long timeout)
|
|
{
|
|
long ret;
|
|
DEFINE_WAIT(wait);
|
|
wait_queue_head_t *wqh = &congestion_wqh[rw];
|
|
|
|
prepare_to_wait(wqh, &wait, TASK_UNINTERRUPTIBLE);
|
|
ret = io_schedule_timeout(timeout);
|
|
finish_wait(wqh, &wait);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(congestion_wait);
|
|
|