Commit 657dc0a0 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] USB: EHCI power management updates

This patch updates EHCI suspend/resume so that its essential
components work on a few different implementations:

   - make root hub suspend/resume work
   - make remote wakeup work (given CONFIG_USB_SUSPEND patch)
   - separate root hub suspend/resume from PCI suspend/resume
   - say if controller supports remote wakeup (on this system)
   - sysfs register dump unavailable if controller is suspended

Plus a handful of minor cleanups.  Please merge, along with the
"hcd-0506.patch" I sent last week.

Tested by modifying sysfs power/state files, since ACPI doesn't
work on this system (so I can't test system suspend/resume):

  - For root hub(*) ... suspend/resume works, also remote wakeup
  - PCI controller ... suspend/resume works, remote wakeup
    signals PME# (according to "lspci -vv"), but that's ignored
    on my test sytem

Regardless of whether USB was active, "echo 1 > /proc/acpi/sleep"
produced a system that wouldn't resume, and the same result
came from "echo standby > /sys/power/state".  So that's about
as far as I can take this testing for now.

- Dave

(*) Doing this relies on the CONFIG_USB_SUSPEND patch.  Otherwise
     no USB devices respond to sysfs power/state updates.  The
     PCI suspend/resume is a superset of this.
parent 23601558
......@@ -170,6 +170,20 @@ dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd)
itd->index[6], itd->index[7]);
}
static void __attribute__((__unused__))
dbg_sitd (const char *label, struct ehci_hcd *ehci, struct ehci_sitd *sitd)
{
ehci_dbg (ehci, "%s [%d] sitd %p, next %08x, urb %p\n",
label, sitd->frame, sitd, le32_to_cpu(sitd->hw_next), sitd->urb);
ehci_dbg (ehci,
" addr %08x sched %04x result %08x buf %08x %08x\n",
le32_to_cpu(sitd->hw_fullspeed_ep),
le32_to_cpu(sitd->hw_uframe),
le32_to_cpu(sitd->hw_results),
le32_to_cpu(sitd->hw_buf [0]),
le32_to_cpu(sitd->hw_buf [1]));
}
static int __attribute__((__unused__))
dbg_status_buf (char *buf, unsigned len, char *label, u32 status)
{
......@@ -625,11 +639,20 @@ show_registers (struct class_device *class_dev, char *buf)
spin_lock_irqsave (&ehci->lock, flags);
if (bus->controller->power.power_state) {
size = scnprintf (next, size,
"bus %s, device %s (driver " DRIVER_VERSION ")\n"
"SUSPENDED (no register access)\n",
hcd->self.controller->bus->name,
hcd->self.controller->bus_id);
goto done;
}
/* Capability Registers */
i = HC_VERSION(readl (&ehci->caps->hc_capbase));
temp = scnprintf (next, size,
"bus %s device %s\n"
"EHCI %x.%02x, hcd state %d (driver " DRIVER_VERSION ")\n",
"bus %s, device %s (driver " DRIVER_VERSION ")\n"
"EHCI %x.%02x, hcd state %d\n",
hcd->self.controller->bus->name,
hcd->self.controller->bus_id,
i >> 8, i & 0x0ff, ehci->hcd.state);
......@@ -672,7 +695,7 @@ show_registers (struct class_device *class_dev, char *buf)
next += temp;
for (i = 0; i < HCS_N_PORTS (ehci->hcs_params); i++) {
temp = dbg_port_buf (scratch, sizeof scratch, label, i,
temp = dbg_port_buf (scratch, sizeof scratch, label, i + 1,
readl (&ehci->regs->port_status [i]));
temp = scnprintf (next, size, fmt, temp, scratch);
size -= temp;
......@@ -701,6 +724,7 @@ show_registers (struct class_device *class_dev, char *buf)
next += temp;
#endif
done:
spin_unlock_irqrestore (&ehci->lock, flags);
return PAGE_SIZE - size;
......
/*
* Copyright (c) 2000-2002 by David Brownell
* Copyright (c) 2000-2004 by David Brownell
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
......@@ -69,6 +69,7 @@
*
* HISTORY:
*
* 2004-05-10 Root hub and PCI suspend/resume support; remote wakeup. (db)
* 2004-02-24 Replace pci_* with generic dma_* API calls (dsaxena@plexity.net)
* 2003-12-29 Rewritten high speed iso transfer support (by Michal Sojka,
* <sojkam@centrum.cz>, updates by DB).
......@@ -96,7 +97,7 @@
* 2001-June Works with usb-storage and NEC EHCI on 2.4
*/
#define DRIVER_VERSION "2003-Dec-29"
#define DRIVER_VERSION "2004-May-10"
#define DRIVER_AUTHOR "David Brownell"
#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
......@@ -128,7 +129,7 @@ static int log2_irq_thresh = 0; // 0 to 6
module_param (log2_irq_thresh, int, S_IRUGO);
MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes");
#define INTR_MASK (STS_IAA | STS_FATAL | STS_ERR | STS_INT)
#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT)
/*-------------------------------------------------------------------------*/
......@@ -201,6 +202,7 @@ static int ehci_reset (struct ehci_hcd *ehci)
dbg_cmd (ehci, "reset", command);
writel (command, &ehci->regs->command);
ehci->hcd.state = USB_STATE_HALT;
ehci->next_statechange = jiffies;
return handshake (&ehci->regs->command, CMD_RESET, 0, 250 * 1000);
}
......@@ -241,6 +243,8 @@ static void ehci_ready (struct ehci_hcd *ehci)
/*-------------------------------------------------------------------------*/
static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs);
#include "ehci-hub.c"
#include "ehci-mem.c"
#include "ehci-q.c"
......@@ -248,8 +252,6 @@ static void ehci_ready (struct ehci_hcd *ehci)
/*-------------------------------------------------------------------------*/
static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs);
static void ehci_watchdog (unsigned long param)
{
struct ehci_hcd *ehci = (struct ehci_hcd *) param;
......@@ -428,12 +430,17 @@ static int ehci_start (struct usb_hcd *hcd)
#ifdef CONFIG_PCI
if (hcd->self.controller->bus == &pci_bus_type) {
struct pci_dev *pdev;
u16 port_wake;
pdev = to_pci_dev(hcd->self.controller);
/* Serial Bus Release Number is at PCI 0x60 offset */
pci_read_config_byte(pdev, 0x60, &sbrn);
/* port wake capability, reported by boot firmware */
pci_read_config_word(pdev, 0x62, &port_wake);
hcd->can_wakeup = (port_wake & 1) != 0;
/* help hc dma work well with cachelines */
pci_set_mwi (pdev);
......@@ -615,41 +622,26 @@ static int ehci_get_frame (struct usb_hcd *hcd)
/* suspend/resume, section 4.3 */
/* These routines rely on PCI to handle powerdown and wakeup, and
* transceivers that don't need any software attention to set up
* the right sort of wakeup.
*/
static int ehci_suspend (struct usb_hcd *hcd, u32 state)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
int ports;
int i;
ehci_dbg (ehci, "suspend to %d\n", state);
ports = HCS_N_PORTS (ehci->hcs_params);
// FIXME: This assumes what's probably a D3 level suspend...
while (time_before (jiffies, ehci->next_statechange))
msec_delay (100);
// FIXME: usb wakeup events on this bus should resume the machine.
// pci config register PORTWAKECAP controls which ports can do it;
// bios may have initted the register...
/* suspend each port, then stop the hc */
for (i = 0; i < ports; i++) {
int temp = readl (&ehci->regs->port_status [i]);
if ((temp & PORT_PE) == 0
|| (temp & PORT_OWNER) != 0)
continue;
ehci_dbg (ehci, "suspend port %d", i);
temp |= PORT_SUSPEND;
writel (temp, &ehci->regs->port_status [i]);
}
if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci);
writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command);
// save pci FLADJ value
#ifdef CONFIG_USB_SUSPEND
(void) usb_suspend_device (hcd->self.root_hub);
#else
/* FIXME lock root hub */
(void) ehci_hub_suspend (hcd);
#endif
/* who tells PCI to reduce power consumption? */
// save (PCI) FLADJ in case of Vaux power loss
return 0;
}
......@@ -657,40 +649,22 @@ static int ehci_suspend (struct usb_hcd *hcd, u32 state)
static int ehci_resume (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
int ports;
int i;
ehci_dbg (ehci, "resume\n");
ports = HCS_N_PORTS (ehci->hcs_params);
// FIXME: if controller didn't retain state,
// return and let generic code clean it up
// test configured_flag ?
/* resume HC and each port */
// restore pci FLADJ value
// khubd and drivers will set HC running, if needed;
hcd->state = USB_STATE_RUNNING;
// FIXME Philips/Intel/... etc don't really have a "READY"
// state ... turn on CMD_RUN too
for (i = 0; i < ports; i++) {
int temp = readl (&ehci->regs->port_status [i]);
if ((temp & PORT_PE) == 0
|| (temp & PORT_SUSPEND) != 0)
continue;
ehci_dbg (ehci, "resume port %d", i);
temp |= PORT_RESUME;
writel (temp, &ehci->regs->port_status [i]);
readl (&ehci->regs->command); /* unblock posted writes */
wait_ms (20);
temp &= ~PORT_RESUME;
writel (temp, &ehci->regs->port_status [i]);
}
readl (&ehci->regs->command); /* unblock posted writes */
return 0;
int retval;
// maybe restore (PCI) FLADJ
while (time_before (jiffies, ehci->next_statechange))
msec_delay (100);
#ifdef CONFIG_USB_SUSPEND
retval = usb_resume_device (hcd->self.root_hub);
#else
/* FIXME lock root hub */
retval = ehci_hub_resume (hcd);
#endif
if (retval == 0)
hcd->self.controller->power.power_state = 0;
return retval;
}
#endif
......@@ -752,7 +726,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
bh = 0;
#ifdef EHCI_VERBOSE_DEBUG
/* unrequested/ignored: Port Change Detect, Frame List Rollover */
/* unrequested/ignored: Frame List Rollover */
dbg_status (ehci, "irq", status);
#endif
......@@ -774,6 +748,34 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
bh = 1;
}
/* remote wakeup [4.3.1] */
if ((status & STS_PCD) && ehci->hcd.remote_wakeup) {
unsigned i = HCS_N_PORTS (ehci->hcs_params);
/* resume root hub? */
status = readl (&ehci->regs->command);
if (!(status & CMD_RUN))
writel (status | CMD_RUN, &ehci->regs->command);
while (i--) {
status = readl (&ehci->regs->port_status [i]);
if (status & PORT_OWNER)
continue;
if (!(status & PORT_RESUME)
|| ehci->reset_done [i] != 0)
continue;
/* start 20 msec resume signaling from this port,
* and make khubd collect PORT_STAT_C_SUSPEND to
* stop that signaling.
*/
ehci->reset_done [i] = jiffies + MSEC_TO_JIFFIES (20);
mod_timer (&ehci->hcd.rh_timer,
ehci->reset_done [i] + 1);
ehci_dbg (ehci, "port %d remote wakeup\n", i + 1);
}
}
/* PCI errors [4.15.2.4] */
if (unlikely ((status & STS_FATAL) != 0)) {
ehci_err (ehci, "fatal error\n");
......@@ -814,7 +816,6 @@ static int ehci_urb_enqueue (
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
struct list_head qtd_list;
urb->transfer_flags &= ~EHCI_STATE_UNLINK;
INIT_LIST_HEAD (&qtd_list);
switch (usb_pipetype (urb->pipe)) {
......@@ -914,7 +915,6 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
// wait till next completion, do it then.
// completion irqs can wait up to 1024 msec,
urb->transfer_flags |= EHCI_STATE_UNLINK;
break;
}
spin_unlock_irqrestore (&ehci->lock, flags);
......
......@@ -28,6 +28,131 @@
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PM
static int ehci_hub_suspend (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
struct usb_device *root = hcd_to_bus (&ehci->hcd)->root_hub;
int port;
int status = 0;
if (root->dev.power.power_state != 0)
return 0;
if (time_before (jiffies, ehci->next_statechange))
return -EAGAIN;
port = HCS_N_PORTS (ehci->hcs_params);
spin_lock_irq (&ehci->lock);
/* suspend any active/unsuspended ports, maybe allow wakeup */
while (port--) {
u32 t1 = readl (&ehci->regs->port_status [port]);
u32 t2 = t1;
if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
t2 |= PORT_SUSPEND;
if (ehci->hcd.remote_wakeup)
t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E;
else
t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
if (t1 != t2) {
ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
port + 1, t1, t2);
writel (t2, &ehci->regs->port_status [port]);
}
}
/* stop schedules, then turn off HC and clean any completed work */
if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci);
ehci->command = readl (&ehci->regs->command);
writel (ehci->command & ~CMD_RUN, &ehci->regs->command);
if (ehci->reclaim)
ehci->reclaim_ready = 1;
ehci_work (ehci, 0);
(void) handshake (&ehci->regs->status, STS_HALT, STS_HALT, 2000);
root->dev.power.power_state = 3;
ehci->next_statechange = jiffies + MSEC_TO_JIFFIES(10);
spin_unlock_irq (&ehci->lock);
return status;
}
/* caller owns root->serialize, and should reset/reinit on error */
static int ehci_hub_resume (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
struct usb_device *root = hcd_to_bus (&ehci->hcd)->root_hub;
u32 temp;
int i;
if (!root->dev.power.power_state)
return 0;
if (time_before (jiffies, ehci->next_statechange))
return -EAGAIN;
/* re-init operational registers in case we lost power */
if (readl (&ehci->regs->intr_enable) == 0) {
writel (INTR_MASK, &ehci->regs->intr_enable);
writel (0, &ehci->regs->segment);
writel (ehci->periodic_dma, &ehci->regs->frame_list);
writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
/* FIXME will this work even (pci) vAUX was lost? */
}
/* restore CMD_RUN, framelist size, and irq threshold */
writel (ehci->command, &ehci->regs->command);
/* take ports out of suspend */
i = HCS_N_PORTS (ehci->hcs_params);
while (i--) {
temp = readl (&ehci->regs->port_status [i]);
temp &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
if (temp & PORT_SUSPEND) {
ehci->reset_done [i] = jiffies + MSEC_TO_JIFFIES (20);
temp |= PORT_RESUME;
}
writel (temp, &ehci->regs->port_status [i]);
}
i = HCS_N_PORTS (ehci->hcs_params);
wait_ms (20);
while (i--) {
temp = readl (&ehci->regs->port_status [i]);
if ((temp & PORT_SUSPEND) == 0)
continue;
temp &= ~PORT_RESUME;
writel (temp, &ehci->regs->port_status [i]);
ehci_vdbg (ehci, "resumed port %d\n", i + 1);
}
(void) readl (&ehci->regs->command);
/* maybe re-activate the schedule(s) */
temp = 0;
if (ehci->async->qh_next.qh)
temp |= CMD_ASE;
if (ehci->periodic_sched)
temp |= CMD_PSE;
if (temp)
writel (ehci->command | temp, &ehci->regs->command);
root->dev.power.power_state = 0;
ehci->next_statechange = jiffies + MSEC_TO_JIFFIES(5);
ehci->hcd.state = USB_STATE_RUNNING;
return 0;
}
#else
#define ehci_hub_suspend 0
#define ehci_hub_resume 0
#endif /* CONFIG_PM */
/*-------------------------------------------------------------------------*/
static int check_reset_complete (
struct ehci_hcd *ehci,
int index,
......@@ -99,7 +224,11 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
}
if (!(temp & PORT_CONNECT))
ehci->reset_done [i] = 0;
if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) {
if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0
// PORT_STAT_C_SUSPEND?
|| ((temp & PORT_RESUME) != 0
&& time_after (jiffies,
ehci->reset_done [i]))) {
if (i < 7)
buf [0] |= 1 << (i + 1);
else
......@@ -143,6 +272,8 @@ ehci_hub_descriptor (
/*-------------------------------------------------------------------------*/
#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
static int ehci_hub_control (
struct usb_hcd *hcd,
u16 typeReq,
......@@ -194,8 +325,20 @@ static int ehci_hub_control (
&ehci->regs->port_status [wIndex]);
break;
case USB_PORT_FEAT_SUSPEND:
if (temp & PORT_RESET)
goto error;
if (temp & PORT_SUSPEND) {
if ((temp & PORT_PE) == 0)
goto error;
/* resume signaling for 20 msec */
writel ((temp & ~PORT_WAKE_BITS) | PORT_RESUME,
&ehci->regs->port_status [wIndex]);
ehci->reset_done [wIndex] = jiffies
+ MSEC_TO_JIFFIES (20);
}
break;
case USB_PORT_FEAT_C_SUSPEND:
/* ? */
/* we auto-clear this feature */
break;
case USB_PORT_FEAT_POWER:
if (HCS_PPC (ehci->hcs_params))
......@@ -239,15 +382,37 @@ static int ehci_hub_control (
status |= 1 << USB_PORT_FEAT_C_CONNECTION;
if (temp & PORT_PEC)
status |= 1 << USB_PORT_FEAT_C_ENABLE;
// USB_PORT_FEAT_C_SUSPEND
if (temp & PORT_OCC)
status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
/* whoever resumes must GetPortStatus to complete it!! */
if ((temp & PORT_RESUME)
&& time_after (jiffies,
ehci->reset_done [wIndex])) {
status |= 1 << USB_PORT_FEAT_C_SUSPEND;
ehci->reset_done [wIndex] = 0;
/* stop resume signaling */
temp = readl (&ehci->regs->port_status [wIndex]);
writel (temp & ~PORT_RESUME,
&ehci->regs->port_status [wIndex]);
retval = handshake (
&ehci->regs->port_status [wIndex],
PORT_RESUME, 0, 2000 /* 2msec */);
if (retval != 0) {
ehci_err (ehci, "port %d resume error %d\n",
wIndex + 1, retval);
goto error;
}
temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
}
/* whoever resets must GetPortStatus to complete it!! */
if ((temp & PORT_RESET)
&& time_after (jiffies,
ehci->reset_done [wIndex])) {
status |= 1 << USB_PORT_FEAT_C_RESET;
ehci->reset_done [wIndex] = 0;
/* force reset to complete */
writel (temp & ~PORT_RESET,
......@@ -275,7 +440,7 @@ static int ehci_hub_control (
}
if (temp & PORT_PE)
status |= 1 << USB_PORT_FEAT_ENABLE;
if (temp & PORT_SUSPEND)
if (temp & (PORT_SUSPEND|PORT_RESUME))
status |= 1 << USB_PORT_FEAT_SUSPEND;
if (temp & PORT_OC)
status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
......@@ -312,6 +477,11 @@ static int ehci_hub_control (
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
if ((temp & PORT_PE) == 0
|| (temp & PORT_RESET) != 0)
goto error;
if (ehci->hcd.remote_wakeup)
temp |= PORT_WAKE_BITS;
writel (temp | PORT_SUSPEND,
&ehci->regs->port_status [wIndex]);
break;
......@@ -321,6 +491,8 @@ static int ehci_hub_control (
&ehci->regs->port_status [wIndex]);
break;
case USB_PORT_FEAT_RESET:
if (temp & PORT_RESUME)
goto error;
/* line status bits may report this as low speed,
* which can be fine if this root hub has a
* transaction translator built in.
......@@ -342,7 +514,7 @@ static int ehci_hub_control (
* usb 2.0 spec says 50 ms resets on root
*/
ehci->reset_done [wIndex] = jiffies
+ ((50 /* msec */ * HZ) / 1000);
+ MSEC_TO_JIFFIES (50);
}
writel (temp, &ehci->regs->port_status [wIndex]);
break;
......
......@@ -84,6 +84,8 @@ struct ehci_hcd { /* one per controller */
struct notifier_block reboot_notifier;
unsigned long actions;
unsigned stamp;
unsigned long next_statechange;
u32 command;
unsigned is_arc_rh_tt:1; /* ARC roothub with TT */
......@@ -99,8 +101,6 @@ struct ehci_hcd { /* one per controller */
/* unwrap an HCD pointer to get an EHCI_HCD pointer */
#define hcd_to_ehci(hcd_ptr) container_of(hcd_ptr, struct ehci_hcd, hcd)
/* NOTE: urb->transfer_flags expected to not use this bit !!! */
#define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */
enum ehci_timer_action {
TIMER_IO_WATCHDOG,
......@@ -221,7 +221,7 @@ struct ehci_regs {
u32 segment; /* address bits 63:32 if needed */
/* PERIODICLISTBASE: offset 0x14 */
u32 frame_list; /* points to periodic list */
/* ASYNCICLISTADDR: offset 0x18 */
/* ASYNCLISTADDR: offset 0x18 */
u32 async_next; /* address of next async queue head */
u32 reserved [9];
......@@ -237,7 +237,10 @@ struct ehci_regs {
#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */
#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */
/* 19:16 for port testing */
/* 15:14 for using port indicator leds (if HCS_INDICATOR allows) */
#define PORT_LED_OFF (0<<14)
#define PORT_LED_AMBER (1<<14)
#define PORT_LED_GREEN (2<<14)
#define PORT_LED_MASK (3<<14)
#define PORT_OWNER (1<<13) /* true: companion hc owns this port */
#define PORT_POWER (1<<12) /* true: has power (see PPC) */
#define PORT_USB11(x) (((x)&(3<<10))==(1<<10)) /* USB 1.1 device */
......@@ -593,6 +596,14 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
/*-------------------------------------------------------------------------*/
#define MSEC_TO_JIFFIES(msec) ((HZ * (msec) + 999) / 1000)
static inline void msec_delay(int msec)
{
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(MSEC_TO_JIFFIES(msec));
}
#ifndef DEBUG
#define STUB_DEBUG_FILES
#endif /* DEBUG */
......
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