Commit f7cca14b authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'rpmsg-v4.18' of git://github.com/andersson/remoteproc

Pull rpmsg updates from Bjorn Andersson:
 "This migrates rpmsg to use SPDX license headers and fixes a
  use-after-free in SMD"

* tag 'rpmsg-v4.18' of git://github.com/andersson/remoteproc:
  rpmsg: smd: do not use mananged resources for endpoints and channels
  rpmsg: char: Switch to SPDX license identifier
  rpmsg: glink: Switch to SPDX license identifier
  rpmsg: smd: Switch to SPDX license identifier
  rpmsg: virtio_rpmsg_bus: Switch to SPDX license identifier
  rpmsg: Switch to SPDX license identifier
  rpmsg: qcom_smd: Access APCS through mailbox framework
  rpmsg: Add driver_override device attribute for rpmsg_device
parents b70c9d37 4a2e84c6
...@@ -73,3 +73,23 @@ Description: ...@@ -73,3 +73,23 @@ Description:
This sysfs entry tells us whether the channel is a local This sysfs entry tells us whether the channel is a local
server channel that is announced (values are either server channel that is announced (values are either
true or false). true or false).
What: /sys/bus/rpmsg/devices/.../driver_override
Date: April 2018
KernelVersion: 4.18
Contact: Bjorn Andersson <bjorn.andersson@linaro.org>
Description:
Every rpmsg device is a communication channel with a remote
processor. Channels are identified by a textual name (see
/sys/bus/rpmsg/devices/.../name above) and have a local
("source") rpmsg address, and remote ("destination") rpmsg
address.
The listening entity (or client) which communicates with a
remote processor is referred as rpmsg driver. The rpmsg device
and rpmsg driver are matched based on rpmsg device name and
rpmsg driver ID table.
This sysfs entry allows the rpmsg driver for a rpmsg device
to be specified which will override standard OF, ID table
and name matching.
...@@ -22,9 +22,15 @@ The edge is described by the following properties: ...@@ -22,9 +22,15 @@ The edge is described by the following properties:
Definition: should specify the IRQ used by the remote processor to Definition: should specify the IRQ used by the remote processor to
signal this processor about communication related updates signal this processor about communication related updates
- qcom,ipc: - mboxes:
Usage: required Usage: required
Value type: <prop-encoded-array> Value type: <prop-encoded-array>
Definition: reference to the associated doorbell in APCS, as described
in mailbox/mailbox.txt
- qcom,ipc:
Usage: required, unless mboxes is specified
Value type: <prop-encoded-array>
Definition: three entries specifying the outgoing ipc bit used for Definition: three entries specifying the outgoing ipc bit used for
signaling the remote processor: signaling the remote processor:
- phandle to a syscon node representing the apcs registers - phandle to a syscon node representing the apcs registers
......
# SPDX-License-Identifier: GPL-2.0
menu "Rpmsg drivers" menu "Rpmsg drivers"
# RPMSG always gets selected by whoever wants it # RPMSG always gets selected by whoever wants it
...@@ -39,6 +41,7 @@ config RPMSG_QCOM_GLINK_SMEM ...@@ -39,6 +41,7 @@ config RPMSG_QCOM_GLINK_SMEM
config RPMSG_QCOM_SMD config RPMSG_QCOM_SMD
tristate "Qualcomm Shared Memory Driver (SMD)" tristate "Qualcomm Shared Memory Driver (SMD)"
depends on MAILBOX
depends on QCOM_SMEM depends on QCOM_SMEM
select RPMSG select RPMSG
help help
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* Copyright (c) 2016-2017, Linaro Ltd * Copyright (c) 2016-2017, Linaro Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/idr.h> #include <linux/idr.h>
......
/* SPDX-License-Identifier: GPL-2.0 */
/* /*
* Copyright (c) 2016-2017, Linaro Ltd * Copyright (c) 2016-2017, Linaro Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#ifndef __QCOM_GLINK_NATIVE_H__ #ifndef __QCOM_GLINK_NATIVE_H__
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* Copyright (c) 2016-2017, Linaro Ltd * Copyright (c) 2016-2017, Linaro Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/idr.h> #include <linux/idr.h>
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* Copyright (c) 2016, Linaro Ltd * Copyright (c) 2016, Linaro Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/io.h> #include <linux/io.h>
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* Copyright (c) 2015, Sony Mobile Communications AB. * Copyright (c) 2015, Sony Mobile Communications AB.
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/mfd/syscon.h> #include <linux/mfd/syscon.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_irq.h> #include <linux/of_irq.h>
...@@ -107,6 +100,8 @@ static const struct { ...@@ -107,6 +100,8 @@ static const struct {
* @ipc_regmap: regmap handle holding the outgoing ipc register * @ipc_regmap: regmap handle holding the outgoing ipc register
* @ipc_offset: offset within @ipc_regmap of the register for ipc * @ipc_offset: offset within @ipc_regmap of the register for ipc
* @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap * @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap
* @mbox_client: mailbox client handle
* @mbox_chan: apcs ipc mailbox channel handle
* @channels: list of all channels detected on this edge * @channels: list of all channels detected on this edge
* @channels_lock: guard for modifications of @channels * @channels_lock: guard for modifications of @channels
* @allocated: array of bitmaps representing already allocated channels * @allocated: array of bitmaps representing already allocated channels
...@@ -129,6 +124,9 @@ struct qcom_smd_edge { ...@@ -129,6 +124,9 @@ struct qcom_smd_edge {
int ipc_offset; int ipc_offset;
int ipc_bit; int ipc_bit;
struct mbox_client mbox_client;
struct mbox_chan *mbox_chan;
struct list_head channels; struct list_head channels;
spinlock_t channels_lock; spinlock_t channels_lock;
...@@ -366,7 +364,17 @@ static void qcom_smd_signal_channel(struct qcom_smd_channel *channel) ...@@ -366,7 +364,17 @@ static void qcom_smd_signal_channel(struct qcom_smd_channel *channel)
{ {
struct qcom_smd_edge *edge = channel->edge; struct qcom_smd_edge *edge = channel->edge;
if (edge->mbox_chan) {
/*
* We can ignore a failing mbox_send_message() as the only
* possible cause is that the FIFO in the framework is full of
* other writes to the same bit.
*/
mbox_send_message(edge->mbox_chan, NULL);
mbox_client_txdone(edge->mbox_chan, 0);
} else {
regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit));
}
} }
/* /*
...@@ -1100,12 +1108,12 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed ...@@ -1100,12 +1108,12 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed
void *info; void *info;
int ret; int ret;
channel = devm_kzalloc(&edge->dev, sizeof(*channel), GFP_KERNEL); channel = kzalloc(sizeof(*channel), GFP_KERNEL);
if (!channel) if (!channel)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
channel->edge = edge; channel->edge = edge;
channel->name = devm_kstrdup(&edge->dev, name, GFP_KERNEL); channel->name = kstrdup(name, GFP_KERNEL);
if (!channel->name) if (!channel->name)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -1156,8 +1164,8 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed ...@@ -1156,8 +1164,8 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed
return channel; return channel;
free_name_and_channel: free_name_and_channel:
devm_kfree(&edge->dev, channel->name); kfree(channel->name);
devm_kfree(&edge->dev, channel); kfree(channel);
return ERR_PTR(ret); return ERR_PTR(ret);
} }
...@@ -1326,6 +1334,15 @@ static int qcom_smd_parse_edge(struct device *dev, ...@@ -1326,6 +1334,15 @@ static int qcom_smd_parse_edge(struct device *dev,
key = "qcom,remote-pid"; key = "qcom,remote-pid";
of_property_read_u32(node, key, &edge->remote_pid); of_property_read_u32(node, key, &edge->remote_pid);
edge->mbox_client.dev = dev;
edge->mbox_client.knows_txdone = true;
edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0);
if (IS_ERR(edge->mbox_chan)) {
if (PTR_ERR(edge->mbox_chan) != -ENODEV)
return PTR_ERR(edge->mbox_chan);
edge->mbox_chan = NULL;
syscon_np = of_parse_phandle(node, "qcom,ipc", 0); syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
if (!syscon_np) { if (!syscon_np) {
dev_err(dev, "no qcom,ipc node\n"); dev_err(dev, "no qcom,ipc node\n");
...@@ -1348,6 +1365,7 @@ static int qcom_smd_parse_edge(struct device *dev, ...@@ -1348,6 +1365,7 @@ static int qcom_smd_parse_edge(struct device *dev,
dev_err(dev, "no bit in %s\n", key); dev_err(dev, "no bit in %s\n", key);
return -EINVAL; return -EINVAL;
} }
}
ret = of_property_read_string(node, "label", &edge->name); ret = of_property_read_string(node, "label", &edge->name);
if (ret < 0) if (ret < 0)
...@@ -1378,13 +1396,13 @@ static int qcom_smd_parse_edge(struct device *dev, ...@@ -1378,13 +1396,13 @@ static int qcom_smd_parse_edge(struct device *dev,
*/ */
static void qcom_smd_edge_release(struct device *dev) static void qcom_smd_edge_release(struct device *dev)
{ {
struct qcom_smd_channel *channel; struct qcom_smd_channel *channel, *tmp;
struct qcom_smd_edge *edge = to_smd_edge(dev); struct qcom_smd_edge *edge = to_smd_edge(dev);
list_for_each_entry(channel, &edge->channels, list) { list_for_each_entry_safe(channel, tmp, &edge->channels, list) {
SET_RX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); list_del(&channel->list);
SET_RX_CHANNEL_INFO(channel, head, 0); kfree(channel->name);
SET_RX_CHANNEL_INFO(channel, tail, 0); kfree(channel);
} }
kfree(edge); kfree(edge);
...@@ -1453,6 +1471,9 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, ...@@ -1453,6 +1471,9 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
return edge; return edge;
unregister_dev: unregister_dev:
if (!IS_ERR_OR_NULL(edge->mbox_chan))
mbox_free_channel(edge->mbox_chan);
device_unregister(&edge->dev); device_unregister(&edge->dev);
return ERR_PTR(ret); return ERR_PTR(ret);
} }
...@@ -1481,6 +1502,7 @@ int qcom_smd_unregister_edge(struct qcom_smd_edge *edge) ...@@ -1481,6 +1502,7 @@ int qcom_smd_unregister_edge(struct qcom_smd_edge *edge)
if (ret) if (ret)
dev_warn(&edge->dev, "can't remove smd device: %d\n", ret); dev_warn(&edge->dev, "can't remove smd device: %d\n", ret);
mbox_free_channel(edge->mbox_chan);
device_unregister(&edge->dev); device_unregister(&edge->dev);
return 0; return 0;
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* Copyright (c) 2016, Linaro Ltd. * Copyright (c) 2016, Linaro Ltd.
* Copyright (c) 2012, Michal Simek <monstr@monstr.eu> * Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
...@@ -7,15 +8,6 @@ ...@@ -7,15 +8,6 @@
* *
* Based on rpmsg performance statistics driver by Michal Simek, which in turn * Based on rpmsg performance statistics driver by Michal Simek, which in turn
* was based on TI & Google OMX rpmsg driver. * was based on TI & Google OMX rpmsg driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/cdev.h> #include <linux/cdev.h>
#include <linux/device.h> #include <linux/device.h>
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* remote processor messaging bus * remote processor messaging bus
* *
...@@ -6,15 +7,6 @@ ...@@ -6,15 +7,6 @@
* *
* Ohad Ben-Cohen <ohad@wizery.com> * Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com> * Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#define pr_fmt(fmt) "%s: " fmt, __func__ #define pr_fmt(fmt) "%s: " fmt, __func__
...@@ -333,11 +325,49 @@ field##_show(struct device *dev, \ ...@@ -333,11 +325,49 @@ field##_show(struct device *dev, \
} \ } \
static DEVICE_ATTR_RO(field); static DEVICE_ATTR_RO(field);
#define rpmsg_string_attr(field, member) \
static ssize_t \
field##_store(struct device *dev, struct device_attribute *attr, \
const char *buf, size_t sz) \
{ \
struct rpmsg_device *rpdev = to_rpmsg_device(dev); \
char *new, *old; \
\
new = kstrndup(buf, sz, GFP_KERNEL); \
if (!new) \
return -ENOMEM; \
new[strcspn(new, "\n")] = '\0'; \
\
device_lock(dev); \
old = rpdev->member; \
if (strlen(new)) { \
rpdev->member = new; \
} else { \
kfree(new); \
rpdev->member = NULL; \
} \
device_unlock(dev); \
\
kfree(old); \
\
return sz; \
} \
static ssize_t \
field##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct rpmsg_device *rpdev = to_rpmsg_device(dev); \
\
return sprintf(buf, "%s\n", rpdev->member); \
} \
static DEVICE_ATTR_RW(field)
/* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */ /* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */
rpmsg_show_attr(name, id.name, "%s\n"); rpmsg_show_attr(name, id.name, "%s\n");
rpmsg_show_attr(src, src, "0x%x\n"); rpmsg_show_attr(src, src, "0x%x\n");
rpmsg_show_attr(dst, dst, "0x%x\n"); rpmsg_show_attr(dst, dst, "0x%x\n");
rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n"); rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n");
rpmsg_string_attr(driver_override, driver_override);
static ssize_t modalias_show(struct device *dev, static ssize_t modalias_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
...@@ -359,6 +389,7 @@ static struct attribute *rpmsg_dev_attrs[] = { ...@@ -359,6 +389,7 @@ static struct attribute *rpmsg_dev_attrs[] = {
&dev_attr_dst.attr, &dev_attr_dst.attr,
&dev_attr_src.attr, &dev_attr_src.attr,
&dev_attr_announce.attr, &dev_attr_announce.attr,
&dev_attr_driver_override.attr,
NULL, NULL,
}; };
ATTRIBUTE_GROUPS(rpmsg_dev); ATTRIBUTE_GROUPS(rpmsg_dev);
......
/* SPDX-License-Identifier: GPL-2.0 */
/* /*
* remote processor messaging bus internals * remote processor messaging bus internals
* *
...@@ -6,15 +7,6 @@ ...@@ -6,15 +7,6 @@
* *
* Ohad Ben-Cohen <ohad@wizery.com> * Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com> * Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#ifndef __RPMSG_INTERNAL_H__ #ifndef __RPMSG_INTERNAL_H__
......
// SPDX-License-Identifier: GPL-2.0
/* /*
* Virtio-based remote processor messaging bus * Virtio-based remote processor messaging bus
* *
...@@ -6,15 +7,6 @@ ...@@ -6,15 +7,6 @@
* *
* Ohad Ben-Cohen <ohad@wizery.com> * Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com> * Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#define pr_fmt(fmt) "%s: " fmt, __func__ #define pr_fmt(fmt) "%s: " fmt, __func__
......
/* SPDX-License-Identifier: BSD-3-Clause */
/* /*
* Remote processor messaging * Remote processor messaging
* *
* Copyright (C) 2011 Texas Instruments, Inc. * Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc. * Copyright (C) 2011 Google, Inc.
* All rights reserved. * All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name Texas Instruments nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#ifndef _LINUX_RPMSG_H #ifndef _LINUX_RPMSG_H
......
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_RPMSG_QCOM_GLINK_H #ifndef _LINUX_RPMSG_QCOM_GLINK_H
#define _LINUX_RPMSG_QCOM_GLINK_H #define _LINUX_RPMSG_QCOM_GLINK_H
......
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/* /*
* Copyright (c) 2016, Linaro Ltd. * Copyright (c) 2016, Linaro Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#ifndef _UAPI_RPMSG_H_ #ifndef _UAPI_RPMSG_H_
......
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