Commit 433e63c6 authored by Mark Allyn's avatar Mark Allyn Committed by Greg Kroah-Hartman

Staging: rar_register: fix checkpatch errors and debug program file

Signed-off-by: default avatarMark Allyn <mark.a.allyn@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 9cedb392
#include <linux/init.h> /*
#include <linux/module.h> * rar_register.c - An Intel Restricted Access Region register driver
#include <linux/fs.h> *
#include <linux/cdev.h> * Copyright(c) 2009 Intel Corporation. All rights reserved.
#include <linux/kdev_t.h> *
#include <linux/semaphore.h> * This program is free software; you can redistribute it and/or
#include <linux/mm.h> * modify it under the terms of the GNU General Public License as
#include <linux/poll.h> * published by the Free Software Foundation; either version 2 of the
#include <linux/wait.h> * License, or (at your option) any later version.
#include <linux/ioctl.h> *
#include <linux/ioport.h> * This program is distributed in the hope that it will be useful,
#include <linux/io.h> * but WITHOUT ANY WARRANTY; without even the implied warranty of
#include <linux/interrupt.h> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#include <linux/pagemap.h> * General Public License for more details.
#include <linux/pci.h> *
#include <linux/firmware.h> * You should have received a copy of the GNU General Public License
#include <linux/sched.h> * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* -------------------------------------------------------------------
* 20091204 Mark Allyn <mark.a.allyn@intel.com>
* Ossama Othman <ossama.othman@intel.com>
* Cleanup per feedback from Alan Cox and Arjan Van De Ven
*
* 20090806 Ossama Othman <ossama.othman@intel.com>
* Return zero high address if upper 22 bits is zero.
* Cleaned up checkpatch errors.
* Clarified that driver is dealing with bus addresses.
*
* 20090702 Ossama Othman <ossama.othman@intel.com>
* Removed unnecessary include directives
* Cleaned up spinlocks.
* Cleaned up logging.
* Improved invalid parameter checks.
* Fixed and simplified RAR address retrieval and RAR locking
* code.
*
* 20090626 Mark Allyn <mark.a.allyn@intel.com>
* Initial publish
*/
#define DEBUG 1
#include "rar_register.h" #include "rar_register.h"
/* The following defines are for the IPC process to retrieve RAR in */ #include <linux/module.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/kernel.h>
/* === Lincroft Message Bus Interface === */ /* === Lincroft Message Bus Interface === */
/* Message Control Register */ /* Message Control Register */
#define LNC_MCR_OFFSET 0xD0 #define LNC_MCR_OFFSET 0xD0
/* Maximum number of clients (other drivers using this driver) */
#define MAX_RAR_CLIENTS 10
/* Message Data Register */ /* Message Data Register */
#define LNC_MDR_OFFSET 0xD4 #define LNC_MDR_OFFSET 0xD4
...@@ -46,50 +80,64 @@ ...@@ -46,50 +80,64 @@
#define LNC_BRAR2L 0x14 #define LNC_BRAR2L 0x14
#define LNC_BRAR2H 0x15 #define LNC_BRAR2H 0x15
/* Moorestown supports three restricted access regions. */
#define MRST_NUM_RAR 3
/* This structure is only used during module initialization. */
struct RAR_offsets {
int low; /* Register offset for low RAR physical address. */
int high; /* Register offset for high RAR physical address. */
};
struct pci_dev *rar_dev; /* RAR Bus Address Range */
static uint32_t registered; struct RAR_address_range {
dma_addr_t low;
dma_addr_t high;
};
/* Moorestown supports three restricted access regions. */ /* Structure containing low and high RAR register offsets. */
#define MRST_NUM_RAR 3 struct RAR_offsets {
u32 low; /* Register offset for low RAR bus address. */
u32 high; /* Register offset for high RAR bus address. */
};
struct RAR_address_struct rar_addr[MRST_NUM_RAR]; struct client {
int (*client_callback)(void *client_data);
void *customer_data;
int client_called;
};
/* prototype for init */ static DEFINE_MUTEX(rar_mutex);
static int __init rar_init_handler(void); static DEFINE_MUTEX(lnc_reg_mutex);
static void __exit rar_exit_handler(void);
/* struct RAR_device {
function that is activated on the successfull probe of the RAR device struct RAR_offsets const rar_offsets[MRST_NUM_RAR];
*/ struct RAR_address_range rar_addr[MRST_NUM_RAR];
static int __devinit rar_probe(struct pci_dev *pdev, struct pci_dev *rar_dev;
const struct pci_device_id *ent); bool registered;
};
static const struct pci_device_id rar_pci_id_tbl[] = { /* this platform has only one rar_device for 3 rar regions */
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4110) }, static struct RAR_device my_rar_device = {
{ 0 } .rar_offsets = {
[0].low = LNC_BRAR0L,
[0].high = LNC_BRAR0H,
[1].low = LNC_BRAR1L,
[1].high = LNC_BRAR1H,
[2].low = LNC_BRAR2L,
[2].high = LNC_BRAR2H
}
}; };
MODULE_DEVICE_TABLE(pci, rar_pci_id_tbl); /* this data is for handling requests from other drivers which arrive
* prior to this driver initializing
*/
/* field for registering driver to PCI device */ static struct client clients[MAX_RAR_CLIENTS];
static struct pci_driver rar_pci_driver = { static int num_clients;
.name = "rar_driver",
.id_table = rar_pci_id_tbl,
.probe = rar_probe
};
/* This function is used to retrieved RAR info using the IPC message /*
bus interface */ * This function is used to retrieved RAR info using the Lincroft
static int memrar_get_rar_addr(struct pci_dev *pdev, * message bus interface.
*/
static int retrieve_rar_addr(struct pci_dev *pdev,
int offset, int offset,
u32 *addr) dma_addr_t *addr)
{ {
/* /*
* ======== The Lincroft Message Bus Interface ======== * ======== The Lincroft Message Bus Interface ========
...@@ -133,7 +181,8 @@ static int memrar_get_rar_addr(struct pci_dev *pdev, ...@@ -133,7 +181,8 @@ static int memrar_get_rar_addr(struct pci_dev *pdev,
* register. * register.
*/ */
int result = 0; /* result */ int result;
/* Construct control message */ /* Construct control message */
u32 const message = u32 const message =
(LNC_MESSAGE_READ_OPCODE << 24) (LNC_MESSAGE_READ_OPCODE << 24)
...@@ -141,233 +190,401 @@ static int memrar_get_rar_addr(struct pci_dev *pdev, ...@@ -141,233 +190,401 @@ static int memrar_get_rar_addr(struct pci_dev *pdev,
| (offset << 8) | (offset << 8)
| (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4); | (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4);
printk(KERN_WARNING "rar- offset to LNC MSG is %x\n", offset); dev_dbg(&pdev->dev, "Offset for 'get' LNC MSG is %x\n", offset);
if (addr == 0) if (addr == 0) {
WARN_ON(1);
return -EINVAL; return -EINVAL;
}
/*
* We synchronize access to the Lincroft MCR and MDR registers
* until BOTH the command is issued through the MCR register
* and the corresponding data is read from the MDR register.
* Otherwise a race condition would exist between accesses to
* both registers.
*/
mutex_lock(&lnc_reg_mutex);
/* Send the control message */ /* Send the control message */
result = pci_write_config_dword(pdev, result = pci_write_config_dword(pdev, LNC_MCR_OFFSET, message);
LNC_MCR_OFFSET,
message);
printk(KERN_WARNING "rar- result from send ctl register is %x\n", dev_dbg(&pdev->dev, "Result from send ctl register is %x\n", result);
result);
if (!result) if (!result) {
result = pci_read_config_dword(pdev, result = pci_read_config_dword(pdev, LNC_MDR_OFFSET,
LNC_MDR_OFFSET, (u32 *)addr);
addr); dev_dbg(&pdev->dev,
"Result from read data register is %x\n", result);
printk(KERN_WARNING "rar- result from read data register is %x\n", dev_dbg(&pdev->dev,
result); "Value read from data register is %lx\n",
(unsigned long)*addr);
}
printk(KERN_WARNING "rar- value read from data register is %x\n", mutex_unlock(&lnc_reg_mutex);
*addr);
if (result) return result;
return -1;
else
return 0;
} }
static int memrar_set_rar_addr(struct pci_dev *pdev, static int set_rar_address(struct pci_dev *pdev,
int offset, int offset,
u32 addr) dma_addr_t addr)
{ {
/* /*
* ======== The Lincroft Message Bus Interface ========
* Lincroft registers may be obtained from the PCI
* (the Host Bridge) using the Lincroft Message Bus
* Interface. That message bus interface is generally
* comprised of two registers: a control register (MCR, 0xDO)
* and a data register (MDR, 0xD4).
*
* The MCR (message control register) format is the following:
* 1. [31:24]: Opcode
* 2. [23:16]: Port
* 3. [15:8]: Register Offset
* 4. [7:4]: Byte Enables (use 0xF to set all of these bits
* to 1)
* 5. [3:0]: reserved
*
* Read (0xD0) and write (0xE0) opcodes are written to the
* control register when reading and writing to Lincroft
* registers, respectively.
*
* We're interested in registers found in the Lincroft
* B-unit. The B-unit port is 0x3.
*
* The six B-unit RAR register offsets we use are listed
* earlier in this file.
*
* Lastly writing to the MCR register requires the "Byte
* enables" bits to be set to 1. This may be achieved by
* writing 0xF at bit 4.
*
* The MDR (message data register) format is the following:
* 1. [31:0]: Read/Write Data
*
* Data being read from this register is only available after
* writing the appropriate control message to the MCR
* register.
*
* Data being written to this register must be written before * Data being written to this register must be written before
* writing the appropriate control message to the MCR * writing the appropriate control message to the MCR
* register. * register.
* @note See rar_get_address() for a description of the
* message bus interface being used here.
*/ */
int result = 0; /* result */ int result = 0;
/* Construct control message */ /* Construct control message */
u32 const message = u32 const message = (LNC_MESSAGE_WRITE_OPCODE << 24)
(LNC_MESSAGE_WRITE_OPCODE << 24)
| (LNC_BUNIT_PORT << 16) | (LNC_BUNIT_PORT << 16)
| (offset << 8) | (offset << 8)
| (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4); | (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4);
printk(KERN_WARNING "rar- offset to LNC MSG is %x\n", offset); if (addr == 0) {
WARN_ON(1);
if (addr == 0)
return -EINVAL; return -EINVAL;
}
dev_dbg(&pdev->dev, "Offset for 'set' LNC MSG is %x\n", offset);
/*
* We synchronize access to the Lincroft MCR and MDR registers
* until BOTH the command is issued through the MCR register
* and the corresponding data is read from the MDR register.
* Otherwise a race condition would exist between accesses to
* both registers.
*/
mutex_lock(&lnc_reg_mutex);
/* Send the control message */ /* Send the control message */
result = pci_write_config_dword(pdev, result = pci_write_config_dword(pdev, LNC_MDR_OFFSET, addr);
LNC_MDR_OFFSET,
addr);
printk(KERN_WARNING "rar- result from send ctl register is %x\n", dev_dbg(&pdev->dev, "Result from write data register is %x\n", result);
result);
if (!result) {
dev_dbg(&pdev->dev,
"Value written to data register is %lx\n",
(unsigned long)addr);
if (!result) result = pci_write_config_dword(pdev, LNC_MCR_OFFSET, message);
result = pci_write_config_dword(pdev,
LNC_MCR_OFFSET,
message);
printk(KERN_WARNING "rar- result from write data register is %x\n", dev_dbg(&pdev->dev, "Result from send ctl register is %x\n",
result); result);
}
printk(KERN_WARNING "rar- value read to data register is %x\n", mutex_unlock(&lnc_reg_mutex);
addr);
if (result) return result;
return -1;
else
return 0;
} }
/* /*
* Initialize RAR parameters, such as bus addresses, etc.
* Initialize RAR parameters, such as physical addresses, etc. */
static int init_rar_params(struct pci_dev *pdev)
*/
static int memrar_init_rar_params(struct pci_dev *pdev)
{ {
struct RAR_offsets const offsets[] = { unsigned int i;
{ LNC_BRAR0L, LNC_BRAR0H },
{ LNC_BRAR1L, LNC_BRAR1H },
{ LNC_BRAR2L, LNC_BRAR2H }
};
size_t const num_offsets = sizeof(offsets) / sizeof(offsets[0]);
struct RAR_offsets const *end = offsets + num_offsets;
struct RAR_offsets const *i;
unsigned int n = 0;
int result = 0; int result = 0;
/* Retrieve RAR start and end physical addresses. */ /* Retrieve RAR start and end bus addresses.
/*
* Access the RAR registers through the Lincroft Message Bus * Access the RAR registers through the Lincroft Message Bus
* Interface on PCI device: 00:00.0 Host bridge. * Interface on PCI device: 00:00.0 Host bridge.
*/ */
/* struct pci_dev *pdev = pci_get_bus_and_slot(0, PCI_DEVFN(0,0)); */ for (i = 0; i < MRST_NUM_RAR; ++i) {
struct RAR_offsets const *offset =
if (pdev == NULL) &my_rar_device.rar_offsets[i];
return -ENODEV; struct RAR_address_range *addr = &my_rar_device.rar_addr[i];
for (i = offsets; i != end; ++i, ++n) { if ((retrieve_rar_addr(pdev, offset->low, &addr->low) != 0)
if (memrar_get_rar_addr(pdev, || (retrieve_rar_addr(pdev, offset->high, &addr->high) != 0)) {
(*i).low,
&(rar_addr[n].low)) != 0
|| memrar_get_rar_addr(pdev,
(*i).high,
&(rar_addr[n].high)) != 0) {
result = -1; result = -1;
break; break;
} }
}
/*
* Only the upper 22 bits of the RAR addresses are
* stored in their corresponding RAR registers so we
* must set the lower 10 bits accordingly.
* The low address has its lower 10 bits cleared, and
* the high address has all its lower 10 bits set,
* e.g.:
* low = 0x2ffffc00
*/
addr->low &= (dma_addr_t)0xfffffc00u;
/*
* Set bits 9:0 on uppser address if bits 31:10 are non
* zero; otherwize clear all bits
*/
if ((addr->high & 0xfffffc00u) == 0)
addr->high = 0;
else
addr->high |= 0x3ffu;
}
/* Done accessing the device. */ /* Done accessing the device. */
/* pci_dev_put(pdev); */
if (result == 0) { if (result == 0) {
if (1) { int z;
size_t z;
for (z = 0; z != MRST_NUM_RAR; ++z) { for (z = 0; z != MRST_NUM_RAR; ++z) {
printk(KERN_WARNING /*
"rar - BRAR[%Zd] physical address low\n" * "BRAR" refers to the RAR registers in the
"\tlow: 0x%08x\n" * Lincroft B-unit.
"\thigh: 0x%08x\n", */
z, dev_info(&pdev->dev, "BRAR[%u] bus address range = "
rar_addr[z].low, "[%lx, %lx]\n", z,
rar_addr[z].high); (unsigned long)my_rar_device.rar_addr[z].low,
(unsigned long)my_rar_device.rar_addr[z].high);
} }
} }
return result;
}
/*
* The rar_get_address function is used by other device drivers
* to obtain RAR address information on a RAR. It takes three
* parameters:
*
* int rar_index
* The rar_index is an index to the rar for which you wish to retrieve
* the address information.
* Values can be 0,1, or 2.
*
* The function returns a 0 upon success or a -1 if there is no RAR
* facility on this system.
*/
int rar_get_address(int rar_index,
dma_addr_t *start_address,
dma_addr_t *end_address)
{
int result = -ENODEV;
if (my_rar_device.registered) {
if (start_address == 0 || end_address == 0
|| rar_index >= MRST_NUM_RAR || rar_index < 0) {
result = -EINVAL;
} else {
*start_address =
my_rar_device.rar_addr[rar_index].low;
*end_address =
my_rar_device.rar_addr[rar_index].high;
result = 0;
}
} }
return result; return result;
} }
EXPORT_SYMBOL(rar_get_address);
/* /*
function that is activated on the successfull probe of the RAR device * The rar_lock function is ued by other device drivers to lock an RAR.
* once an RAR is locked, it stays locked until the next system reboot.
* The function takes one parameter:
*
* int rar_index
* The rar_index is an index to the rar that you want to lock.
* Values can be 0,1, or 2.
*
* The function returns a 0 upon success or a -1 if there is no RAR
* facility on this system.
*/
int rar_lock(int rar_index)
{
int result = -ENODEV;
if (rar_index >= MRST_NUM_RAR || rar_index < 0) {
result = -EINVAL;
return result;
}
dev_dbg(&my_rar_device.rar_dev->dev, "rar_lock mutex locking\n");
mutex_lock(&rar_mutex);
if (my_rar_device.registered) {
dma_addr_t low = my_rar_device.rar_addr[rar_index].low &
0xfffffc00u;
dma_addr_t high = my_rar_device.rar_addr[rar_index].high &
0xfffffc00u;
/*
* Only allow I/O from the graphics and Langwell;
* Not from the x96 processor
*/
if (rar_index == (int)RAR_TYPE_VIDEO) {
low |= 0x00000009;
high |= 0x00000015;
}
else if (rar_index == (int)RAR_TYPE_AUDIO) {
/* Only allow I/O from Langwell; nothing from x86 */
low |= 0x00000008;
high |= 0x00000018;
}
else
/* Read-only from all agents */
high |= 0x00000018;
/*
* Now program the register using the Lincroft message
* bus interface.
*/
result = set_rar_address(my_rar_device.rar_dev,
my_rar_device.rar_offsets[rar_index].low,
low);
if (result == 0)
result = set_rar_address(
my_rar_device.rar_dev,
my_rar_device.rar_offsets[rar_index].high,
high);
}
dev_dbg(&my_rar_device.rar_dev->dev, "rar_lock mutex unlocking\n");
mutex_unlock(&rar_mutex);
return result;
}
EXPORT_SYMBOL(rar_lock);
/* The register_rar function is to used by other device drivers
* to ensure that this driver is ready. As we cannot be sure of
* the compile/execute order of dirvers in ther kernel, it is
* best to give this driver a callback function to call when
* it is ready to give out addresses. The callback function
* would have those steps that continue the initialization of
* a driver that do require a valid RAR address. One of those
* steps would be to call rar_get_address()
* This function return 0 on success an -1 on failure.
*/ */
static int __devinit rar_probe(struct pci_dev *pdev, int register_rar(int (*callback)(void *yourparameter), void *yourparameter)
const struct pci_device_id *ent)
{ {
/* error */
int error;
/*------------------------ int result = -ENODEV;
CODE
---------------------------*/ if (callback == NULL)
return -EINVAL;
mutex_lock(&rar_mutex);
if (my_rar_device.registered) {
mutex_unlock(&rar_mutex);
/*
* if the driver already registered, then we can simply
* call the callback right now
*/
return (*callback)(yourparameter);
}
if (num_clients < MRST_NUM_RAR) {
clients[num_clients].client_callback = callback;
clients[num_clients].customer_data = yourparameter;
num_clients += 1;
result = 0;
}
mutex_unlock(&rar_mutex);
return result;
}
EXPORT_SYMBOL(register_rar);
/*
* This function registers the driver with the device subsystem (
* either PCI, USB, etc).
* Function that is activaed on the succesful probe of the RAR device
* (Moorestown host controller).
*/
static int rar_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
int error;
int counter;
DEBUG_PRINT_0(RAR_DEBUG_LEVEL_EXTENDED, dev_dbg(&dev->dev, "PCI probe starting\n");
"Rar pci probe starting\n");
error = 0;
/* enable the device */ /* enable the device */
error = pci_enable_device(pdev); error = pci_enable_device(dev);
if (error) { if (error) {
DEBUG_PRINT_0(RAR_DEBUG_LEVEL_EXTENDED, dev_err(&dev->dev,
"error enabling pci device\n"); "Error enabling RAR register PCI device\n");
goto end_function; goto end_function;
} }
rar_dev = pdev; /* we have only one device; fill in the rar_device structure */
registered = 1; my_rar_device.rar_dev = dev;
/* Initialize the RAR parameters, which have to be retrieved */
/* via the message bus service */
error = memrar_init_rar_params(rar_dev);
/*
* Initialize the RAR parameters, which have to be retrieved
* via the message bus interface.
*/
error = init_rar_params(dev);
if (error) { if (error) {
DEBUG_PRINT_0(RAR_DEBUG_LEVEL_EXTENDED, pci_disable_device(dev);
"error getting RAR addresses device\n");
registered = 0; dev_err(&dev->dev,
"Error retrieving RAR addresses\n");
goto end_function; goto end_function;
} }
dev_dbg(&dev->dev, "PCI probe locking\n");
mutex_lock(&rar_mutex);
my_rar_device.registered = 1;
/* now call anyone who has registered (using callbacks) */
for (counter = 0; counter < num_clients; counter += 1) {
if (clients[counter].client_callback) {
error = (*clients[counter].client_callback)(
clients[counter].customer_data);
/* set callback to NULL to indicate it has been done */
clients[counter].client_callback = NULL;
dev_dbg(&my_rar_device.rar_dev->dev,
"Callback called for %d\n",
counter);
}
}
dev_dbg(&dev->dev, "PCI probe unlocking\n");
mutex_unlock(&rar_mutex);
end_function: end_function:
return error; return error;
} }
/* const struct pci_device_id rar_pci_id_tbl[] = {
this function registers the driver to { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_RAR_DEVICE_ID) },
the device subsystem (either PCI, USB, etc) { 0 }
*/ };
MODULE_DEVICE_TABLE(pci, rar_pci_id_tbl);
const struct pci_device_id *my_id_table = rar_pci_id_tbl;
/* field for registering driver to PCI device */
static struct pci_driver rar_pci_driver = {
.name = "rar_register_driver",
.id_table = rar_pci_id_tbl,
.probe = rar_probe
};
static int __init rar_init_handler(void) static int __init rar_init_handler(void)
{ {
return pci_register_driver(&rar_pci_driver); return pci_register_driver(&rar_pci_driver);
...@@ -382,59 +599,4 @@ module_init(rar_init_handler); ...@@ -382,59 +599,4 @@ module_init(rar_init_handler);
module_exit(rar_exit_handler); module_exit(rar_exit_handler);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel Restricted Access Region Register Driver");
/* The get_rar_address function is used by other device drivers
* to obtain RAR address information on a RAR. It takes two
* parameter:
*
* int rar_index
* The rar_index is an index to the rar for which you wish to retrieve
* the address information.
* Values can be 0,1, or 2.
*
* struct RAR_address_struct is a pointer to a place to which the function
* can return the address structure for the RAR.
*
* The function returns a 0 upon success or a -1 if there is no RAR
* facility on this system.
*/
int get_rar_address(int rar_index, struct RAR_address_struct *addresses)
{
if (registered && (rar_index < 3) && (rar_index >= 0)) {
*addresses = rar_addr[rar_index];
/* strip off lock bit information */
addresses->low = addresses->low & 0xfffffff0;
addresses->high = addresses->high & 0xfffffff0;
return 0;
} else
return -ENODEV;
}
EXPORT_SYMBOL(get_rar_address);
/* The lock_rar function is used by other device drivers to lock an RAR.
* once an RAR is locked, it stays locked until the next system reboot.
* The function takes one parameter:
*
* int rar_index
* The rar_index is an index to the rar that you want to lock.
* Values can be 0,1, or 2.
*
* The function returns a 0 upon success or a -1 if there is no RAR
* facility on this system.
*/
int lock_rar(int rar_index)
{
u32 working_addr;
int result;
if (registered && (rar_index < 3) && (rar_index >= 0)) {
/* first make sure that lock bits are clear (this does lock) */
working_addr = rar_addr[rar_index].low & 0xfffffff0;
/* now send that value to the register using the IPC */
result = memrar_set_rar_addr(rar_dev, rar_index, working_addr);
return result;
} else
return -ENODEV;
}
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