Skip to contentSkip to conversation

watchdog: introduce the ARM64 SBSA watchdog driver

PATCH In review Timur Tabi 4 participants 7 messages May 22, 2015 – Jun 10, 2015 Watchdog device drivers

lore
Share & tools
lore

Timur Tabi

author
#0 lore raw

PATCH v4 1/1 watchdog: introduce the ARM64 SBSA watchdog driver

The ARM Server Base System Architecture is a specification for ARM-based server systems. Among other things, it defines the behavior and register interface for a watchdog timer.

---

[v4]
Removed COMPILE_TEST
pm_status is now bool
removed some #includes
use do_div instead
display arch version if unsupported
remove watchdog_set_drvdata

 drivers/watchdog/Kconfig        |   9 ++
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/arm_sbsa_wdt.c | 295 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 305 insertions(+)
 create mode 100644 drivers/watchdog/arm_sbsa_wdt.c
drivers/watchdog/Kconfig +9 -0
index e5e7c55..7720190 100644
@@ -514,6 +514,15 @@ config MEDIATEK_WATCHDOG
 	  To compile this driver as a module, choose M here: the
 	  module will be called mtk_wdt.
 
+config ARM_SBSA_WDT
+	tristate "ARM Server Base System Architecture watchdog"
+	depends on ARM64
+	depends on ARM_ARCH_TIMER
+	select WATCHDOG_CORE
+	help
+	  Say Y here to include watchdog timer support for ARM Server Base
+	  System Architecture (SBSA) systems.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
drivers/watchdog/Makefile +1 -0
index 5c19294..063ab8c 100644
@@ -64,6 +64,7 @@ obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
 obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
 obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
 obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_ARM_SBSA_WDT) += arm_sbsa_wdt.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
drivers/watchdog/arm_sbsa_wdt.c new file +295 -0

295 lines not on this page · raw message

Signature · 3 lines
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project.
#1 lore raw
Quoted 6 lines · Timur Tabi

The ARM Server Base System Architecture is a specification for ARM-based server systems. Among other things, it defines the behavior and register interface for a watchdog timer.

---

My apologies for not getting a chance to think through earlier versions before now.

So, this is meant to be SBSA compliant; is it also meant to be SBBR compliant? I suspect not, since there is no ACPI initialization and SBBR requires both UEFI and ACPI. Is there any reason for not being SBBR compliant? I'm not saying this is good or bad; I'm only trying to understand the reasoning.

I'll also admit that I'm not an expert in ARM timers. Could I ask a really big favor, please? When I read the SBSA (section 5.2, specifically), that implies to me that there are two interrupts: a first interrupt for the timer itself set to go off after the timeout expires, and a second interrupt that is required when the timeout expires to force some "executive action". I only see one IRQ in the patch; what am I missing?

Thanks.

Quoted 348 lines

[v4]
Removed COMPILE_TEST
pm_status is now bool
removed some #includes
use do_div instead
display arch version if unsupported
remove watchdog_set_drvdata

 drivers/watchdog/Kconfig        |   9 ++
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/arm_sbsa_wdt.c | 295 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 305 insertions(+)
 create mode 100644 drivers/watchdog/arm_sbsa_wdt.c
drivers/watchdog/Kconfig +9 -0
index e5e7c55..7720190 100644
@@ -514,6 +514,15 @@ config MEDIATEK_WATCHDOG
 	  To compile this driver as a module, choose M here: the
 	  module will be called mtk_wdt.
 
+config ARM_SBSA_WDT
+	tristate "ARM Server Base System Architecture watchdog"
+	depends on ARM64
+	depends on ARM_ARCH_TIMER
+	select WATCHDOG_CORE
+	help
+	  Say Y here to include watchdog timer support for ARM Server Base
+	  System Architecture (SBSA) systems.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
drivers/watchdog/Makefile +1 -0
index 5c19294..063ab8c 100644
@@ -64,6 +64,7 @@ obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
 obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
 obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
 obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_ARM_SBSA_WDT) += arm_sbsa_wdt.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
drivers/watchdog/arm_sbsa_wdt.c new file +295 -0
new file mode 100644
index 0000000..12ed520e
@@ -0,0 +1,295 @@
+/*
+ * Watchdog driver for SBSA-compliant watchdog timers
+ *
+ * Copyright (c) 2015, 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.
+ *
+ * ARM Server Base System Architecture watchdog driver.
+ *
+ * Register descriptions are taken from the ARM Server Base System
+ * Architecture document (ARM-DEN-0029)
+ */
+
+#define pr_fmt(fmt) "sbsa-gwdt: " fmt
+
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
+
+#include <asm/arch_timer.h>
+
+/* Watchdog Interface Identification Registers */
+struct arm_sbsa_watchdog_ident {
+	__le32 w_iidr;	/* Watchdog Interface Identification Register */
+	uint8_t res2[0xFE8 - 0xFD0];
+	__le32 w_pidr2;	/* Peripheral ID2 Register */
+};
+
+/* Watchdog Refresh Frame */
+struct arm_sbsa_watchdog_refresh {
+	__le32 wrr;		/* Watchdog Refresh Register */
+	uint8_t res1[0xFCC - 0x004];
+	struct arm_sbsa_watchdog_ident ident;
+};
+
+/* Watchdog Control Frame */
+struct arm_sbsa_watchdog_control {
+	__le32 wcs;
+	__le32 res1;
+	__le32 wor;
+	__le32 res2;
+	__le64 wcv;
+	uint8_t res3[0xFCC - 0x018];
+	struct arm_sbsa_watchdog_ident ident;
+};
+
+struct arm_sbsa_watchdog_data {
+	struct watchdog_device wdev;
+	bool pm_status;
+	struct arm_sbsa_watchdog_refresh __iomem *refresh;
+	struct arm_sbsa_watchdog_control __iomem *control;
+};
+
+static int arm_sbsa_wdt_start(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	/* Writing to the control register will also reset the counter */
+	writel(1, &data->control->wcs);
+
+	return 0;
+}
+
+static int arm_sbsa_wdt_stop(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	writel(0, &data->control->wcs);
+
+	return 0;
+}
+
+static int arm_sbsa_wdt_set_timeout(struct watchdog_device *wdev,
+	unsigned int timeout)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	wdev->timeout = timeout;
+	writel(arch_timer_get_cntfrq() * wdev->timeout, &data->control->wor);
+
+	return 0;
+}
+
+static int arm_sbsa_wdt_ping(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	writel(1, &data->refresh->wrr);
+
+	return 0;
+}
+
+static unsigned int arm_sbsa_wdt_status(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	return (readl(&data->control->wcs) & 1) << WDOG_ACTIVE;
+}
+
+static unsigned int arm_sbsa_wdt_timeleft(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+	uint64_t diff = readq(&data->control->wcv) - arch_counter_get_cntvct();
+
+	do_div(diff, arch_timer_get_cntfrq());
+
+	return diff;
+}
+
+static irqreturn_t arm_sbsa_wdt_interrupt(int irq, void *p)
+{
+	/*
+	 * The WS0 interrupt occurs after the first timeout, so we attempt
+	 * a manual reboot.  If this doesn't work, the WS1 timeout will
+	 * cause a hardware reset.
+	 */
+	pr_crit("Initiating system reboot\n");
+	emergency_restart();
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Disable watchdog if it is active during suspend
+ */
+static int __maybe_unused arm_sbsa_wdt_suspend(struct device *dev)
+{
+	struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+	data->pm_status = !!(readl(&data->control->wcs) & 1);
+
+	if (data->pm_status)
+		writel(0, &data->control->wcs);
+
+	return 0;
+}
+
+/*
+ * Enable watchdog and configure it if necessary
+ */
+static int __maybe_unused arm_sbsa_wdt_resume(struct device *dev)
+{
+	struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+	if (data->pm_status)
+		writel(1, &data->control->wcs);
+
+	return 0;
+}
+
+static const struct dev_pm_ops arm_sbsa_wdt_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(arm_sbsa_wdt_suspend, arm_sbsa_wdt_resume)
+};
+
+static struct watchdog_info arm_sbsa_wdt_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity = "ARM SBSA watchdog",
+};
+
+static struct watchdog_ops arm_sbsa_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = arm_sbsa_wdt_start,
+	.stop = arm_sbsa_wdt_stop,
+	.ping = arm_sbsa_wdt_ping,
+	.set_timeout = arm_sbsa_wdt_set_timeout,
+	.status = arm_sbsa_wdt_status,
+	.get_timeleft = arm_sbsa_wdt_timeleft,
+};
+
+static int __init arm_sbsa_wdt_probe(struct platform_device *pdev)
+{
+	struct arm_sbsa_watchdog_data *data;
+	struct resource *res;
+	uint32_t iidr;
+	unsigned int arch;
+	int irq, ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
+	data->control = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->control))
+		return PTR_ERR(data->control);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "refresh");
+	data->refresh = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->refresh))
+		return PTR_ERR(data->refresh);
+
+	/* We only support architecture version 0 */
+	iidr = readl(&data->control->ident.w_iidr);
+	arch = (iidr >> 16) & 0xf;
+	if (arch != 0) {
+		dev_err(&pdev->dev,
+			 "architecture version %u is not supported\n", arch);
+		return -ENODEV;
+	}
+
+	irq = platform_get_irq_byname(pdev, "ws0");
+	if (irq < 0) {
+		dev_err(&pdev->dev, "could not get interrupt\n");
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, arm_sbsa_wdt_interrupt,
+			       0, pdev->name, NULL);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "could not request irq %i (ret=%i)\n",
+			irq, ret);
+		return ret;
+	}
+
+	data->wdev.info = &arm_sbsa_wdt_info;
+	data->wdev.ops = &arm_sbsa_wdt_ops;
+	data->wdev.min_timeout = 1;
+	data->wdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+	/* Calculate the maximum timeout in seconds that we can support */
+	data->wdev.max_timeout = U32_MAX / arch_timer_get_cntfrq();
+
+	/*
+	 * Bits [15:12] are an implementation-defined revision number
+	 * for the component.
+	 */
+	arm_sbsa_wdt_info.firmware_version = (iidr >> 12) & 0xf;
+
+	ret = watchdog_register_device(&data->wdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"could not register watchdog device (ret=%i)\n", ret);
+		return ret;
+	}
+
+	dev_dbg(&pdev->dev, "implementer code is %03x\n",
+		 (iidr & 0xf00) >> 1 | (iidr & 0x7f));
+	dev_info(&pdev->dev, "maximum timeout is %u seconds\n",
+		 data->wdev.max_timeout);
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static int __exit arm_sbsa_wdt_remove(struct platform_device *pdev)
+{
+	struct arm_sbsa_watchdog_data *data = platform_get_drvdata(pdev);
+
+	watchdog_unregister_device(&data->wdev);
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id arm_sbsa_wdt_of_match[] = {
+	{ .compatible = "arm,sbsa-gwdt" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, arm_sbsa_wdt_of_match);
+#endif
+
+static struct platform_driver arm_sbsa_wdt_driver = {
+	.driver = {
+		.name = "sbsa-gwdt",
+		.owner = THIS_MODULE,
+		.pm = &arm_sbsa_wdt_pm_ops,
+		.of_match_table = of_match_ptr(arm_sbsa_wdt_of_match),
+	},
+	.probe = arm_sbsa_wdt_probe,
+	.remove = __exit_p(arm_sbsa_wdt_remove),
+};
+
+module_platform_driver(arm_sbsa_wdt_driver);
+
+MODULE_DESCRIPTION("ARM Server Base System Architecture Watchdog Driver");
+MODULE_LICENSE("GPL v2");
Signature · 8 lines
ciao,
al
-----------------------------------
Al Stone
Software Engineer
Linaro Enterprise Group
[email protected]
-----------------------------------
author
#2 lore raw
Quoted 4 lines · Al Stone

So, this is meant to be SBSA compliant; is it also meant to be SBBR compliant? I suspect not, since there is no ACPI initialization and SBBR requires both UEFI and ACPI. Is there any reason for not being SBBR compliant? I'm not saying this is good or bad; I'm only trying to understand the reasoning.

The driver expects some platform code to read the ACPI tables and initialize a platform device.

Quoted 6 lines

I'll also admit that I'm not an expert in ARM timers. Could I ask a really big favor, please? When I read the SBSA (section 5.2, specifically), that implies to me that there are two interrupts: a first interrupt for the timer itself set to go off after the timeout expires, and a second interrupt that is required when the timeout expires to force some "executive action". I only see one IRQ in the patch; what am I missing?

My driver just uses the first interrupt as a software reset. The second reset is treated as a "backup" hardware reset, in case the software reset didn't work.

Fu's driver, which I admit is better at handling this, uses the first interrupt as an optional pre-timeout that can be used for debugging. The second timeout, which is a hardware reset, is the "real" timeout.

Note that the ACPI specification for the watchdog device only allows for one interrupt to be specified. For these drivers, we expect the first interrupt (WS0) to be specified in the ACPI tables. We assume that the second timeout (WS1) will just cause an immediate hardware reset, without an interrupt.

Also, Fu and I have discussed this, and I think it makes sense to pick up his driver instead of mine. So I'm withdrawing my driver.

Signature · 3 lines
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.
#3 lore raw
Quoted 9 lines · Timur Tabi
Quoted 4 lines · Al Stone

So, this is meant to be SBSA compliant; is it also meant to be SBBR compliant? I suspect not, since there is no ACPI initialization and SBBR requires both UEFI and ACPI. Is there any reason for not being SBBR compliant? I'm not saying this is good or bad; I'm only trying to understand the reasoning.

The driver expects some platform code to read the ACPI tables and initialize a platform device.

Ah. I have to think on that a bit; it sounds reasonable. I guess I was wondering about how the DT usage would work vs the ACPI usage and trying to make them very similar in my head. That may just give me a headache, however.

Quoted 7 lines
Quoted 6 lines

I'll also admit that I'm not an expert in ARM timers. Could I ask a really big favor, please? When I read the SBSA (section 5.2, specifically), that implies to me that there are two interrupts: a first interrupt for the timer itself set to go off after the timeout expires, and a second interrupt that is required when the timeout expires to force some "executive action". I only see one IRQ in the patch; what am I missing?

Quoted 6 lines

My driver just uses the first interrupt as a software reset. The second reset is treated as a "backup" hardware reset, in case the software reset didn't work.

Fu's driver, which I admit is better at handling this, uses the first interrupt as an optional pre-timeout that can be used for debugging. The second timeout, which is a hardware reset, is the "real" timeout.

Oh, I see. I think I was misunderstanding the SBSA, too; I saw the word "signal" and was thinking that had to be an interrupt, which is not correct.

Quoted 4 lines

Note that the ACPI specification for the watchdog device only allows for one interrupt to be specified. For these drivers, we expect the first interrupt (WS0) to be specified in the ACPI tables. We assume that the second timeout (WS1) will just cause an immediate hardware reset, without an interrupt.

Right. I think I've got the mapping now. Thanks, that helped clarify things for me. I appreciate the time spent.

Quoted 2 lines

Also, Fu and I have discussed this, and I think it makes sense to pick up his driver instead of mine. So I'm withdrawing my driver.

Signature · 8 lines
ciao,
al
-----------------------------------
Al Stone
Software Engineer
Linaro Enterprise Group
[email protected]
-----------------------------------
author
#4 lore raw
Quoted 4 lines · Al Stone

Ah. I have to think on that a bit; it sounds reasonable. I guess I was wondering about how the DT usage would work vs the ACPI usage and trying to make them very similar in my head. That may just give me a headache, however.

I haven't tested the code myself, but the driver uses named properties to get the base address for the device, so as long as the device tree has those property names, it should work automatically.

Signature · 3 lines
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.
#5 lore raw
In reply to Al Stone

Hi

Quoted 19 lines · Al Stone
Quoted 7 lines · Timur Tabi

The ARM Server Base System Architecture is a specification for ARM-based server systems. Among other things, it defines the behavior and register
interface for a watchdog timer.

---

My apologies for not getting a chance to think through earlier versions before now.

So, this is meant to be SBSA compliant; is it also meant to be SBBR compliant?
I suspect not, since there is no ACPI initialization and SBBR requires both
UEFI and ACPI. Is there any reason for not being SBBR compliant? I'm not saying this is good or bad; I'm only trying to understand the reasoning.

Quoted 11 lines

I'll also admit that I'm not an expert in ARM timers. Could I ask a really big
favor, please? When I read the SBSA (section 5.2, specifically), that implies
to me that there are two interrupts: a first interrupt for the timer itself set
to go off after the timeout expires, and a second interrupt that is required
when the timeout expires to force some "executive action". I only see one IRQ
in the patch; what am I missing?

The sbsa watchdog has two signals, WS0 and WS1, or as some have now renamed them,
the bark and bite. At a kernel level the first is going to come in as an interrupt,
and second will be a essentially a reset.This gives you two chances at recovery. One
reason for having this second chance is to allow a debug agent to step in an gather
crash information.

HTH

Charles

Quoted 370 lines

Thanks.

Quoted 352 lines

[v4]
Removed COMPILE_TEST
pm_status is now bool
removed some #includes
use do_div instead
display arch version if unsupported
remove watchdog_set_drvdata

 drivers/watchdog/Kconfig        |   9 ++
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/arm_sbsa_wdt.c | 295
++++++++++++++++++++++++++++++++++++++++
 3 files changed, 305 insertions(+)
 create mode 100644 drivers/watchdog/arm_sbsa_wdt.c
drivers/watchdog/Kconfig +9 -0
index e5e7c55..7720190 100644
@@ -514,6 +514,15 @@ config MEDIATEK_WATCHDOG
 	  To compile this driver as a module, choose M here: the
 	  module will be called mtk_wdt.
 
+config ARM_SBSA_WDT
+	tristate "ARM Server Base System Architecture watchdog"
+	depends on ARM64
+	depends on ARM_ARCH_TIMER
+	select WATCHDOG_CORE
+	help
+	  Say Y here to include watchdog timer support for ARM Server Base
+	  System Architecture (SBSA) systems.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
drivers/watchdog/Makefile +1 -0
index 5c19294..063ab8c 100644
@@ -64,6 +64,7 @@ obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
 obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
 obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
 obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_ARM_SBSA_WDT) += arm_sbsa_wdt.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
drivers/watchdog/arm_sbsa_wdt.c new file +121 -0
new file mode 100644
index 0000000..12ed520e
@@ -0,0 +1,295 @@
+/*
+ * Watchdog driver for SBSA-compliant watchdog timers
+ *
+ * Copyright (c) 2015, 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.
+ *
+ * ARM Server Base System Architecture watchdog driver.
+ *
+ * Register descriptions are taken from the ARM Server Base System
+ * Architecture document (ARM-DEN-0029)
+ */
+
+#define pr_fmt(fmt) "sbsa-gwdt: " fmt
+
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
+
+#include <asm/arch_timer.h>
+
+/* Watchdog Interface Identification Registers */
+struct arm_sbsa_watchdog_ident {
+	__le32 w_iidr;	/* Watchdog Interface Identification Register */
+	uint8_t res2[0xFE8 - 0xFD0];
+	__le32 w_pidr2;	/* Peripheral ID2 Register */
+};
+
+/* Watchdog Refresh Frame */
+struct arm_sbsa_watchdog_refresh {
+	__le32 wrr;		/* Watchdog Refresh Register */
+	uint8_t res1[0xFCC - 0x004];
+	struct arm_sbsa_watchdog_ident ident;
+};
+
+/* Watchdog Control Frame */
+struct arm_sbsa_watchdog_control {
+	__le32 wcs;
+	__le32 res1;
+	__le32 wor;
+	__le32 res2;
+	__le64 wcv;
+	uint8_t res3[0xFCC - 0x018];
+	struct arm_sbsa_watchdog_ident ident;
+};
+
+struct arm_sbsa_watchdog_data {
+	struct watchdog_device wdev;
+	bool pm_status;
+	struct arm_sbsa_watchdog_refresh __iomem *refresh;
+	struct arm_sbsa_watchdog_control __iomem *control;
+};
+
+static int arm_sbsa_wdt_start(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	/* Writing to the control register will also reset the counter */
+	writel(1, &data->control->wcs);
+
+	return 0;
+}
+
+static int arm_sbsa_wdt_stop(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	writel(0, &data->control->wcs);
+
+	return 0;
+}
+
+static int arm_sbsa_wdt_set_timeout(struct watchdog_device *wdev,
+	unsigned int timeout)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	wdev->timeout = timeout;
+	writel(arch_timer_get_cntfrq() * wdev->timeout, &data->control->wor);
+
+	return 0;
+}
+
+static int arm_sbsa_wdt_ping(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	writel(1, &data->refresh->wrr);
+
+	return 0;
+}
+
+static unsigned int arm_sbsa_wdt_status(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+	return (readl(&data->control->wcs) & 1) << WDOG_ACTIVE;
+}
+
+static unsigned int arm_sbsa_wdt_timeleft(struct watchdog_device *wdev)
+{
+	struct arm_sbsa_watchdog_data *data =
+		container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+	uint64_t diff = readq(&data->control->wcv) -

arch_counter_get_cntvct();

drivers/watchdog/arm_sbsa_wdt.c new file +173 -0
+
+	do_div(diff, arch_timer_get_cntfrq());
+
+	return diff;
+}
+
+static irqreturn_t arm_sbsa_wdt_interrupt(int irq, void *p)
+{
+	/*
+	 * The WS0 interrupt occurs after the first timeout, so we attempt
+	 * a manual reboot.  If this doesn't work, the WS1 timeout will
+	 * cause a hardware reset.
+	 */
+	pr_crit("Initiating system reboot\n");
+	emergency_restart();
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Disable watchdog if it is active during suspend
+ */
+static int __maybe_unused arm_sbsa_wdt_suspend(struct device *dev)
+{
+	struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+	data->pm_status = !!(readl(&data->control->wcs) & 1);
+
+	if (data->pm_status)
+		writel(0, &data->control->wcs);
+
+	return 0;
+}
+
+/*
+ * Enable watchdog and configure it if necessary
+ */
+static int __maybe_unused arm_sbsa_wdt_resume(struct device *dev)
+{
+	struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+	if (data->pm_status)
+		writel(1, &data->control->wcs);
+
+	return 0;
+}
+
+static const struct dev_pm_ops arm_sbsa_wdt_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(arm_sbsa_wdt_suspend, arm_sbsa_wdt_resume)
+};
+
+static struct watchdog_info arm_sbsa_wdt_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity = "ARM SBSA watchdog",
+};
+
+static struct watchdog_ops arm_sbsa_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = arm_sbsa_wdt_start,
+	.stop = arm_sbsa_wdt_stop,
+	.ping = arm_sbsa_wdt_ping,
+	.set_timeout = arm_sbsa_wdt_set_timeout,
+	.status = arm_sbsa_wdt_status,
+	.get_timeleft = arm_sbsa_wdt_timeleft,
+};
+
+static int __init arm_sbsa_wdt_probe(struct platform_device *pdev)
+{
+	struct arm_sbsa_watchdog_data *data;
+	struct resource *res;
+	uint32_t iidr;
+	unsigned int arch;
+	int irq, ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
+	data->control = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->control))
+		return PTR_ERR(data->control);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "refresh");
+	data->refresh = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->refresh))
+		return PTR_ERR(data->refresh);
+
+	/* We only support architecture version 0 */
+	iidr = readl(&data->control->ident.w_iidr);
+	arch = (iidr >> 16) & 0xf;
+	if (arch != 0) {
+		dev_err(&pdev->dev,
+			 "architecture version %u is not supported\n", arch);
+		return -ENODEV;
+	}
+
+	irq = platform_get_irq_byname(pdev, "ws0");
+	if (irq < 0) {
+		dev_err(&pdev->dev, "could not get interrupt\n");
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, arm_sbsa_wdt_interrupt,
+			       0, pdev->name, NULL);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "could not request irq %i (ret=%i)\n",
+			irq, ret);
+		return ret;
+	}
+
+	data->wdev.info = &arm_sbsa_wdt_info;
+	data->wdev.ops = &arm_sbsa_wdt_ops;
+	data->wdev.min_timeout = 1;
+	data->wdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+	/* Calculate the maximum timeout in seconds that we can support */
+	data->wdev.max_timeout = U32_MAX / arch_timer_get_cntfrq();
+
+	/*
+	 * Bits [15:12] are an implementation-defined revision number
+	 * for the component.
+	 */
+	arm_sbsa_wdt_info.firmware_version = (iidr >> 12) & 0xf;
+
+	ret = watchdog_register_device(&data->wdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"could not register watchdog device (ret=%i)\n", ret);
+		return ret;
+	}
+
+	dev_dbg(&pdev->dev, "implementer code is %03x\n",
+		 (iidr & 0xf00) >> 1 | (iidr & 0x7f));
+	dev_info(&pdev->dev, "maximum timeout is %u seconds\n",
+		 data->wdev.max_timeout);
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static int __exit arm_sbsa_wdt_remove(struct platform_device *pdev)
+{
+	struct arm_sbsa_watchdog_data *data = platform_get_drvdata(pdev);
+
+	watchdog_unregister_device(&data->wdev);
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id arm_sbsa_wdt_of_match[] = {
+	{ .compatible = "arm,sbsa-gwdt" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, arm_sbsa_wdt_of_match);
+#endif
+
+static struct platform_driver arm_sbsa_wdt_driver = {
+	.driver = {
+		.name = "sbsa-gwdt",
+		.owner = THIS_MODULE,
+		.pm = &arm_sbsa_wdt_pm_ops,
+		.of_match_table = of_match_ptr(arm_sbsa_wdt_of_match),
+	},
+	.probe = arm_sbsa_wdt_probe,
+	.remove = __exit_p(arm_sbsa_wdt_remove),
+};
+
+module_platform_driver(arm_sbsa_wdt_driver);
+
+MODULE_DESCRIPTION("ARM Server Base System Architecture Watchdog

Driver");
+MODULE_LICENSE("GPL v2");

Signature · 12 lines
ciao,
al
-----------------------------------
Al Stone
Software Engineer
Linaro Enterprise Group
[email protected]
-----------------------------------
_______________________________________________
Linaro-acpi mailing list
[email protected]
https://lists.linaro.org/mailman/listinfo/linaro-acpi
#6 lore raw

Hi Charles,

Great thanks for your info.
Sorry, I just see this message.

Would you please provide some info about How to use the WS0/1. What is the original thought of this two stage timeout?

Those info is very important for implementing this driver. :-)

Thanks again!

Quoted 421 lines · administrator

Hi

Quoted 19 lines · Al Stone
Quoted 7 lines · Timur Tabi

The ARM Server Base System Architecture is a specification for ARM-based server systems. Among other things, it defines the behavior and register
interface for a watchdog timer.

---

My apologies for not getting a chance to think through earlier versions before now.

So, this is meant to be SBSA compliant; is it also meant to be SBBR compliant?
I suspect not, since there is no ACPI initialization and SBBR requires both
UEFI and ACPI. Is there any reason for not being SBBR compliant? I'm not saying this is good or bad; I'm only trying to understand the reasoning.

Quoted 11 lines

I'll also admit that I'm not an expert in ARM timers. Could I ask a really big
favor, please? When I read the SBSA (section 5.2, specifically), that implies
to me that there are two interrupts: a first interrupt for the timer itself set
to go off after the timeout expires, and a second interrupt that is required
when the timeout expires to force some "executive action". I only see one IRQ
in the patch; what am I missing?

The sbsa watchdog has two signals, WS0 and WS1, or as some have now renamed them,
the bark and bite. At a kernel level the first is going to come in as an interrupt,
and second will be a essentially a reset.This gives you two chances at recovery. One
reason for having this second chance is to allow a debug agent to step in an gather
crash information.

HTH

Charles

Quoted 370 lines

Thanks.

Quoted 349 lines

[v4]
Removed COMPILE_TEST
pm_status is now bool
removed some #includes
use do_div instead
display arch version if unsupported
remove watchdog_set_drvdata

 drivers/watchdog/Kconfig        |   9 ++
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/arm_sbsa_wdt.c | 295
++++++++++++++++++++++++++++++++++++++++
 3 files changed, 305 insertions(+)
 create mode 100644 drivers/watchdog/arm_sbsa_wdt.c
drivers/watchdog/Kconfig +9 -0
index e5e7c55..7720190 100644
@@ -514,6 +514,15 @@ config MEDIATEK_WATCHDOG
       To compile this driver as a module, choose M here: the
       module will be called mtk_wdt.
 
+config ARM_SBSA_WDT
+    tristate "ARM Server Base System Architecture watchdog"
+    depends on ARM64
+    depends on ARM_ARCH_TIMER
+    select WATCHDOG_CORE
+    help
+      Say Y here to include watchdog timer support for ARM Server Base
+      System Architecture (SBSA) systems.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
drivers/watchdog/Makefile +1 -0
index 5c19294..063ab8c 100644
@@ -64,6 +64,7 @@ obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
 obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
 obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
 obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_ARM_SBSA_WDT) += arm_sbsa_wdt.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
drivers/watchdog/arm_sbsa_wdt.c new file +121 -0
new file mode 100644
index 0000000..12ed520e
@@ -0,0 +1,295 @@
+/*
+ * Watchdog driver for SBSA-compliant watchdog timers
+ *
+ * Copyright (c) 2015, 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.
+ *
+ * ARM Server Base System Architecture watchdog driver.
+ *
+ * Register descriptions are taken from the ARM Server Base System
+ * Architecture document (ARM-DEN-0029)
+ */
+
+#define pr_fmt(fmt) "sbsa-gwdt: " fmt
+
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
+
+#include <asm/arch_timer.h>
+
+/* Watchdog Interface Identification Registers */
+struct arm_sbsa_watchdog_ident {
+    __le32 w_iidr;  /* Watchdog Interface Identification Register */
+    uint8_t res2[0xFE8 - 0xFD0];
+    __le32 w_pidr2; /* Peripheral ID2 Register */
+};
+
+/* Watchdog Refresh Frame */
+struct arm_sbsa_watchdog_refresh {
+    __le32 wrr;             /* Watchdog Refresh Register */
+    uint8_t res1[0xFCC - 0x004];
+    struct arm_sbsa_watchdog_ident ident;
+};
+
+/* Watchdog Control Frame */
+struct arm_sbsa_watchdog_control {
+    __le32 wcs;
+    __le32 res1;
+    __le32 wor;
+    __le32 res2;
+    __le64 wcv;
+    uint8_t res3[0xFCC - 0x018];
+    struct arm_sbsa_watchdog_ident ident;
+};
+
+struct arm_sbsa_watchdog_data {
+    struct watchdog_device wdev;
+    bool pm_status;
+    struct arm_sbsa_watchdog_refresh __iomem *refresh;
+    struct arm_sbsa_watchdog_control __iomem *control;
+};
+
+static int arm_sbsa_wdt_start(struct watchdog_device *wdev)
+{
+    struct arm_sbsa_watchdog_data *data =
+            container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+    /* Writing to the control register will also reset the counter */
+    writel(1, &data->control->wcs);
+
+    return 0;
+}
+
+static int arm_sbsa_wdt_stop(struct watchdog_device *wdev)
+{
+    struct arm_sbsa_watchdog_data *data =
+            container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+    writel(0, &data->control->wcs);
+
+    return 0;
+}
+
+static int arm_sbsa_wdt_set_timeout(struct watchdog_device *wdev,
+    unsigned int timeout)
+{
+    struct arm_sbsa_watchdog_data *data =
+            container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+    wdev->timeout = timeout;
+    writel(arch_timer_get_cntfrq() * wdev->timeout, &data->control->wor);
+
+    return 0;
+}
+
+static int arm_sbsa_wdt_ping(struct watchdog_device *wdev)
+{
+    struct arm_sbsa_watchdog_data *data =
+            container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+    writel(1, &data->refresh->wrr);
+
+    return 0;
+}
+
+static unsigned int arm_sbsa_wdt_status(struct watchdog_device *wdev)
+{
+    struct arm_sbsa_watchdog_data *data =
+            container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+    return (readl(&data->control->wcs) & 1) << WDOG_ACTIVE;
+}
+
+static unsigned int arm_sbsa_wdt_timeleft(struct watchdog_device *wdev)
+{
+    struct arm_sbsa_watchdog_data *data =
+            container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+    uint64_t diff = readq(&data->control->wcv) -

arch_counter_get_cntvct();

drivers/watchdog/arm_sbsa_wdt.c new file +173 -0
+
+    do_div(diff, arch_timer_get_cntfrq());
+
+    return diff;
+}
+
+static irqreturn_t arm_sbsa_wdt_interrupt(int irq, void *p)
+{
+    /*
+     * The WS0 interrupt occurs after the first timeout, so we attempt
+     * a manual reboot.  If this doesn't work, the WS1 timeout will
+     * cause a hardware reset.
+     */
+    pr_crit("Initiating system reboot\n");
+    emergency_restart();
+
+    return IRQ_HANDLED;
+}
+
+/*
+ * Disable watchdog if it is active during suspend
+ */
+static int __maybe_unused arm_sbsa_wdt_suspend(struct device *dev)
+{
+    struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+    data->pm_status = !!(readl(&data->control->wcs) & 1);
+
+    if (data->pm_status)
+            writel(0, &data->control->wcs);
+
+    return 0;
+}
+
+/*
+ * Enable watchdog and configure it if necessary
+ */
+static int __maybe_unused arm_sbsa_wdt_resume(struct device *dev)
+{
+    struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+    if (data->pm_status)
+            writel(1, &data->control->wcs);
+
+    return 0;
+}
+
+static const struct dev_pm_ops arm_sbsa_wdt_pm_ops = {
+    SET_SYSTEM_SLEEP_PM_OPS(arm_sbsa_wdt_suspend, arm_sbsa_wdt_resume)
+};
+
+static struct watchdog_info arm_sbsa_wdt_info = {
+    .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+    .identity = "ARM SBSA watchdog",
+};
+
+static struct watchdog_ops arm_sbsa_wdt_ops = {
+    .owner = THIS_MODULE,
+    .start = arm_sbsa_wdt_start,
+    .stop = arm_sbsa_wdt_stop,
+    .ping = arm_sbsa_wdt_ping,
+    .set_timeout = arm_sbsa_wdt_set_timeout,
+    .status = arm_sbsa_wdt_status,
+    .get_timeleft = arm_sbsa_wdt_timeleft,
+};
+
+static int __init arm_sbsa_wdt_probe(struct platform_device *pdev)
+{
+    struct arm_sbsa_watchdog_data *data;
+    struct resource *res;
+    uint32_t iidr;
+    unsigned int arch;
+    int irq, ret;
+
+    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+    if (!data)
+            return -ENOMEM;
+
+    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
+    data->control = devm_ioremap_resource(&pdev->dev, res);
+    if (IS_ERR(data->control))
+            return PTR_ERR(data->control);
+
+    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "refresh");
+    data->refresh = devm_ioremap_resource(&pdev->dev, res);
+    if (IS_ERR(data->refresh))
+            return PTR_ERR(data->refresh);
+
+    /* We only support architecture version 0 */
+    iidr = readl(&data->control->ident.w_iidr);
+    arch = (iidr >> 16) & 0xf;
+    if (arch != 0) {
+            dev_err(&pdev->dev,
+                     "architecture version %u is not supported\n", arch);
+            return -ENODEV;
+    }
+
+    irq = platform_get_irq_byname(pdev, "ws0");
+    if (irq < 0) {
+            dev_err(&pdev->dev, "could not get interrupt\n");
+            return irq;
+    }
+
+    ret = devm_request_irq(&pdev->dev, irq, arm_sbsa_wdt_interrupt,
+                           0, pdev->name, NULL);
+    if (ret < 0) {
+            dev_err(&pdev->dev, "could not request irq %i (ret=%i)\n",
+                    irq, ret);
+            return ret;
+    }
+
+    data->wdev.info = &arm_sbsa_wdt_info;
+    data->wdev.ops = &arm_sbsa_wdt_ops;
+    data->wdev.min_timeout = 1;
+    data->wdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+    /* Calculate the maximum timeout in seconds that we can support */
+    data->wdev.max_timeout = U32_MAX / arch_timer_get_cntfrq();
+
+    /*
+     * Bits [15:12] are an implementation-defined revision number
+     * for the component.
+     */
+    arm_sbsa_wdt_info.firmware_version = (iidr >> 12) & 0xf;
+
+    ret = watchdog_register_device(&data->wdev);
+    if (ret < 0) {
+            dev_err(&pdev->dev,
+                    "could not register watchdog device (ret=%i)\n", ret);
+            return ret;
+    }
+
+    dev_dbg(&pdev->dev, "implementer code is %03x\n",
+             (iidr & 0xf00) >> 1 | (iidr & 0x7f));
+    dev_info(&pdev->dev, "maximum timeout is %u seconds\n",
+             data->wdev.max_timeout);
+
+    platform_set_drvdata(pdev, data);
+
+    return 0;
+}
+
+static int __exit arm_sbsa_wdt_remove(struct platform_device *pdev)
+{
+    struct arm_sbsa_watchdog_data *data = platform_get_drvdata(pdev);
+
+    watchdog_unregister_device(&data->wdev);
+
+    return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id arm_sbsa_wdt_of_match[] = {
+    { .compatible = "arm,sbsa-gwdt" },
+    {},
+};
+MODULE_DEVICE_TABLE(of, arm_sbsa_wdt_of_match);
+#endif
+
+static struct platform_driver arm_sbsa_wdt_driver = {
+    .driver = {
+            .name = "sbsa-gwdt",
+            .owner = THIS_MODULE,
+            .pm = &arm_sbsa_wdt_pm_ops,
+            .of_match_table = of_match_ptr(arm_sbsa_wdt_of_match),
+    },
+    .probe = arm_sbsa_wdt_probe,
+    .remove = __exit_p(arm_sbsa_wdt_remove),
+};
+
+module_platform_driver(arm_sbsa_wdt_driver);
+
+MODULE_DESCRIPTION("ARM Server Base System Architecture Watchdog
Quoted 3 lines

Driver");
+MODULE_LICENSE("GPL v2");

--
ciao,
al
-----------------------------------
Al Stone
Software Engineer
Linaro Enterprise Group
[email protected]
-----------------------------------
_______________________________________________
Linaro-acpi mailing list
[email protected]
https://lists.linaro.org/mailman/listinfo/linaro-acpi

Signature · 10 lines
Best regards,

Fu Wei
Software Engineer
Red Hat Software (Beijing) Co.,Ltd.Shanghai Branch
Ph: +86 21 61221326(direct)
Ph: +86 186 2020 4684 (mobile)
Room 1512, Regus One Corporate Avenue,Level 15,
One Corporate Avenue,222 Hubin Road,Huangpu District,
Shanghai,China 200021