Commit 383a058c authored by Kai Germaschewski's avatar Kai Germaschewski

ISDN: Init ISA AVM CAPI drivers at module load time

Don't use a special CAPI solution to tell the drivers about 
ISA cards but use module parameters, just as other drivers do.

Internally use struct pci_dev to save that data - hopefully
one day the device tree will provide a nicer way to achieve this.
parent c1c5774f
...@@ -1135,54 +1135,13 @@ static struct capi_driver *find_driver(char *name) ...@@ -1135,54 +1135,13 @@ static struct capi_driver *find_driver(char *name)
static int old_capi_manufacturer(unsigned int cmd, void *data) static int old_capi_manufacturer(unsigned int cmd, void *data)
{ {
avmb1_loadandconfigdef ldef; avmb1_loadandconfigdef ldef;
avmb1_extcarddef cdef;
avmb1_resetdef rdef; avmb1_resetdef rdef;
avmb1_getdef gdef; avmb1_getdef gdef;
struct capi_driver *driver;
struct capi_ctr *card; struct capi_ctr *card;
capicardparams cparams;
capiloaddata ldata; capiloaddata ldata;
int retval; int retval;
switch (cmd) { switch (cmd) {
case AVMB1_ADDCARD:
case AVMB1_ADDCARD_WITH_TYPE:
if (cmd == AVMB1_ADDCARD) {
if ((retval = copy_from_user((void *) &cdef, data,
sizeof(avmb1_carddef))))
return retval;
cdef.cardtype = AVM_CARDTYPE_B1;
} else {
if ((retval = copy_from_user((void *) &cdef, data,
sizeof(avmb1_extcarddef))))
return retval;
}
cparams.port = cdef.port;
cparams.irq = cdef.irq;
cparams.cardnr = cdef.cardnr;
switch (cdef.cardtype) {
case AVM_CARDTYPE_B1:
driver = find_driver("b1isa");
break;
case AVM_CARDTYPE_T1:
driver = find_driver("t1isa");
break;
default:
driver = 0;
break;
}
if (!driver) {
printk(KERN_ERR "kcapi: driver not loaded.\n");
return -EIO;
}
if (!driver->add_card) {
printk(KERN_ERR "kcapi: driver has no add card function.\n");
return -EIO;
}
return driver->add_card(driver, &cparams);
case AVMB1_LOAD: case AVMB1_LOAD:
case AVMB1_LOAD_AND_CONFIG: case AVMB1_LOAD_AND_CONFIG:
...@@ -1327,8 +1286,6 @@ static int capi_manufacturer(unsigned int cmd, void *data) ...@@ -1327,8 +1286,6 @@ static int capi_manufacturer(unsigned int cmd, void *data)
switch (cmd) { switch (cmd) {
#ifdef CONFIG_AVMB1_COMPAT #ifdef CONFIG_AVMB1_COMPAT
case AVMB1_ADDCARD:
case AVMB1_ADDCARD_WITH_TYPE:
case AVMB1_LOAD: case AVMB1_LOAD:
case AVMB1_LOAD_AND_CONFIG: case AVMB1_LOAD_AND_CONFIG:
case AVMB1_RESETCARD: case AVMB1_RESETCARD:
...@@ -1354,37 +1311,6 @@ static int capi_manufacturer(unsigned int cmd, void *data) ...@@ -1354,37 +1311,6 @@ static int capi_manufacturer(unsigned int cmd, void *data)
return 0; return 0;
} }
case KCAPI_CMD_ADDCARD:
{
struct capi_driver *driver;
capicardparams cparams;
kcapi_carddef cdef;
if ((retval = copy_from_user((void *) &cdef, data,
sizeof(cdef))))
return retval;
cparams.port = cdef.port;
cparams.irq = cdef.irq;
cparams.membase = cdef.membase;
cparams.cardnr = cdef.cardnr;
cparams.cardtype = 0;
cdef.driver[sizeof(cdef.driver)-1] = 0;
if ((driver = find_driver(cdef.driver)) == 0) {
printk(KERN_ERR "kcapi: driver \"%s\" not loaded.\n",
cdef.driver);
return -ESRCH;
}
if (!driver->add_card) {
printk(KERN_ERR "kcapi: driver \"%s\" has no add card function.\n", cdef.driver);
return -EIO;
}
return driver->add_card(driver, &cparams);
}
default: default:
printk(KERN_ERR "kcapi: manufacturer command %d unknown.\n", printk(KERN_ERR "kcapi: manufacturer command %d unknown.\n",
cmd); cmd);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/capi.h> #include <linux/capi.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/pci.h>
#include <asm/io.h> #include <asm/io.h>
#include <linux/isdn/capicmd.h> #include <linux/isdn/capicmd.h>
#include <linux/isdn/capiutil.h> #include <linux/isdn/capiutil.h>
...@@ -34,16 +35,18 @@ MODULE_LICENSE("GPL"); ...@@ -34,16 +35,18 @@ MODULE_LICENSE("GPL");
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
static void b1isa_remove_ctr(struct capi_ctr *ctrl) static struct capi_driver b1isa_driver;
static void b1isa_remove(struct pci_dev *pdev)
{ {
avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); avmctrl_info *cinfo = pci_get_drvdata(pdev);
avmcard *card = cinfo->card; avmcard *card = cinfo->card;
unsigned int port = card->port; unsigned int port = cinfo->card->port;
b1_reset(port); b1_reset(port);
b1_reset(port); b1_reset(port);
detach_capi_ctr(ctrl); detach_capi_ctr(cinfo->capi_ctrl);
free_irq(card->irq, card); free_irq(card->irq, card);
release_region(card->port, AVMB1_PORTLEN); release_region(card->port, AVMB1_PORTLEN);
b1_free_card(card); b1_free_card(card);
...@@ -51,7 +54,7 @@ static void b1isa_remove_ctr(struct capi_ctr *ctrl) ...@@ -51,7 +54,7 @@ static void b1isa_remove_ctr(struct capi_ctr *ctrl)
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p) static int __init b1isa_probe(struct pci_dev *pdev)
{ {
avmctrl_info *cinfo; avmctrl_info *cinfo;
avmcard *card; avmcard *card;
...@@ -66,10 +69,10 @@ static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p) ...@@ -66,10 +69,10 @@ static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p)
cinfo = card->ctrlinfo; cinfo = card->ctrlinfo;
sprintf(card->name, "b1isa-%x", p->port); card->port = pci_resource_start(pdev, 0);
card->port = p->port; card->irq = pdev->irq;
card->irq = p->irq;
card->cardtype = avm_b1isa; card->cardtype = avm_b1isa;
sprintf(card->name, "b1isa-%x", card->port);
if ( card->port != 0x150 && card->port != 0x250 if ( card->port != 0x150 && card->port != 0x250
&& card->port != 0x300 && card->port != 0x340) { && card->port != 0x300 && card->port != 0x340) {
...@@ -103,7 +106,7 @@ static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p) ...@@ -103,7 +106,7 @@ static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p)
b1_reset(card->port); b1_reset(card->port);
b1_getrevision(card); b1_getrevision(card);
cinfo->capi_ctrl = attach_capi_ctr(driver, card->name, cinfo); cinfo->capi_ctrl = attach_capi_ctr(&b1isa_driver, card->name, cinfo);
if (!cinfo->capi_ctrl) { if (!cinfo->capi_ctrl) {
printk(KERN_ERR "b1isa: attach controller failed.\n"); printk(KERN_ERR "b1isa: attach controller failed.\n");
retval = -EBUSY; retval = -EBUSY;
...@@ -112,8 +115,9 @@ static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p) ...@@ -112,8 +115,9 @@ static int b1isa_add_card(struct capi_driver *driver, struct capicardparams *p)
printk(KERN_INFO printk(KERN_INFO
"%s: AVM B1 ISA at i/o %#x, irq %d, revision %d\n", "%s: AVM B1 ISA at i/o %#x, irq %d, revision %d\n",
driver->name, card->port, card->irq, card->revision); b1isa_driver.name, card->port, card->irq, card->revision);
pci_set_drvdata(pdev, cinfo);
return 0; return 0;
err_free_irq: err_free_irq:
...@@ -150,7 +154,6 @@ static struct capi_driver b1isa_driver = { ...@@ -150,7 +154,6 @@ static struct capi_driver b1isa_driver = {
revision: "0.0", revision: "0.0",
load_firmware: b1_load_firmware, load_firmware: b1_load_firmware,
reset_ctr: b1_reset_ctr, reset_ctr: b1_reset_ctr,
remove_ctr: b1isa_remove_ctr,
register_appl: b1_register_appl, register_appl: b1_register_appl,
release_appl: b1_release_appl, release_appl: b1_release_appl,
send_message: b1_send_message, send_message: b1_send_message,
...@@ -158,24 +161,63 @@ static struct capi_driver b1isa_driver = { ...@@ -158,24 +161,63 @@ static struct capi_driver b1isa_driver = {
procinfo: b1isa_procinfo, procinfo: b1isa_procinfo,
ctr_read_proc: b1ctl_read_proc, ctr_read_proc: b1ctl_read_proc,
driver_read_proc: 0, /* use standard driver_read_proc */ driver_read_proc: 0, /* use standard driver_read_proc */
add_card: b1isa_add_card,
}; };
#define MAX_CARDS 4
static struct pci_dev isa_dev[MAX_CARDS];
static int io[MAX_CARDS];
static int irq[MAX_CARDS];
MODULE_PARM(io, "1-" __MODULE_STRING(MAX_CARDS) "i");
MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_CARDS) "i");
MODULE_PARM_DESC(io, "I/O base address(es)");
MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)");
static int __init b1isa_init(void) static int __init b1isa_init(void)
{ {
int i, retval;
int found = 0;
MOD_INC_USE_COUNT; MOD_INC_USE_COUNT;
b1_set_revision(&b1isa_driver, revision); b1_set_revision(&b1isa_driver, revision);
attach_capi_driver(&b1isa_driver); attach_capi_driver(&b1isa_driver);
for (i = 0; i < MAX_CARDS; i++) {
if (!io[i])
break;
isa_dev[i].resource[0].start = io[i];
isa_dev[i].irq_resource[0].start = irq[i];
if (b1isa_probe(&isa_dev[i]) == 0)
found++;
}
if (found == 0) {
retval = -ENODEV;
goto err;
}
retval = 0;
goto out;
err:
detach_capi_driver(&b1isa_driver);
out:
MOD_DEC_USE_COUNT; MOD_DEC_USE_COUNT;
return 0; return retval;
} }
static void __exit b1isa_exit(void) static void __exit b1isa_exit(void)
{ {
detach_capi_driver(&b1isa_driver); int i;
for (i = 0; i < MAX_CARDS; i++) {
if (!io[i])
break;
b1isa_remove(&isa_dev[i]);
}
detach_capi_driver(&b1isa_driver);
} }
module_init(b1isa_init); module_init(b1isa_init);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/kernelcapi.h> #include <linux/kernelcapi.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/pci.h>
#include <asm/io.h> #include <asm/io.h>
#include <linux/isdn/capicmd.h> #include <linux/isdn/capicmd.h>
#include <linux/isdn/capiutil.h> #include <linux/isdn/capiutil.h>
...@@ -36,6 +37,8 @@ MODULE_LICENSE("GPL"); ...@@ -36,6 +37,8 @@ MODULE_LICENSE("GPL");
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
static struct capi_driver t1isa_driver;
static int hema_irq_table[16] = static int hema_irq_table[16] =
{0, {0,
0, 0,
...@@ -323,9 +326,9 @@ void t1isa_reset_ctr(struct capi_ctr *ctrl) ...@@ -323,9 +326,9 @@ void t1isa_reset_ctr(struct capi_ctr *ctrl)
ctrl->reseted(ctrl); ctrl->reseted(ctrl);
} }
static void t1isa_remove_ctr(struct capi_ctr *ctrl) static void t1isa_remove(struct pci_dev *pdev)
{ {
avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); avmctrl_info *cinfo = pci_get_drvdata(pdev);
avmcard *card = cinfo->card; avmcard *card = cinfo->card;
unsigned int port = card->port; unsigned int port = card->port;
...@@ -334,7 +337,7 @@ static void t1isa_remove_ctr(struct capi_ctr *ctrl) ...@@ -334,7 +337,7 @@ static void t1isa_remove_ctr(struct capi_ctr *ctrl)
b1_reset(port); b1_reset(port);
t1_reset(port); t1_reset(port);
detach_capi_ctr(ctrl); detach_capi_ctr(cinfo->capi_ctrl);
free_irq(card->irq, card); free_irq(card->irq, card);
release_region(card->port, AVMB1_PORTLEN); release_region(card->port, AVMB1_PORTLEN);
b1_free_card(card); b1_free_card(card);
...@@ -342,87 +345,74 @@ static void t1isa_remove_ctr(struct capi_ctr *ctrl) ...@@ -342,87 +345,74 @@ static void t1isa_remove_ctr(struct capi_ctr *ctrl)
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
static int t1isa_add_card(struct capi_driver *driver, struct capicardparams *p) static int __init t1isa_probe(struct pci_dev *pdev)
{ {
struct capi_ctr *ctrl;
struct list_head *l;
avmctrl_info *cinfo; avmctrl_info *cinfo;
avmcard *card; avmcard *card;
int retval; int retval;
static int cardnr = 1;
card = b1_alloc_card(1); card = b1_alloc_card(1);
if (!card) { if (!card) {
printk(KERN_WARNING "%s: no memory.\n", driver->name); printk(KERN_WARNING "%s: no memory.\n", t1isa_driver.name);
retval = -ENOMEM; retval = -ENOMEM;
goto err; goto err;
} }
cinfo = card->ctrlinfo; cinfo = card->ctrlinfo;
sprintf(card->name, "t1isa-%x", p->port); card->port = pci_resource_start(pdev, 0);
card->port = p->port; card->irq = pdev->irq;
card->irq = p->irq;
card->cardtype = avm_t1isa; card->cardtype = avm_t1isa;
card->cardnr = p->cardnr; card->cardnr = cardnr++;
sprintf(card->name, "t1isa-%x", card->port);
if (!(((card->port & 0x7) == 0) && ((card->port & 0x30) != 0x30))) { if (!(((card->port & 0x7) == 0) && ((card->port & 0x30) != 0x30))) {
printk(KERN_WARNING "%s: illegal port 0x%x.\n", printk(KERN_WARNING "%s: illegal port 0x%x.\n",
driver->name, card->port); t1isa_driver.name, card->port);
retval = -EINVAL; retval = -EINVAL;
goto err_free; goto err_free;
} }
if (hema_irq_table[card->irq & 0xf] == 0) { if (hema_irq_table[card->irq & 0xf] == 0) {
printk(KERN_WARNING "%s: irq %d not valid.\n", printk(KERN_WARNING "%s: irq %d not valid.\n",
driver->name, card->irq); t1isa_driver.name, card->irq);
retval = -EINVAL; retval = -EINVAL;
goto err_free; goto err_free;
} }
list_for_each(l, &driver->contr_head) {
avmcard *cardp;
ctrl = list_entry(l, struct capi_ctr, driver_list);
cardp = ((avmctrl_info *)(ctrl->driverdata))->card;
if (cardp->cardnr == card->cardnr) {
printk(KERN_WARNING "%s: card with number %d already installed at 0x%x.\n",
driver->name, card->cardnr, cardp->port);
retval = -EINVAL;
goto err_free;
}
}
if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { if (!request_region(card->port, AVMB1_PORTLEN, card->name)) {
printk(KERN_WARNING "%s: ports 0x%03x-0x%03x in use.\n", printk(KERN_INFO "%s: ports 0x%03x-0x%03x in use.\n",
driver->name, card->port, card->port + AVMB1_PORTLEN); t1isa_driver.name, card->port, card->port + AVMB1_PORTLEN);
retval = -EBUSY; retval = -EBUSY;
goto err_free; goto err_free;
} }
retval = request_irq(card->irq, t1isa_interrupt, 0, card->name, card); retval = request_irq(card->irq, t1isa_interrupt, 0, card->name, card);
if (retval) { if (retval) {
printk(KERN_ERR "%s: unable to get IRQ %d.\n", printk(KERN_INFO "%s: unable to get IRQ %d.\n",
driver->name, card->irq); t1isa_driver.name, card->irq);
retval = -EBUSY; retval = -EBUSY;
goto err_release_region; goto err_release_region;
} }
if ((retval = t1_detectandinit(card->port, card->irq, card->cardnr)) != 0) { if ((retval = t1_detectandinit(card->port, card->irq, card->cardnr)) != 0) {
printk(KERN_NOTICE "%s: NO card at 0x%x (%d)\n", printk(KERN_INFO "%s: NO card at 0x%x (%d)\n",
driver->name, card->port, retval); t1isa_driver.name, card->port, retval);
retval = -ENODEV; retval = -ENODEV;
goto err_free_irq; goto err_free_irq;
} }
t1_disable_irq(card->port); t1_disable_irq(card->port);
b1_reset(card->port); b1_reset(card->port);
cinfo->capi_ctrl = attach_capi_ctr(driver, card->name, cinfo); cinfo->capi_ctrl = attach_capi_ctr(&t1isa_driver, card->name, cinfo);
if (!cinfo->capi_ctrl) { if (!cinfo->capi_ctrl) {
printk(KERN_ERR "%s: attach controller failed.\n", printk(KERN_INFO "%s: attach controller failed.\n",
driver->name); t1isa_driver.name);
retval = -EBUSY; retval = -EBUSY;
goto err_free_irq; goto err_free_irq;
} }
printk(KERN_INFO printk(KERN_INFO "%s: AVM T1 ISA at i/o %#x, irq %d, card %d\n",
"%s: AVM T1 ISA at i/o %#x, irq %d, card %d\n", t1isa_driver.name, card->port, card->irq, card->cardnr);
driver->name, card->port, card->irq, card->cardnr);
pci_set_drvdata(pdev, cinfo);
return 0; return 0;
err_free_irq: err_free_irq:
...@@ -502,7 +492,6 @@ static struct capi_driver t1isa_driver = { ...@@ -502,7 +492,6 @@ static struct capi_driver t1isa_driver = {
revision: "0.0", revision: "0.0",
load_firmware: t1isa_load_firmware, load_firmware: t1isa_load_firmware,
reset_ctr: t1isa_reset_ctr, reset_ctr: t1isa_reset_ctr,
remove_ctr: t1isa_remove_ctr,
register_appl: b1_register_appl, register_appl: b1_register_appl,
release_appl: b1_release_appl, release_appl: b1_release_appl,
send_message: t1isa_send_message, send_message: t1isa_send_message,
...@@ -510,23 +499,62 @@ static struct capi_driver t1isa_driver = { ...@@ -510,23 +499,62 @@ static struct capi_driver t1isa_driver = {
procinfo: t1isa_procinfo, procinfo: t1isa_procinfo,
ctr_read_proc: b1ctl_read_proc, ctr_read_proc: b1ctl_read_proc,
driver_read_proc: 0, /* use standard driver_read_proc */ driver_read_proc: 0, /* use standard driver_read_proc */
add_card: t1isa_add_card,
}; };
#define MAX_CARDS 4
static struct pci_dev isa_dev[MAX_CARDS];
static int io[MAX_CARDS];
static int irq[MAX_CARDS];
MODULE_PARM(io, "1-" __MODULE_STRING(MAX_CARDS) "i");
MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_CARDS) "i");
MODULE_PARM_DESC(io, "I/O base address(es)");
MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)");
static int __init t1isa_init(void) static int __init t1isa_init(void)
{ {
int i, retval;
int found = 0;
MOD_INC_USE_COUNT; MOD_INC_USE_COUNT;
b1_set_revision(&t1isa_driver, revision); b1_set_revision(&t1isa_driver, revision);
attach_capi_driver(&t1isa_driver); attach_capi_driver(&t1isa_driver);
for (i = 0; i < MAX_CARDS; i++) {
if (!io[i])
break;
isa_dev[i].resource[0].start = io[i];
isa_dev[i].irq_resource[0].start = irq[i];
if (t1isa_probe(&isa_dev[i]) == 0)
found++;
}
if (found == 0) {
retval = -ENODEV;
goto err;
}
retval = 0;
goto out;
err:
detach_capi_driver(&t1isa_driver);
out:
MOD_DEC_USE_COUNT; MOD_DEC_USE_COUNT;
return 0; return retval;
} }
static void __exit t1isa_exit(void) static void __exit t1isa_exit(void)
{ {
int i;
for (i = 0; i < MAX_CARDS; i++) {
if (!io[i])
break;
t1isa_remove(&isa_dev[i]);
}
detach_capi_driver(&t1isa_driver); detach_capi_driver(&t1isa_driver);
} }
......
...@@ -701,8 +701,6 @@ static struct capi_driver hycapi_driver = { ...@@ -701,8 +701,6 @@ static struct capi_driver hycapi_driver = {
procinfo: hycapi_procinfo, procinfo: hycapi_procinfo,
ctr_read_proc: hycapi_read_proc, ctr_read_proc: hycapi_read_proc,
driver_read_proc: 0, /* use standard driver_read_proc */ driver_read_proc: 0, /* use standard driver_read_proc */
add_card: 0, /* no add_card function */
}; };
......
...@@ -61,7 +61,7 @@ typedef struct avmb1_extcarddef { ...@@ -61,7 +61,7 @@ typedef struct avmb1_extcarddef {
} avmb1_extcarddef; } avmb1_extcarddef;
#define AVMB1_LOAD 0 /* load image to card */ #define AVMB1_LOAD 0 /* load image to card */
#define AVMB1_ADDCARD 1 /* add a new card */ #define AVMB1_ADDCARD 1 /* add a new card - OBSOLETE */
#define AVMB1_RESETCARD 2 /* reset a card */ #define AVMB1_RESETCARD 2 /* reset a card */
#define AVMB1_LOAD_AND_CONFIG 3 /* load image and config to card */ #define AVMB1_LOAD_AND_CONFIG 3 /* load image and config to card */
#define AVMB1_ADDCARD_WITH_TYPE 4 /* add a new card, with cardtype */ #define AVMB1_ADDCARD_WITH_TYPE 4 /* add a new card, with cardtype */
......
...@@ -92,8 +92,6 @@ struct capi_driver { ...@@ -92,8 +92,6 @@ struct capi_driver {
int (*driver_read_proc)(char *page, char **start, off_t off, int (*driver_read_proc)(char *page, char **start, off_t off,
int count, int *eof, struct capi_driver *driver); int count, int *eof, struct capi_driver *driver);
int (*add_card)(struct capi_driver *driver, capicardparams *data);
/* intitialized by kcapi */ /* intitialized by kcapi */
struct list_head contr_head; /* list of controllers */ struct list_head contr_head; /* list of controllers */
struct list_head driver_list; struct list_head driver_list;
......
...@@ -32,7 +32,7 @@ typedef struct kcapi_carddef { ...@@ -32,7 +32,7 @@ typedef struct kcapi_carddef {
/* new ioctls >= 10 */ /* new ioctls >= 10 */
#define KCAPI_CMD_TRACE 10 #define KCAPI_CMD_TRACE 10
#define KCAPI_CMD_ADDCARD 11 /* add card to named driver */ #define KCAPI_CMD_ADDCARD 11 /* OBSOLETE */
/* /*
* flag > 2 => trace also data * flag > 2 => trace also data
......
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