Commit 2eca25e4 authored by Douglas Gilbert's avatar Douglas Gilbert Committed by James Bottomley

scsi_debug 1.64 , remove detect(), "hotplug" hosts

This patch is against lk 2.5.46-bk3 which includes
Christoph's work removing the requirement for
detect() functions in LLDDs. He sent me an example
for scsi_debug which I have built on with this patch.

As threatened, this version includes a "scsi_debug_add_host"
parameter. At kernel or module load time this is an absolute
number (0..127 are allowable and 1 is the default). So:
     modprobe scsi_debug scsi_debug_num_devs=20 scsi_debug_add_host=0
will result in no scsi_debug hosts (thus no devices) but
the driver has 20 slots available for devices.

Then a host can be introduced (simulated hotplug) by
     echo 1 > /sysfs/bus/scsi/drivers/scsi_debug/add_host
This causes a scsi_debug host to appear and devices get
found on it [14 in my system: 7 targets (0..6) each with
2 luns (0..1)]. Another host hotplug can be simulated by
     echo 1 > /sysfs/bus/scsi/drivers/scsi_debug/add_host
which results in another 6 devices being attached (for a
total of 20 scsi_debug devices as dictated by the original
scsi_debug_num_devs).

That last (second) scsi_debug host can be removed by
     echo -1 > /sysfs/bus/scsi/drivers/scsi_debug/add_host
and its 6 devices go. Another application of this "echo"
removes the first host and all remaining devices.

Seems to work fine.
parent 55b283a5
/* /*
* linux/kernel/scsi_debug.c * linux/kernel/scsi_debug.c
* * vvvvvvvvvvvvvvvvvvvvvvv Original vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
* Copyright (C) 1992 Eric Youngdale * Copyright (C) 1992 Eric Youngdale
* Simulate a host adapter with 2 disks attached. Do a lot of checking * Simulate a host adapter with 2 disks attached. Do a lot of checking
* to make sure that we are not getting blocks mixed up, and PANIC if * to make sure that we are not getting blocks mixed up, and PANIC if
* anything out of the ordinary is seen. * anything out of the ordinary is seen.
* ^^^^^^^^^^^^^^^^^^^^^^^ Original ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* *
* This version is more generic, simulating a variable number of disk * This version is more generic, simulating a variable number of disk
* (or disk like devices) sharing a common amount of RAM (default 8 MB * (or disk like devices) sharing a common amount of RAM
* but can be set at driver/module load time). *
* *
* For documentation see http://www.torque.net/sg/sdebug.html * For documentation see http://www.torque.net/sg/sdebug.html
* *
...@@ -36,8 +37,6 @@ ...@@ -36,8 +37,6 @@
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <asm/io.h>
#include <linux/blk.h> #include <linux/blk.h>
#include "scsi.h" #include "scsi.h"
#include "hosts.h" #include "hosts.h"
...@@ -50,9 +49,7 @@ ...@@ -50,9 +49,7 @@
#include "scsi_debug.h" #include "scsi_debug.h"
static const char * scsi_debug_version_str = "Version: 1.63 (20021103)"; static const char * scsi_debug_version_str = "Version: 1.64 (20021109)";
#define DRIVERFS_SUPPORT 1 /* comment out whole line to disable */
#ifndef SCSI_CMD_READ_16 #ifndef SCSI_CMD_READ_16
...@@ -72,6 +69,8 @@ static const char * scsi_debug_version_str = "Version: 1.63 (20021103)"; ...@@ -72,6 +69,8 @@ static const char * scsi_debug_version_str = "Version: 1.63 (20021103)";
#define DEF_DELAY 1 #define DEF_DELAY 1
#define DEF_MAX_LUNS 2 #define DEF_MAX_LUNS 2
#define DEF_SCSI_LEVEL 3 #define DEF_SCSI_LEVEL 3
#define DEF_ADD_HOST 1
#define MAX_NUM_HOSTS 128
#define DEF_OPTS 0 #define DEF_OPTS 0
#define SCSI_DEBUG_OPT_NOISE 1 #define SCSI_DEBUG_OPT_NOISE 1
...@@ -87,8 +86,9 @@ static int scsi_debug_cmnd_count = 0; ...@@ -87,8 +86,9 @@ static int scsi_debug_cmnd_count = 0;
static int scsi_debug_delay = DEF_DELAY; static int scsi_debug_delay = DEF_DELAY;
static int scsi_debug_max_luns = DEF_MAX_LUNS; static int scsi_debug_max_luns = DEF_MAX_LUNS;
static int scsi_debug_scsi_level = DEF_SCSI_LEVEL; static int scsi_debug_scsi_level = DEF_SCSI_LEVEL;
static int scsi_debug_add_host = DEF_ADD_HOST;
#define NR_HOSTS_PRESENT (((scsi_debug_num_devs - 1) / 7) + 1) /* #define NR_HOSTS_PRESENT (((scsi_debug_num_devs - 1) / 7) + 1) */
/* This assumes one lun used per allocated target id */ /* This assumes one lun used per allocated target id */
#define N_HEAD 8 #define N_HEAD 8
#define N_SECTOR 32 #define N_SECTOR 32
...@@ -109,6 +109,7 @@ static int scsi_debug_dev_size_mb = DEF_DEV_SIZE_MB; ...@@ -109,6 +109,7 @@ static int scsi_debug_dev_size_mb = DEF_DEV_SIZE_MB;
#define CAPACITY (N_HEAD * N_SECTOR * N_CYLINDER) #define CAPACITY (N_HEAD * N_SECTOR * N_CYLINDER)
#define SECT_SIZE_PER(TGT) SECT_SIZE #define SECT_SIZE_PER(TGT) SECT_SIZE
struct Scsi_Host *scsi_debug_hosts[MAX_NUM_HOSTS];
#define SDEBUG_SENSE_LEN 32 #define SDEBUG_SENSE_LEN 32
...@@ -146,9 +147,7 @@ static int num_host_resets = 0; ...@@ -146,9 +147,7 @@ static int num_host_resets = 0;
static spinlock_t queued_arr_lock = SPIN_LOCK_UNLOCKED; static spinlock_t queued_arr_lock = SPIN_LOCK_UNLOCKED;
static rwlock_t atomic_rw = RW_LOCK_UNLOCKED; static rwlock_t atomic_rw = RW_LOCK_UNLOCKED;
#ifdef DRIVERFS_SUPPORT
static struct device_driver sdebug_driverfs_driver; static struct device_driver sdebug_driverfs_driver;
#endif
/* function declarations */ /* function declarations */
static int resp_inquiry(unsigned char * cmd, int target, unsigned char * buff, static int resp_inquiry(unsigned char * cmd, int target, unsigned char * buff,
...@@ -176,10 +175,9 @@ static void stop_all_queued(void); ...@@ -176,10 +175,9 @@ static void stop_all_queued(void);
static int stop_queued_cmnd(struct scsi_cmnd * cmnd); static int stop_queued_cmnd(struct scsi_cmnd * cmnd);
static int inquiry_evpd_83(unsigned char * arr, int dev_id_num, static int inquiry_evpd_83(unsigned char * arr, int dev_id_num,
const char * dev_id_str, int dev_id_str_len); const char * dev_id_str, int dev_id_str_len);
#ifdef DRIVERFS_SUPPORT
static void do_create_driverfs_files(void); static void do_create_driverfs_files(void);
static void do_remove_driverfs_files(void); static void do_remove_driverfs_files(void);
#endif static struct Scsi_Host * sdebug_add_shost(void);
static unsigned char * scatg2virt(const struct scatterlist * sclp) static unsigned char * scatg2virt(const struct scatterlist * sclp)
...@@ -225,7 +223,7 @@ int scsi_debug_queuecommand(struct scsi_cmnd * SCpnt, done_funct_t done) ...@@ -225,7 +223,7 @@ int scsi_debug_queuecommand(struct scsi_cmnd * SCpnt, done_funct_t done)
bufflen = SDEBUG_SENSE_LEN; bufflen = SDEBUG_SENSE_LEN;
} }
if(target == driver_template.this_id) { if(target == sdebug_driver_template.this_id) {
printk(KERN_WARNING printk(KERN_WARNING
"scsi_debug: initiator's id used as target!\n"); "scsi_debug: initiator's id used as target!\n");
return schedule_resp(SCpnt, NULL, done, 0, 0); return schedule_resp(SCpnt, NULL, done, 0, 0);
...@@ -809,92 +807,9 @@ static void timer_intr_handler(unsigned long indx) ...@@ -809,92 +807,9 @@ static void timer_intr_handler(unsigned long indx)
spin_unlock_irqrestore(&queued_arr_lock, iflags); spin_unlock_irqrestore(&queued_arr_lock, iflags);
} }
static int initialized = 0;
static int num_hosts_present = 0; static int num_hosts_present = 0;
static const char * sdebug_proc_name = "scsi_debug"; static const char * sdebug_proc_name = "scsi_debug";
static int scsi_debug_detect(struct SHT * tpnt)
{
int k, sz;
struct Scsi_Host * hpnt;
if (SCSI_DEBUG_OPT_NOISE & scsi_debug_opts)
printk(KERN_INFO "scsi_debug: detect\n");
if (0 == initialized) {
++initialized;
sz = sizeof(struct sdebug_dev_info) * scsi_debug_num_devs;
devInfop = vmalloc(sz);
if (NULL == devInfop) {
printk(KERN_ERR "scsi_debug_detect: out of "
"memory\n");
return 0;
}
memset(devInfop, 0, sz);
sz = STORE_SIZE;
fake_storep = vmalloc(sz);
if (NULL == fake_storep) {
printk(KERN_ERR "scsi_debug_detect: out of memory"
", 1\n");
return 0;
}
memset(fake_storep, 0, sz);
init_all_queued();
#ifdef DRIVERFS_SUPPORT
sdebug_driverfs_driver.name = (char *)sdebug_proc_name;
sdebug_driverfs_driver.bus = &scsi_driverfs_bus_type;
driver_register(&sdebug_driverfs_driver);
do_create_driverfs_files();
#endif
tpnt->proc_name = (char *)sdebug_proc_name;
for (num_hosts_present = 0, k = 0; k < NR_HOSTS_PRESENT; k++) {
if ((hpnt = scsi_register(tpnt, 0)) == NULL)
printk(KERN_ERR "scsi_debug_detect: "
"scsi_register failed k=%d\n", k);
else {
hpnt->max_lun = scsi_debug_max_luns;
++num_hosts_present;
}
}
if (SCSI_DEBUG_OPT_NOISE & scsi_debug_opts)
printk(KERN_INFO "scsi_debug: ... built %d host(s)\n",
num_hosts_present);
return num_hosts_present;
} else {
printk(KERN_WARNING "scsi_debug_detect: called again\n");
return 0;
}
}
static int num_releases = 0;
static int scsi_debug_release(struct Scsi_Host * hpnt)
{
int host_no = hpnt->host_no;
if (++num_releases == num_hosts_present) {
if (SCSI_DEBUG_OPT_NOISE & scsi_debug_opts)
printk(KERN_INFO "scsi_debug: [last] release, "
"host_no=%u\n", host_no);
num_releases = 0;
initialized = 0;
stop_all_queued();
#ifdef DRIVERFS_SUPPORT
do_remove_driverfs_files();
driver_unregister(&sdebug_driverfs_driver);
#endif
vfree(fake_storep);
vfree(devInfop);
}
else {
if (SCSI_DEBUG_OPT_NOISE & scsi_debug_opts)
printk(KERN_INFO "scsi_debug: release, host_no=%u\n",
host_no);
}
scsi_unregister(hpnt);
return 0;
}
static int scsi_debug_slave_attach(struct scsi_device * sdp) static int scsi_debug_slave_attach(struct scsi_device * sdp)
{ {
int k; int k;
...@@ -1290,6 +1205,22 @@ static int __init delay_setup(char *str) ...@@ -1290,6 +1205,22 @@ static int __init delay_setup(char *str)
} }
__setup("scsi_debug_delay=", delay_setup); __setup("scsi_debug_delay=", delay_setup);
static int __init add_host_setup(char *str)
{
int tmp;
if (get_option(&str, &tmp) == 1) {
scsi_debug_add_host = tmp;
return 1;
} else {
printk(KERN_INFO "scsi_debug_add_host: usage "
"scsi_debug_add_host=<n>\n"
" <n> 0..127 (default 1)\n");
return 0;
}
}
__setup("scsi_debug_add_host=", add_host_setup);
#endif #endif
MODULE_AUTHOR("Eric Youngdale + Douglas Gilbert"); MODULE_AUTHOR("Eric Youngdale + Douglas Gilbert");
...@@ -1308,6 +1239,8 @@ MODULE_PARM(scsi_debug_every_nth, "i"); ...@@ -1308,6 +1239,8 @@ MODULE_PARM(scsi_debug_every_nth, "i");
MODULE_PARM_DESC(scsi_debug_every_nth, "timeout every nth command(def=100)"); MODULE_PARM_DESC(scsi_debug_every_nth, "timeout every nth command(def=100)");
MODULE_PARM(scsi_debug_delay, "i"); MODULE_PARM(scsi_debug_delay, "i");
MODULE_PARM_DESC(scsi_debug_delay, "# of jiffies to delay response(def=1)"); MODULE_PARM_DESC(scsi_debug_delay, "# of jiffies to delay response(def=1)");
MODULE_PARM(scsi_debug_add_host, "i");
MODULE_PARM_DESC(scsi_debug_add_host, "0..127 hosts allowed(def=1)");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
static char sdebug_info[256]; static char sdebug_info[256];
...@@ -1372,7 +1305,6 @@ static int scsi_debug_proc_info(char *buffer, char **start, off_t offset, ...@@ -1372,7 +1305,6 @@ static int scsi_debug_proc_info(char *buffer, char **start, off_t offset,
return len; return len;
} }
#ifdef DRIVERFS_SUPPORT
static ssize_t sdebug_delay_read(struct device_driver * ddp, char * buf, static ssize_t sdebug_delay_read(struct device_driver * ddp, char * buf,
size_t count, loff_t off) size_t count, loff_t off)
{ {
...@@ -1464,6 +1396,69 @@ static ssize_t sdebug_scsi_level_read(struct device_driver * ddp, char * buf, ...@@ -1464,6 +1396,69 @@ static ssize_t sdebug_scsi_level_read(struct device_driver * ddp, char * buf,
} }
DRIVER_ATTR(scsi_level, S_IRUGO, sdebug_scsi_level_read, NULL) DRIVER_ATTR(scsi_level, S_IRUGO, sdebug_scsi_level_read, NULL)
static ssize_t sdebug_add_host_read(struct device_driver * ddp, char * buf,
size_t count, loff_t off)
{
return off ? 0 : snprintf(buf, count, "%d\n", scsi_debug_add_host);
}
static ssize_t sdebug_add_host_write(struct device_driver * ddp,
const char * buf, size_t count, loff_t off)
{
struct Scsi_Host * hpnt;
int add_host, num, k;
char work[20];
if (off)
return 0;
if (1 != sscanf(buf, "%10s", work))
return -EINVAL;
{ /* temporary hack around sscanf() problem with -ve nums */
int neg = 0;
if ('-' == *work)
neg = 1;
if (1 != sscanf(work + neg, "%d", &add_host))
return -EINVAL;
if (neg)
add_host = -add_host;
}
num = 0;
if (add_host > 0) {
do {
for (k = 0; k < MAX_NUM_HOSTS; ++k) {
if (NULL == scsi_debug_hosts[k]) {
hpnt = sdebug_add_shost();
scsi_debug_hosts[k] = hpnt;
break;
}
}
if (k == MAX_NUM_HOSTS)
break;
++num;
} while (--add_host);
scsi_debug_add_host += num;
} else if (add_host < 0) {
do {
for (k = MAX_NUM_HOSTS - 1; k >= 0; --k) {
if (scsi_debug_hosts[k]) {
scsi_remove_host(scsi_debug_hosts[k]);
scsi_unregister(scsi_debug_hosts[k]);
scsi_debug_hosts[k] = NULL;
break;
}
}
if (k < 0)
break;
++num;
} while (++add_host);
scsi_debug_add_host -= num;
}
return count;
}
DRIVER_ATTR(add_host, S_IRUGO | S_IWUSR, sdebug_add_host_read,
sdebug_add_host_write)
static void do_create_driverfs_files() static void do_create_driverfs_files()
{ {
driver_create_file(&sdebug_driverfs_driver, &driver_attr_delay); driver_create_file(&sdebug_driverfs_driver, &driver_attr_delay);
...@@ -1473,10 +1468,12 @@ static void do_create_driverfs_files() ...@@ -1473,10 +1468,12 @@ static void do_create_driverfs_files()
driver_create_file(&sdebug_driverfs_driver, &driver_attr_every_nth); driver_create_file(&sdebug_driverfs_driver, &driver_attr_every_nth);
driver_create_file(&sdebug_driverfs_driver, &driver_attr_max_luns); driver_create_file(&sdebug_driverfs_driver, &driver_attr_max_luns);
driver_create_file(&sdebug_driverfs_driver, &driver_attr_scsi_level); driver_create_file(&sdebug_driverfs_driver, &driver_attr_scsi_level);
driver_create_file(&sdebug_driverfs_driver, &driver_attr_add_host);
} }
static void do_remove_driverfs_files() static void do_remove_driverfs_files()
{ {
driver_remove_file(&sdebug_driverfs_driver, &driver_attr_add_host);
driver_remove_file(&sdebug_driverfs_driver, &driver_attr_scsi_level); driver_remove_file(&sdebug_driverfs_driver, &driver_attr_scsi_level);
driver_remove_file(&sdebug_driverfs_driver, &driver_attr_max_luns); driver_remove_file(&sdebug_driverfs_driver, &driver_attr_max_luns);
driver_remove_file(&sdebug_driverfs_driver, &driver_attr_every_nth); driver_remove_file(&sdebug_driverfs_driver, &driver_attr_every_nth);
...@@ -1485,6 +1482,100 @@ static void do_remove_driverfs_files() ...@@ -1485,6 +1482,100 @@ static void do_remove_driverfs_files()
driver_remove_file(&sdebug_driverfs_driver, &driver_attr_opts); driver_remove_file(&sdebug_driverfs_driver, &driver_attr_opts);
driver_remove_file(&sdebug_driverfs_driver, &driver_attr_delay); driver_remove_file(&sdebug_driverfs_driver, &driver_attr_delay);
} }
#endif
#include "scsi_module.c" static struct Scsi_Host * sdebug_add_shost(void)
{
struct Scsi_Host * hpnt;
int err;
hpnt = scsi_register(&sdebug_driver_template, 0);
if (NULL == hpnt) {
printk(KERN_ERR "sdebug_add_shost: scsi_register failed\n");
return NULL;
}
err = scsi_add_host(hpnt);
if (err) {
printk(KERN_ERR "sdebug_add_shost: scsi_add_host failed\n");
scsi_unregister(hpnt);
return NULL;
}
hpnt->max_lun = scsi_debug_max_luns;
return hpnt;
}
static int __init scsi_debug_init(void)
{
struct Scsi_Host * hpnt;
int sz, k;
if (scsi_debug_num_devs > 0) {
sz = sizeof(struct sdebug_dev_info) * scsi_debug_num_devs;
devInfop = vmalloc(sz);
if (NULL == devInfop) {
printk(KERN_ERR "scsi_debug_init: out of memory\n");
return -ENOMEM;
}
memset(devInfop, 0, sz);
}
sz = STORE_SIZE;
fake_storep = vmalloc(sz);
if (NULL == fake_storep) {
printk(KERN_ERR "scsi_debug_init: out of memory, 1\n");
if (devInfop)
vfree(devInfop);
return -ENOMEM;
}
memset(fake_storep, 0, sz);
init_all_queued();
sdebug_driverfs_driver.name = (char *)sdebug_proc_name;
sdebug_driverfs_driver.bus = &scsi_driverfs_bus_type;
driver_register(&sdebug_driverfs_driver);
do_create_driverfs_files();
sdebug_driver_template.proc_name = (char *)sdebug_proc_name;
for (k = 0; (k < scsi_debug_add_host) && (k < MAX_NUM_HOSTS); k++) {
hpnt = sdebug_add_shost();
if (NULL == hpnt) {
printk(KERN_ERR "scsi_debug_init: "
"sdebug_add_shost failed k=%d\n", k);
break;
}
++num_hosts_present;
scsi_debug_hosts[k] = hpnt;
}
if (SCSI_DEBUG_OPT_NOISE & scsi_debug_opts) {
printk(KERN_INFO "scsi_debug: ... built %d host(s)\n",
num_hosts_present);
}
return 0;
}
static void __exit scsi_debug_exit(void)
{
int k;
for (k = 0; k < num_hosts_present; k++) {
scsi_remove_host(scsi_debug_hosts[k]);
scsi_unregister(scsi_debug_hosts[k]);
scsi_debug_hosts[k] = NULL;
}
stop_all_queued();
do_remove_driverfs_files();
driver_unregister(&sdebug_driverfs_driver);
vfree(fake_storep);
if (devInfop)
vfree(devInfop);
}
module_init(scsi_debug_init);
module_exit(scsi_debug_exit);
#ifndef _SCSI_DEBUG_H #ifndef _SCSI_DEBUG_H
#include <linux/types.h> #include <linux/types.h>
#include <linux/kdev_t.h>
static int scsi_debug_detect(struct SHT *);
static int scsi_debug_slave_attach(struct scsi_device *); static int scsi_debug_slave_attach(struct scsi_device *);
static void scsi_debug_slave_detach(struct scsi_device *); static void scsi_debug_slave_detach(struct scsi_device *);
static int scsi_debug_release(struct Scsi_Host *);
/* static int scsi_debug_command(struct scsi_cmnd *); */
static int scsi_debug_queuecommand(struct scsi_cmnd *, static int scsi_debug_queuecommand(struct scsi_cmnd *,
void (*done) (struct scsi_cmnd *)); void (*done) (struct scsi_cmnd *));
static int scsi_debug_ioctl(struct scsi_device *, int, void *); static int scsi_debug_ioctl(struct scsi_device *, int, void *);
...@@ -20,10 +16,6 @@ static int scsi_debug_host_reset(struct scsi_cmnd *); ...@@ -20,10 +16,6 @@ static int scsi_debug_host_reset(struct scsi_cmnd *);
static int scsi_debug_proc_info(char *, char **, off_t, int, int, int); static int scsi_debug_proc_info(char *, char **, off_t, int, int, int);
static const char * scsi_debug_info(struct Scsi_Host *); static const char * scsi_debug_info(struct Scsi_Host *);
#ifndef NULL
#define NULL 0
#endif
/* /*
* This driver is written for the lk 2.5 series * This driver is written for the lk 2.5 series
*/ */
...@@ -31,14 +23,12 @@ static const char * scsi_debug_info(struct Scsi_Host *); ...@@ -31,14 +23,12 @@ static const char * scsi_debug_info(struct Scsi_Host *);
#define SCSI_DEBUG_MAX_CMD_LEN 16 #define SCSI_DEBUG_MAX_CMD_LEN 16
static Scsi_Host_Template driver_template = { static Scsi_Host_Template sdebug_driver_template = {
.proc_info = scsi_debug_proc_info, .proc_info = scsi_debug_proc_info,
.name = "SCSI DEBUG", .name = "SCSI DEBUG",
.info = scsi_debug_info, .info = scsi_debug_info,
.detect = scsi_debug_detect,
.slave_attach = scsi_debug_slave_attach, .slave_attach = scsi_debug_slave_attach,
.slave_detach = scsi_debug_slave_detach, .slave_detach = scsi_debug_slave_detach,
.release = scsi_debug_release,
.ioctl = scsi_debug_ioctl, .ioctl = scsi_debug_ioctl,
.queuecommand = scsi_debug_queuecommand, .queuecommand = scsi_debug_queuecommand,
.eh_abort_handler = scsi_debug_abort, .eh_abort_handler = scsi_debug_abort,
...@@ -50,8 +40,9 @@ static Scsi_Host_Template driver_template = { ...@@ -50,8 +40,9 @@ static Scsi_Host_Template driver_template = {
.this_id = 7, .this_id = 7,
.sg_tablesize = 64, .sg_tablesize = 64,
.cmd_per_lun = 3, .cmd_per_lun = 3,
.max_sectors = 4096,
.unchecked_isa_dma = 0, .unchecked_isa_dma = 0,
.use_clustering = ENABLE_CLUSTERING, .use_clustering = ENABLE_CLUSTERING,
}; /* the name 'driver_template' is used by scsi_module.c */ };
#endif #endif
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