Commit 32694850 authored by Thomas Maier's avatar Thomas Maier Committed by Linus Torvalds

[PATCH] pktcdvd: add sysfs and debugfs interface

Add a sysfs and debugfs interface to the pktcdvd driver.

Look into the Documentation/ABI/testing/* files in the patch for more info.
Signed-off-by: default avatarThomas Maier <balagi@justmail.de>
Signed-off-by: default avatarPeter Osterlund <petero2@telia.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 0a0fc960
What: /debug/pktcdvd/pktcdvd[0-7]
Date: Oct. 2006
KernelVersion: 2.6.19
Contact: Thomas Maier <balagi@justmail.de>
Description:
debugfs interface
-----------------
The pktcdvd module (packet writing driver) creates
these files in debugfs:
/debug/pktcdvd/pktcdvd[0-7]/
info (0444) Lots of human readable driver
statistics and infos. Multiple lines!
Example:
-------
cat /debug/pktcdvd/pktcdvd0/info
What: /sys/class/pktcdvd/
Date: Oct. 2006
KernelVersion: 2.6.19
Contact: Thomas Maier <balagi@justmail.de>
Description:
sysfs interface
---------------
The pktcdvd module (packet writing driver) creates
these files in the sysfs:
(<devid> is in format major:minor )
/sys/class/pktcdvd/
add (0200) Write a block device id (major:minor)
to create a new pktcdvd device and map
it to the block device.
remove (0200) Write the pktcdvd device id (major:minor)
to it to remove the pktcdvd device.
device_map (0444) Shows the device mapping in format:
pktcdvd[0-7] <pktdevid> <blkdevid>
/sys/class/pktcdvd/pktcdvd[0-7]/
dev (0444) Device id
uevent (0200) To send an uevent.
/sys/class/pktcdvd/pktcdvd[0-7]/stat/
packets_started (0444) Number of started packets.
packets_finished (0444) Number of finished packets.
kb_written (0444) kBytes written.
kb_read (0444) kBytes read.
kb_read_gather (0444) kBytes read to fill write packets.
reset (0200) Write any value to it to reset
pktcdvd device statistic values, like
bytes read/written.
/sys/class/pktcdvd/pktcdvd[0-7]/write_queue/
size (0444) Contains the size of the bio write
queue.
congestion_off (0644) If bio write queue size is below
this mark, accept new bio requests
from the block layer.
congestion_on (0644) If bio write queue size is higher
as this mark, do no longer accept
bio write requests from the block
layer and wait till the pktcdvd
device has processed enough bio's
so that bio write queue size is
below congestion off mark.
A value of <= 0 disables congestion
control.
Example:
--------
To use the pktcdvd sysfs interface directly, you can do:
# create a new pktcdvd device mapped to /dev/hdc
echo "22:0" >/sys/class/pktcdvd/add
cat /sys/class/pktcdvd/device_map
# assuming device pktcdvd0 was created, look at stat's
cat /sys/class/pktcdvd/pktcdvd0/stat/kb_written
# print the device id of the mapped block device
fgrep pktcdvd0 /sys/class/pktcdvd/device_map
# remove device, using pktcdvd0 device id 253:0
echo "253:0" >/sys/class/pktcdvd/remove
...@@ -90,6 +90,41 @@ Notes ...@@ -90,6 +90,41 @@ Notes
to create an ext2 filesystem on the disc. to create an ext2 filesystem on the disc.
Using the pktcdvd sysfs interface
---------------------------------
Since Linux 2.6.19, the pktcdvd module has a sysfs interface
and can be controlled by it. For example the "pktcdvd" tool uses
this interface. (see http://people.freenet.de/BalaGi#pktcdvd )
"pktcdvd" works similar to "pktsetup", e.g.:
# pktcdvd -a dev_name /dev/hdc
# mkudffs /dev/pktcdvd/dev_name
# mount -t udf -o rw,noatime /dev/pktcdvd/dev_name /dvdram
# cp files /dvdram
# umount /dvdram
# pktcdvd -r dev_name
For a description of the sysfs interface look into the file:
Documentation/ABI/testing/sysfs-block-pktcdvd
Using the pktcdvd debugfs interface
-----------------------------------
To read pktcdvd device infos in human readable form, do:
# cat /debug/pktcdvd/pktcdvd[0-7]/info
For a description of the debugfs interface look into the file:
Documentation/ABI/testing/debugfs-pktcdvd
Links Links
----- -----
......
...@@ -60,6 +60,8 @@ ...@@ -60,6 +60,8 @@
#include <scsi/scsi_cmnd.h> #include <scsi/scsi_cmnd.h>
#include <scsi/scsi_ioctl.h> #include <scsi/scsi_ioctl.h>
#include <scsi/scsi.h> #include <scsi/scsi.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
...@@ -89,6 +91,419 @@ static int write_congestion_off = PKT_WRITE_CONGESTION_OFF; ...@@ -89,6 +91,419 @@ static int write_congestion_off = PKT_WRITE_CONGESTION_OFF;
static struct mutex ctl_mutex; /* Serialize open/close/setup/teardown */ static struct mutex ctl_mutex; /* Serialize open/close/setup/teardown */
static mempool_t *psd_pool; static mempool_t *psd_pool;
static struct class *class_pktcdvd = NULL; /* /sys/class/pktcdvd */
static struct dentry *pkt_debugfs_root = NULL; /* /debug/pktcdvd */
/* forward declaration */
static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev);
static int pkt_remove_dev(dev_t pkt_dev);
static int pkt_seq_show(struct seq_file *m, void *p);
/*
* create and register a pktcdvd kernel object.
*/
static struct pktcdvd_kobj* pkt_kobj_create(struct pktcdvd_device *pd,
const char* name,
struct kobject* parent,
struct kobj_type* ktype)
{
struct pktcdvd_kobj *p;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return NULL;
kobject_set_name(&p->kobj, "%s", name);
p->kobj.parent = parent;
p->kobj.ktype = ktype;
p->pd = pd;
if (kobject_register(&p->kobj) != 0)
return NULL;
return p;
}
/*
* remove a pktcdvd kernel object.
*/
static void pkt_kobj_remove(struct pktcdvd_kobj *p)
{
if (p)
kobject_unregister(&p->kobj);
}
/*
* default release function for pktcdvd kernel objects.
*/
static void pkt_kobj_release(struct kobject *kobj)
{
kfree(to_pktcdvdkobj(kobj));
}
/**********************************************************
*
* sysfs interface for pktcdvd
* by (C) 2006 Thomas Maier <balagi@justmail.de>
*
**********************************************************/
#define DEF_ATTR(_obj,_name,_mode) \
static struct attribute _obj = { \
.name = _name, .owner = THIS_MODULE, .mode = _mode }
/**********************************************************
/sys/class/pktcdvd/pktcdvd[0-7]/
stat/reset
stat/packets_started
stat/packets_finished
stat/kb_written
stat/kb_read
stat/kb_read_gather
write_queue/size
write_queue/congestion_off
write_queue/congestion_on
**********************************************************/
DEF_ATTR(kobj_pkt_attr_st1, "reset", 0200);
DEF_ATTR(kobj_pkt_attr_st2, "packets_started", 0444);
DEF_ATTR(kobj_pkt_attr_st3, "packets_finished", 0444);
DEF_ATTR(kobj_pkt_attr_st4, "kb_written", 0444);
DEF_ATTR(kobj_pkt_attr_st5, "kb_read", 0444);
DEF_ATTR(kobj_pkt_attr_st6, "kb_read_gather", 0444);
static struct attribute *kobj_pkt_attrs_stat[] = {
&kobj_pkt_attr_st1,
&kobj_pkt_attr_st2,
&kobj_pkt_attr_st3,
&kobj_pkt_attr_st4,
&kobj_pkt_attr_st5,
&kobj_pkt_attr_st6,
NULL
};
DEF_ATTR(kobj_pkt_attr_wq1, "size", 0444);
DEF_ATTR(kobj_pkt_attr_wq2, "congestion_off", 0644);
DEF_ATTR(kobj_pkt_attr_wq3, "congestion_on", 0644);
static struct attribute *kobj_pkt_attrs_wqueue[] = {
&kobj_pkt_attr_wq1,
&kobj_pkt_attr_wq2,
&kobj_pkt_attr_wq3,
NULL
};
/* declares a char buffer[64] _dbuf, copies data from
* _b with length _l into it and ensures that _dbuf ends
* with a \0 character.
*/
#define DECLARE_BUF_AS_STRING(_dbuf, _b, _l) \
char _dbuf[64]; int dlen = (_l) < 0 ? 0 : (_l); \
if (dlen >= sizeof(_dbuf)) dlen = sizeof(_dbuf)-1; \
memcpy(_dbuf, _b, dlen); _dbuf[dlen] = 0
static ssize_t kobj_pkt_show(struct kobject *kobj,
struct attribute *attr, char *data)
{
struct pktcdvd_device *pd = to_pktcdvdkobj(kobj)->pd;
int n = 0;
int v;
if (strcmp(attr->name, "packets_started") == 0) {
n = sprintf(data, "%lu\n", pd->stats.pkt_started);
} else if (strcmp(attr->name, "packets_finished") == 0) {
n = sprintf(data, "%lu\n", pd->stats.pkt_ended);
} else if (strcmp(attr->name, "kb_written") == 0) {
n = sprintf(data, "%lu\n", pd->stats.secs_w >> 1);
} else if (strcmp(attr->name, "kb_read") == 0) {
n = sprintf(data, "%lu\n", pd->stats.secs_r >> 1);
} else if (strcmp(attr->name, "kb_read_gather") == 0) {
n = sprintf(data, "%lu\n", pd->stats.secs_rg >> 1);
} else if (strcmp(attr->name, "size") == 0) {
spin_lock(&pd->lock);
v = pd->bio_queue_size;
spin_unlock(&pd->lock);
n = sprintf(data, "%d\n", v);
} else if (strcmp(attr->name, "congestion_off") == 0) {
spin_lock(&pd->lock);
v = pd->write_congestion_off;
spin_unlock(&pd->lock);
n = sprintf(data, "%d\n", v);
} else if (strcmp(attr->name, "congestion_on") == 0) {
spin_lock(&pd->lock);
v = pd->write_congestion_on;
spin_unlock(&pd->lock);
n = sprintf(data, "%d\n", v);
}
return n;
}
static void init_write_congestion_marks(int* lo, int* hi)
{
if (*hi > 0) {
*hi = max(*hi, 500);
*hi = min(*hi, 1000000);
if (*lo <= 0)
*lo = *hi - 100;
else {
*lo = min(*lo, *hi - 100);
*lo = max(*lo, 100);
}
} else {
*hi = -1;
*lo = -1;
}
}
static ssize_t kobj_pkt_store(struct kobject *kobj,
struct attribute *attr,
const char *data, size_t len)
{
struct pktcdvd_device *pd = to_pktcdvdkobj(kobj)->pd;
int val;
DECLARE_BUF_AS_STRING(dbuf, data, len); /* ensure sscanf scans a string */
if (strcmp(attr->name, "reset") == 0 && dlen > 0) {
pd->stats.pkt_started = 0;
pd->stats.pkt_ended = 0;
pd->stats.secs_w = 0;
pd->stats.secs_rg = 0;
pd->stats.secs_r = 0;
} else if (strcmp(attr->name, "congestion_off") == 0
&& sscanf(dbuf, "%d", &val) == 1) {
spin_lock(&pd->lock);
pd->write_congestion_off = val;
init_write_congestion_marks(&pd->write_congestion_off,
&pd->write_congestion_on);
spin_unlock(&pd->lock);
} else if (strcmp(attr->name, "congestion_on") == 0
&& sscanf(dbuf, "%d", &val) == 1) {
spin_lock(&pd->lock);
pd->write_congestion_on = val;
init_write_congestion_marks(&pd->write_congestion_off,
&pd->write_congestion_on);
spin_unlock(&pd->lock);
}
return len;
}
static struct sysfs_ops kobj_pkt_ops = {
.show = kobj_pkt_show,
.store = kobj_pkt_store
};
static struct kobj_type kobj_pkt_type_stat = {
.release = pkt_kobj_release,
.sysfs_ops = &kobj_pkt_ops,
.default_attrs = kobj_pkt_attrs_stat
};
static struct kobj_type kobj_pkt_type_wqueue = {
.release = pkt_kobj_release,
.sysfs_ops = &kobj_pkt_ops,
.default_attrs = kobj_pkt_attrs_wqueue
};
static void pkt_sysfs_dev_new(struct pktcdvd_device *pd)
{
if (class_pktcdvd) {
pd->clsdev = class_device_create(class_pktcdvd,
NULL, pd->pkt_dev,
NULL, "%s", pd->name);
if (IS_ERR(pd->clsdev))
pd->clsdev = NULL;
}
if (pd->clsdev) {
pd->kobj_stat = pkt_kobj_create(pd, "stat",
&pd->clsdev->kobj,
&kobj_pkt_type_stat);
pd->kobj_wqueue = pkt_kobj_create(pd, "write_queue",
&pd->clsdev->kobj,
&kobj_pkt_type_wqueue);
}
}
static void pkt_sysfs_dev_remove(struct pktcdvd_device *pd)
{
pkt_kobj_remove(pd->kobj_stat);
pkt_kobj_remove(pd->kobj_wqueue);
if (class_pktcdvd)
class_device_destroy(class_pktcdvd, pd->pkt_dev);
}
/********************************************************************
/sys/class/pktcdvd/
add map block device
remove unmap packet dev
device_map show mappings
*******************************************************************/
static void class_pktcdvd_release(struct class *cls)
{
kfree(cls);
}
static ssize_t class_pktcdvd_show_map(struct class *c, char *data)
{
int n = 0;
int idx;
mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
for (idx = 0; idx < MAX_WRITERS; idx++) {
struct pktcdvd_device *pd = pkt_devs[idx];
if (!pd)
continue;
n += sprintf(data+n, "%s %u:%u %u:%u\n",
pd->name,
MAJOR(pd->pkt_dev), MINOR(pd->pkt_dev),
MAJOR(pd->bdev->bd_dev),
MINOR(pd->bdev->bd_dev));
}
mutex_unlock(&ctl_mutex);
return n;
}
static ssize_t class_pktcdvd_store_add(struct class *c, const char *buf,
size_t count)
{
unsigned int major, minor;
DECLARE_BUF_AS_STRING(dbuf, buf, count);
if (sscanf(dbuf, "%u:%u", &major, &minor) == 2) {
pkt_setup_dev(MKDEV(major, minor), NULL);
return count;
}
return -EINVAL;
}
static ssize_t class_pktcdvd_store_remove(struct class *c, const char *buf,
size_t count)
{
unsigned int major, minor;
DECLARE_BUF_AS_STRING(dbuf, buf, count);
if (sscanf(dbuf, "%u:%u", &major, &minor) == 2) {
pkt_remove_dev(MKDEV(major, minor));
return count;
}
return -EINVAL;
}
static struct class_attribute class_pktcdvd_attrs[] = {
__ATTR(add, 0200, NULL, class_pktcdvd_store_add),
__ATTR(remove, 0200, NULL, class_pktcdvd_store_remove),
__ATTR(device_map, 0444, class_pktcdvd_show_map, NULL),
__ATTR_NULL
};
static int pkt_sysfs_init(void)
{
int ret = 0;
/*
* create control files in sysfs
* /sys/class/pktcdvd/...
*/
class_pktcdvd = kzalloc(sizeof(*class_pktcdvd), GFP_KERNEL);
if (!class_pktcdvd)
return -ENOMEM;
class_pktcdvd->name = DRIVER_NAME;
class_pktcdvd->owner = THIS_MODULE;
class_pktcdvd->class_release = class_pktcdvd_release;
class_pktcdvd->class_attrs = class_pktcdvd_attrs;
ret = class_register(class_pktcdvd);
if (ret) {
kfree(class_pktcdvd);
class_pktcdvd = NULL;
printk(DRIVER_NAME": failed to create class pktcdvd\n");
return ret;
}
return 0;
}
static void pkt_sysfs_cleanup(void)
{
if (class_pktcdvd)
class_destroy(class_pktcdvd);
class_pktcdvd = NULL;
}
/********************************************************************
entries in debugfs
/debugfs/pktcdvd[0-7]/
info
*******************************************************************/
static int pkt_debugfs_seq_show(struct seq_file *m, void *p)
{
return pkt_seq_show(m, p);
}
static int pkt_debugfs_fops_open(struct inode *inode, struct file *file)
{
return single_open(file, pkt_debugfs_seq_show, inode->i_private);
}
static struct file_operations debug_fops = {
.open = pkt_debugfs_fops_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
static void pkt_debugfs_dev_new(struct pktcdvd_device *pd)
{
if (!pkt_debugfs_root)
return;
pd->dfs_f_info = NULL;
pd->dfs_d_root = debugfs_create_dir(pd->name, pkt_debugfs_root);
if (IS_ERR(pd->dfs_d_root)) {
pd->dfs_d_root = NULL;
return;
}
pd->dfs_f_info = debugfs_create_file("info", S_IRUGO,
pd->dfs_d_root, pd, &debug_fops);
if (IS_ERR(pd->dfs_f_info)) {
pd->dfs_f_info = NULL;
return;
}
}
static void pkt_debugfs_dev_remove(struct pktcdvd_device *pd)
{
if (!pkt_debugfs_root)
return;
if (pd->dfs_f_info)
debugfs_remove(pd->dfs_f_info);
pd->dfs_f_info = NULL;
if (pd->dfs_d_root)
debugfs_remove(pd->dfs_d_root);
pd->dfs_d_root = NULL;
}
static void pkt_debugfs_init(void)
{
pkt_debugfs_root = debugfs_create_dir(DRIVER_NAME, NULL);
if (IS_ERR(pkt_debugfs_root)) {
pkt_debugfs_root = NULL;
return;
}
}
static void pkt_debugfs_cleanup(void)
{
if (!pkt_debugfs_root)
return;
debugfs_remove(pkt_debugfs_root);
pkt_debugfs_root = NULL;
}
/* ----------------------------------------------------------*/
static void pkt_bio_finished(struct pktcdvd_device *pd) static void pkt_bio_finished(struct pktcdvd_device *pd)
{ {
...@@ -2527,6 +2942,9 @@ static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev) ...@@ -2527,6 +2942,9 @@ static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev)
add_disk(disk); add_disk(disk);
pkt_sysfs_dev_new(pd);
pkt_debugfs_dev_new(pd);
pkt_devs[idx] = pd; pkt_devs[idx] = pd;
if (pkt_dev) if (pkt_dev)
*pkt_dev = pd->pkt_dev; *pkt_dev = pd->pkt_dev;
...@@ -2577,6 +2995,11 @@ static int pkt_remove_dev(dev_t pkt_dev) ...@@ -2577,6 +2995,11 @@ static int pkt_remove_dev(dev_t pkt_dev)
if (!IS_ERR(pd->cdrw.thread)) if (!IS_ERR(pd->cdrw.thread))
kthread_stop(pd->cdrw.thread); kthread_stop(pd->cdrw.thread);
pkt_devs[idx] = NULL;
pkt_debugfs_dev_remove(pd);
pkt_sysfs_dev_remove(pd);
blkdev_put(pd->bdev); blkdev_put(pd->bdev);
remove_proc_entry(pd->name, pkt_proc); remove_proc_entry(pd->name, pkt_proc);
...@@ -2586,7 +3009,6 @@ static int pkt_remove_dev(dev_t pkt_dev) ...@@ -2586,7 +3009,6 @@ static int pkt_remove_dev(dev_t pkt_dev)
blk_cleanup_queue(pd->disk->queue); blk_cleanup_queue(pd->disk->queue);
put_disk(pd->disk); put_disk(pd->disk);
pkt_devs[idx] = NULL;
mempool_destroy(pd->rb_pool); mempool_destroy(pd->rb_pool);
kfree(pd); kfree(pd);
...@@ -2670,6 +3092,8 @@ static int __init pkt_init(void) ...@@ -2670,6 +3092,8 @@ static int __init pkt_init(void)
{ {
int ret; int ret;
mutex_init(&ctl_mutex);
psd_pool = mempool_create_kmalloc_pool(PSD_POOL_SIZE, psd_pool = mempool_create_kmalloc_pool(PSD_POOL_SIZE,
sizeof(struct packet_stacked_data)); sizeof(struct packet_stacked_data));
if (!psd_pool) if (!psd_pool)
...@@ -2683,18 +3107,25 @@ static int __init pkt_init(void) ...@@ -2683,18 +3107,25 @@ static int __init pkt_init(void)
if (!pktdev_major) if (!pktdev_major)
pktdev_major = ret; pktdev_major = ret;
ret = pkt_sysfs_init();
if (ret)
goto out;
pkt_debugfs_init();
ret = misc_register(&pkt_misc); ret = misc_register(&pkt_misc);
if (ret) { if (ret) {
printk(DRIVER_NAME": Unable to register misc device\n"); printk(DRIVER_NAME": Unable to register misc device\n");
goto out; goto out_misc;
} }
mutex_init(&ctl_mutex);
pkt_proc = proc_mkdir(DRIVER_NAME, proc_root_driver); pkt_proc = proc_mkdir(DRIVER_NAME, proc_root_driver);
return 0; return 0;
out_misc:
pkt_debugfs_cleanup();
pkt_sysfs_cleanup();
out: out:
unregister_blkdev(pktdev_major, DRIVER_NAME); unregister_blkdev(pktdev_major, DRIVER_NAME);
out2: out2:
...@@ -2706,6 +3137,10 @@ static void __exit pkt_exit(void) ...@@ -2706,6 +3137,10 @@ static void __exit pkt_exit(void)
{ {
remove_proc_entry(DRIVER_NAME, proc_root_driver); remove_proc_entry(DRIVER_NAME, proc_root_driver);
misc_deregister(&pkt_misc); misc_deregister(&pkt_misc);
pkt_debugfs_cleanup();
pkt_sysfs_cleanup();
unregister_blkdev(pktdev_major, DRIVER_NAME); unregister_blkdev(pktdev_major, DRIVER_NAME);
mempool_destroy(psd_pool); mempool_destroy(psd_pool);
} }
......
...@@ -111,7 +111,8 @@ struct pkt_ctrl_command { ...@@ -111,7 +111,8 @@ struct pkt_ctrl_command {
#include <linux/blkdev.h> #include <linux/blkdev.h>
#include <linux/completion.h> #include <linux/completion.h>
#include <linux/cdrom.h> #include <linux/cdrom.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
/* default bio write queue congestion marks */ /* default bio write queue congestion marks */
#define PKT_WRITE_CONGESTION_ON 10000 #define PKT_WRITE_CONGESTION_ON 10000
...@@ -247,6 +248,14 @@ struct packet_stacked_data ...@@ -247,6 +248,14 @@ struct packet_stacked_data
}; };
#define PSD_POOL_SIZE 64 #define PSD_POOL_SIZE 64
struct pktcdvd_kobj
{
struct kobject kobj;
struct pktcdvd_device *pd;
};
#define to_pktcdvdkobj(_k) \
((struct pktcdvd_kobj*)container_of(_k,struct pktcdvd_kobj,kobj))
struct pktcdvd_device struct pktcdvd_device
{ {
struct block_device *bdev; /* dev attached */ struct block_device *bdev; /* dev attached */
...@@ -280,6 +289,13 @@ struct pktcdvd_device ...@@ -280,6 +289,13 @@ struct pktcdvd_device
int write_congestion_off; int write_congestion_off;
int write_congestion_on; int write_congestion_on;
struct class_device *clsdev; /* sysfs pktcdvd[0-7] class dev */
struct pktcdvd_kobj *kobj_stat; /* sysfs pktcdvd[0-7]/stat/ */
struct pktcdvd_kobj *kobj_wqueue; /* sysfs pktcdvd[0-7]/write_queue/ */
struct dentry *dfs_d_root; /* debugfs: devname directory */
struct dentry *dfs_f_info; /* debugfs: info file */
}; };
#endif /* __KERNEL__ */ #endif /* __KERNEL__ */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment