Linux userland API discussions
 help / color / mirror / Atom feed
* [PATCH 0/5] Enhancements to twl4030 phy to support better charging - V2
From: NeilBrown @ 2015-03-22 22:35 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: Tony Lindgren, linux-api-u79uwXL29TY76Z2rM5mHXA, GTA04 owners,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Pavel Machek

Hi Kishon,
 I wonder if you could queue the following for the next merge window.
 They allow the twl4030 phy to provide more information to the
 twl4030 battery charger.
 There are only minimal changes since the first version, particularly
 documentation has been improved.

Thanks,
NeilBrown


---

NeilBrown (5):
      usb: phy: twl4030: make runtime pm more reliable.
      usb: phy: twl4030: allow charger to see usb current draw limits.
      usb: phy: twl4030: add ABI documentation
      usb: phy: twl4030: add support for reading restore on ID pin.
      usb: phy: twl4030: test ID resistance to see if charger is present.


 .../ABI/testing/sysfs-platform-twl4030-usb         |   30 ++++
 drivers/phy/phy-twl4030-usb.c                      |  147 ++++++++++++++++++--
 2 files changed, 162 insertions(+), 15 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-twl4030-usb

--
Signature

^ permalink raw reply

* [PATCH 1/5] usb: phy: twl4030: make runtime pm more reliable.
From: NeilBrown @ 2015-03-22 22:35 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: Tony Lindgren, linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, GTA04 owners, NeilBrown,
	Pavel Machek
In-Reply-To: <20150322223307.21765.62974.stgit-wvvUuzkyo1EYVZTmpyfIwg@public.gmane.org>

From: NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>

A construct like:

        if (pm_runtime_suspended(twl->dev))
               pm_runtime_get_sync(twl->dev);

is against the spirit of the runtime_pm interface as it
makes the internal refcounting useless.

In this case it is also racy, particularly as 'put_autosuspend'
is use to drop a reference.
When that happens a timer is started and the device is
runtime-suspended after the timeout.
If the above code runs in this window, the device will not be
found to be suspended so no pm_runtime reference is taken.
When the timer expires the device will be suspended, which is
against the intention of the code.

So be more direct is taking and dropping references.
If twl->linkstat is VBUS_VALID or ID_GROUND, then hold a
pm_runtime reference, otherwise don't.
Define "cable_present()" to test for this condition.

Tested-by: Tony Lindgren <tony-4v6yS6AI5VpBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>
---
 drivers/phy/phy-twl4030-usb.c |   29 ++++++++++++++++++++---------
 1 file changed, 20 insertions(+), 9 deletions(-)

diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c
index 8e87f54671f3..1a244f34b748 100644
--- a/drivers/phy/phy-twl4030-usb.c
+++ b/drivers/phy/phy-twl4030-usb.c
@@ -144,6 +144,16 @@
 #define PMBR1				0x0D
 #define GPIO_USB_4PIN_ULPI_2430C	(3 << 0)
 
+/*
+ * If VBUS is valid or ID is ground, then we know a
+ * cable is present and we need to be runtime-enabled
+ */
+static inline bool cable_present(enum omap_musb_vbus_id_status stat)
+{
+	return stat == OMAP_MUSB_VBUS_VALID ||
+		stat == OMAP_MUSB_ID_GROUND;
+}
+
 struct twl4030_usb {
 	struct usb_phy		phy;
 	struct device		*dev;
@@ -536,8 +546,10 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 
 	mutex_lock(&twl->lock);
 	if (status >= 0 && status != twl->linkstat) {
+		status_changed =
+			cable_present(twl->linkstat) !=
+			cable_present(status);
 		twl->linkstat = status;
-		status_changed = true;
 	}
 	mutex_unlock(&twl->lock);
 
@@ -553,15 +565,11 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 		 * USB_LINK_VBUS state.  musb_hdrc won't care until it
 		 * starts to handle softconnect right.
 		 */
-		if ((status == OMAP_MUSB_VBUS_VALID) ||
-		    (status == OMAP_MUSB_ID_GROUND)) {
-			if (pm_runtime_suspended(twl->dev))
-				pm_runtime_get_sync(twl->dev);
+		if (cable_present(status)) {
+			pm_runtime_get_sync(twl->dev);
 		} else {
-			if (pm_runtime_active(twl->dev)) {
-				pm_runtime_mark_last_busy(twl->dev);
-				pm_runtime_put_autosuspend(twl->dev);
-			}
+			pm_runtime_mark_last_busy(twl->dev);
+			pm_runtime_put_autosuspend(twl->dev);
 		}
 		omap_musb_mailbox(status);
 	}
@@ -768,6 +776,9 @@ static int twl4030_usb_remove(struct platform_device *pdev)
 
 	/* disable complete OTG block */
 	twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
+
+	if (cable_present(twl->linkstat))
+		pm_runtime_put_noidle(twl->dev);
 	pm_runtime_mark_last_busy(twl->dev);
 	pm_runtime_put(twl->dev);
 

^ permalink raw reply related

* [PATCH 3/5] usb: phy: twl4030: add ABI documentation
From: NeilBrown @ 2015-03-22 22:35 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: Tony Lindgren, linux-api, GTA04 owners, linux-kernel,
	Pavel Machek
In-Reply-To: <20150322223307.21765.62974.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

This driver device one local attribute: vbus.
Describe that in Documentation/ABI/testing/sysfs-platform/twl4030-usb.

Signed-off-by: NeilBrown <neil@brown.name>
---
 .../ABI/testing/sysfs-platform-twl4030-usb         |    8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-twl4030-usb

diff --git a/Documentation/ABI/testing/sysfs-platform-twl4030-usb b/Documentation/ABI/testing/sysfs-platform-twl4030-usb
new file mode 100644
index 000000000000..512c51be64ae
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-twl4030-usb
@@ -0,0 +1,8 @@
+What: /sys/bus/platform/devices/*twl4030-usb/vbus
+Description:
+	Read-only status reporting if VBUS (approx 5V)
+	is being supplied by the USB bus.
+
+	Possible values: "on", "off".
+
+	Changes are notified via select/poll.

^ permalink raw reply related

* [PATCH 5/5] usb: phy: twl4030: test ID resistance to see if charger is present.
From: NeilBrown @ 2015-03-22 22:35 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, Tony Lindgren,
	Pavel Machek
In-Reply-To: <20150322223307.21765.62974.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

If an 'A' plug is inserted, ID should be pulled to ground.
If a 'B' plug, then ID should be floating.

If an Accessory Charger Adapter is inserted, then ID will
be neither grounded nor floating.  In this case tell the
USB subsystem that it is an A plug, and the battery
charging subsystem that it is a charger.

Fortunately, this will treat the Openmoko charger (and
other similar chargers) as a charger.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/phy/phy-twl4030-usb.c |   28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c
index 8dbf9d2a99d1..d81753bad1ca 100644
--- a/drivers/phy/phy-twl4030-usb.c
+++ b/drivers/phy/phy-twl4030-usb.c
@@ -606,9 +606,31 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 	struct twl4030_usb *twl = _twl;
 	enum omap_musb_vbus_id_status status;
 	bool status_changed = false;
+	bool found_charger = false;
 
 	status = twl4030_usb_linkstat(twl);
 
+	if (status == OMAP_MUSB_ID_GROUND ||
+	    status == OMAP_MUSB_VBUS_VALID) {
+		/* We should check the resistance on the ID pin.
+		 * If not a Ground or Floating, then this is
+		 * likely a charger
+		 */
+		enum twl4030_id_status sts = twl4030_get_id(twl);
+		if (sts > TWL4030_GROUND &&
+		    sts < TWL4030_FLOATING) {
+			/*
+			 * This might be a charger, or an
+			 * Accessory Charger Adapter.
+			 * In either case we can charge, and it
+			 * makes sense to tell the USB system
+			 * that we might be acting as a HOST.
+			 */
+			status = OMAP_MUSB_ID_GROUND;
+			found_charger = true;
+		}
+	}
+
 	mutex_lock(&twl->lock);
 	if (status >= 0 && status != twl->linkstat) {
 		status_changed =
@@ -633,6 +655,12 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 		}
 		omap_musb_mailbox(status);
 	}
+	if (found_charger && twl->phy.last_event != USB_EVENT_CHARGER) {
+		twl->phy.last_event = USB_EVENT_CHARGER;
+		atomic_notifier_call_chain(&twl->phy.notifier,
+					   USB_EVENT_CHARGER,
+					   NULL);
+	}
 
 	/* don't schedule during sleep - irq works right then */
 	if (status == OMAP_MUSB_ID_GROUND && pm_runtime_active(twl->dev)) {

^ permalink raw reply related

* [PATCH 4/5] usb: phy: twl4030: add support for reading restore on ID pin.
From: NeilBrown @ 2015-03-22 22:35 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, Tony Lindgren,
	Pavel Machek
In-Reply-To: <20150322223307.21765.62974.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

The twl4030 phy can measure, with low precision, the
resistance-to-ground of the ID pin.

Add a function to read the value, and export the result
via sysfs.

If the read fails, which it does sometimes, try again in 50msec.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 .../ABI/testing/sysfs-platform-twl4030-usb         |   22 +++++++
 drivers/phy/phy-twl4030-usb.c                      |   63 ++++++++++++++++++++
 2 files changed, 85 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-platform-twl4030-usb b/Documentation/ABI/testing/sysfs-platform-twl4030-usb
index 512c51be64ae..425d23676f8a 100644
--- a/Documentation/ABI/testing/sysfs-platform-twl4030-usb
+++ b/Documentation/ABI/testing/sysfs-platform-twl4030-usb
@@ -6,3 +6,25 @@ Description:
 	Possible values: "on", "off".
 
 	Changes are notified via select/poll.
+
+What: /sys/bus/platform/devices/*twl4030-usb/id
+Description:
+	Read-only report on measurement of USB-OTG ID pin.
+
+	The ID pin may be floating, grounded, or pulled to
+	ground by a resistor.
+
+	A very course grained reading of the resistance is
+	available.  The numbers given in kilo-ohms are roughly
+	the center-point of the detected range.
+
+	Possible values are:
+		ground
+		102k
+		200k
+		440k
+		floating
+		unknown
+
+	"unknown" indicates a problem with trying to detect
+	the resistance.
diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c
index 0fcef8717d5b..8dbf9d2a99d1 100644
--- a/drivers/phy/phy-twl4030-usb.c
+++ b/drivers/phy/phy-twl4030-usb.c
@@ -384,6 +384,56 @@ static void twl4030_i2c_access(struct twl4030_usb *twl, int on)
 	}
 }
 
+enum twl4030_id_status {
+	TWL4030_GROUND,
+	TWL4030_102K,
+	TWL4030_200K,
+	TWL4030_440K,
+	TWL4030_FLOATING,
+	TWL4030_ID_UNKNOWN,
+};
+static char *twl4030_id_names[] = {
+	"ground",
+	"102k",
+	"200k",
+	"440k",
+	"floating",
+	"unknown"
+};
+
+enum twl4030_id_status twl4030_get_id(struct twl4030_usb *twl)
+{
+	int ret;
+
+	pm_runtime_get_sync(twl->dev);
+	if (twl->usb_mode == T2_USB_MODE_ULPI)
+		twl4030_i2c_access(twl, 1);
+	ret = twl4030_usb_read(twl, ULPI_OTG_CTRL);
+	if (ret < 0 || !(ret & ULPI_OTG_ID_PULLUP)) {
+		/* Need pull-up to read ID */
+		twl4030_usb_set_bits(twl, ULPI_OTG_CTRL,
+				     ULPI_OTG_ID_PULLUP);
+		mdelay(50);
+	}
+	ret = twl4030_usb_read(twl, ID_STATUS);
+	if (ret < 0 || (ret & 0x1f) == 0) {
+		mdelay(50);
+		ret = twl4030_usb_read(twl, ID_STATUS);
+	}
+
+	if (twl->usb_mode == T2_USB_MODE_ULPI)
+		twl4030_i2c_access(twl, 0);
+	pm_runtime_put_autosuspend(twl->dev);
+
+	if (ret < 0)
+		return TWL4030_ID_UNKNOWN;
+	ret = ffs(ret) - 1;
+	if (ret < TWL4030_GROUND || ret > TWL4030_FLOATING)
+		return TWL4030_ID_UNKNOWN;
+
+	return ret;
+}
+
 static void __twl4030_phy_power(struct twl4030_usb *twl, int on)
 {
 	u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL);
@@ -541,6 +591,16 @@ static ssize_t twl4030_usb_vbus_show(struct device *dev,
 }
 static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
 
+static ssize_t twl4030_usb_id_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct twl4030_usb *twl = dev_get_drvdata(dev);
+	return scnprintf(buf, PAGE_SIZE, "%s\n",
+			 twl4030_id_names[twl4030_get_id(twl)]);
+}
+static DEVICE_ATTR(id, 0444, twl4030_usb_id_show, NULL);
+
 static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 {
 	struct twl4030_usb *twl = _twl;
@@ -729,6 +789,8 @@ static int twl4030_usb_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, twl);
 	if (device_create_file(&pdev->dev, &dev_attr_vbus))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
+	if (device_create_file(&pdev->dev, &dev_attr_id))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
 
 	ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier);
 
@@ -774,6 +836,7 @@ static int twl4030_usb_remove(struct platform_device *pdev)
 	pm_runtime_get_sync(twl->dev);
 	cancel_delayed_work(&twl->id_workaround_work);
 	device_remove_file(twl->dev, &dev_attr_vbus);
+	device_remove_file(twl->dev, &dev_attr_id);
 
 	/* set transceiver mode to power on defaults */
 	twl4030_usb_set_mode(twl, -1);

^ permalink raw reply related

* [PATCH 2/5] usb: phy: twl4030: allow charger to see usb current draw limits.
From: NeilBrown @ 2015-03-22 22:35 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: NeilBrown, linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, GTA04 owners, Tony Lindgren,
	Pavel Machek
In-Reply-To: <20150322223307.21765.62974.stgit-wvvUuzkyo1EYVZTmpyfIwg@public.gmane.org>

From: NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>

The charger needs to know when a USB gadget has been enumerated
and what the agreed maximum current was so that it can adjust
charging accordingly.

So define a "set_power()" function to record the permitted
draw, and pass a pointer to that when sending USB_EVENT_ENUMERATED
notification.

Signed-off-by: NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>
---
 drivers/phy/phy-twl4030-usb.c |   27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c
index 1a244f34b748..0fcef8717d5b 100644
--- a/drivers/phy/phy-twl4030-usb.c
+++ b/drivers/phy/phy-twl4030-usb.c
@@ -173,6 +173,11 @@ struct twl4030_usb {
 	enum omap_musb_vbus_id_status linkstat;
 	bool			vbus_supplied;
 
+	/* Permitted vbus draw in mA - only meaningful after
+	 * USB_EVENT_ENUMERATED
+	 */
+	unsigned int		vbus_draw;
+
 	struct delayed_work	id_workaround_work;
 };
 
@@ -554,12 +559,7 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 	mutex_unlock(&twl->lock);
 
 	if (status_changed) {
-		/* FIXME add a set_power() method so that B-devices can
-		 * configure the charger appropriately.  It's not always
-		 * correct to consume VBUS power, and how much current to
-		 * consume is a function of the USB configuration chosen
-		 * by the host.
-		 *
+		/*
 		 * REVISIT usb_gadget_vbus_connect(...) as needed, ditto
 		 * its disconnect() sibling, when changing to/from the
 		 * USB_LINK_VBUS state.  musb_hdrc won't care until it
@@ -631,6 +631,20 @@ static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host)
 	return 0;
 }
 
+static int twl4030_set_power(struct usb_phy *phy, unsigned mA)
+{
+	struct twl4030_usb *twl = phy_to_twl(phy);
+
+	if (twl->vbus_draw != mA) {
+		phy->last_event = USB_EVENT_ENUMERATED;
+		twl->vbus_draw = mA;
+		atomic_notifier_call_chain(&phy->notifier,
+					   USB_EVENT_ENUMERATED,
+					   &twl->vbus_draw);
+	}
+	return 0;
+}
+
 static const struct phy_ops ops = {
 	.init		= twl4030_phy_init,
 	.power_on	= twl4030_phy_power_on,
@@ -681,6 +695,7 @@ static int twl4030_usb_probe(struct platform_device *pdev)
 	twl->phy.label		= "twl4030";
 	twl->phy.otg		= otg;
 	twl->phy.type		= USB_PHY_TYPE_USB2;
+	twl->phy.set_power	= twl4030_set_power;
 
 	otg->usb_phy		= &twl->phy;
 	otg->set_host		= twl4030_set_host;

^ permalink raw reply related

* [PATCH 00/14] Enhance twl4030_charger functionality. - V2
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: linux-api, linux-kernel, GTA04 owners, inux-pm, Pavel Machek,
	linux-omap, Lee Jones

Hi Sebastian,
 here is V2 of my patches to improve the twl4030 charger.
 They include improved documentation and removal of changes
 to the CVENAC bit which I didn't properly understand.

 One patch needs to make a change in drivers/mfd, and that has
 been acked by Lee Jones.

Summary from before:
These patches make a number of improvements to twl4030_charger.

Some are just internal cleanups (e.g. use of devres).  Others allow
better control of charging through both manual and automatic means.

- the maximum current can be configured via sysfs.
- the charger will only draw that current if it can do so without
  the voltage dropping too much
- a 'continuous' mode is available which ignores voltage and just
  takes what it can (to be used with caution, but very useful in
  some circumstances).
- 'ac' and 'usb' power sources can be configured separately.


Thanks,
NeilBrown

---

NeilBrown (14):
      twl4030_charger: use devm_request_threaded_irq
      twl4030_charger: use devres for power_supply_register and kzalloc.
      twl4030_charger: use runtime_pm to keep usb phy active while charging.
      twl4030_charger: trust phy to determine when USB power is available.
      twl4030_charger: split uA calculation into a function.
      twl4030_charger: allow fine control of charger current.
      twl4030_charger: distinguish between USB current and 'AC' current
      twl4030_charger: allow max_current to be managed via sysfs.
      twl4030_charger: only draw USB current as negotiated with host.
      twl4030_charger: enable manual enable/disable of usb charging.
      twl4030_charger: add software controlled linear charging mode.
      twl4030_charger: add ac/mode to match usb/mode
      twl4030_charger: Increase current carefully while watching voltage.
      twl4030_charger: assume a 'charger' can supply maximum current.


 .../ABI/testing/sysfs-class-power-twl4030          |   45 ++
 drivers/mfd/twl-core.c                             |    9 
 drivers/power/twl4030_charger.c                    |  573 +++++++++++++++++---
 3 files changed, 536 insertions(+), 91 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-twl4030

--
Signature

^ permalink raw reply

* [PATCH 01/14] twl4030_charger: use devm_request_threaded_irq
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

This simplifies the error paths.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |   18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index b07f4e2f2dde..58996d252ecf 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -610,21 +610,21 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		goto fail_register_usb;
 	}
 
-	ret = request_threaded_irq(bci->irq_chg, NULL,
+	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
 			twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name,
 			bci);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_chg, ret);
-		goto fail_chg_irq;
+		goto fail;
 	}
 
-	ret = request_threaded_irq(bci->irq_bci, NULL,
+	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
 			twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_bci, ret);
-		goto fail_bci_irq;
+		goto fail;
 	}
 
 	INIT_WORK(&bci->work, twl4030_bci_usb_work);
@@ -647,7 +647,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 			       TWL4030_INTERRUPTS_BCIIMR1A);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
-		goto fail_unmask_interrupts;
+		goto fail;
 	}
 
 	reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
@@ -666,11 +666,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 
 	return 0;
 
-fail_unmask_interrupts:
-	free_irq(bci->irq_bci, bci);
-fail_bci_irq:
-	free_irq(bci->irq_chg, bci);
-fail_chg_irq:
+fail:
 	power_supply_unregister(&bci->usb);
 fail_register_usb:
 	power_supply_unregister(&bci->ac);
@@ -695,8 +691,6 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR2A);
 
-	free_irq(bci->irq_bci, bci);
-	free_irq(bci->irq_chg, bci);
 	power_supply_unregister(&bci->usb);
 	power_supply_unregister(&bci->ac);
 	kfree(bci);

^ permalink raw reply related

* [PATCH 02/14] twl4030_charger: use devres for power_supply_register and kzalloc.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

Final allocations/registrations are now managed by devres.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |   32 +++++++++-----------------------
 1 file changed, 9 insertions(+), 23 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 58996d252ecf..7b6c4000cd30 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -565,7 +565,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	int ret;
 	u32 reg;
 
-	bci = kzalloc(sizeof(*bci), GFP_KERNEL);
+	bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL);
 	if (bci == NULL)
 		return -ENOMEM;
 
@@ -580,7 +580,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	ret = twl4030_is_battery_present(bci);
 	if  (ret) {
 		dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret);
-		goto fail_no_battery;
+		return ret;
 	}
 
 	platform_set_drvdata(pdev, bci);
@@ -590,10 +590,10 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props);
 	bci->ac.get_property = twl4030_bci_get_property;
 
-	ret = power_supply_register(&pdev->dev, &bci->ac);
+	ret = devm_power_supply_register(&pdev->dev, &bci->ac);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
-		goto fail_register_ac;
+		return ret;
 	}
 
 	bci->usb.name = "twl4030_usb";
@@ -604,10 +604,10 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 
 	bci->usb_reg = regulator_get(bci->dev, "bci3v1");
 
-	ret = power_supply_register(&pdev->dev, &bci->usb);
+	ret = devm_power_supply_register(&pdev->dev, &bci->usb);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
-		goto fail_register_usb;
+		return ret;
 	}
 
 	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
@@ -616,7 +616,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_chg, ret);
-		goto fail;
+		return ret;
 	}
 
 	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
@@ -624,7 +624,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_bci, ret);
-		goto fail;
+		return ret;
 	}
 
 	INIT_WORK(&bci->work, twl4030_bci_usb_work);
@@ -647,7 +647,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 			       TWL4030_INTERRUPTS_BCIIMR1A);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
-		goto fail;
+		return ret;
 	}
 
 	reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
@@ -665,16 +665,6 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		twl4030_charger_enable_backup(0, 0);
 
 	return 0;
-
-fail:
-	power_supply_unregister(&bci->usb);
-fail_register_usb:
-	power_supply_unregister(&bci->ac);
-fail_register_ac:
-fail_no_battery:
-	kfree(bci);
-
-	return ret;
 }
 
 static int __exit twl4030_bci_remove(struct platform_device *pdev)
@@ -691,10 +681,6 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR2A);
 
-	power_supply_unregister(&bci->usb);
-	power_supply_unregister(&bci->ac);
-	kfree(bci);
-
 	return 0;
 }
 



^ permalink raw reply related

* [PATCH 03/14] twl4030_charger: use runtime_pm to keep usb phy active while charging.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

The twl4030 usb phy needs to be active while we are using
the USB VBUS as a current source for charging.
In particular, the usb3v1 regulator must be enabled and the
PHY_PWR_PHYPWD bit must be set to keep the phy powered.

commit ab37813f4093a5f59cb8e083cde277289dc72ed3
    twl4030_charger: Allow charger to control the regulator that feeds it

Gave the charger control over the regulator, but didn't resolve
the PHY_PWR_PHYPWD issue.

Now that both of these are controlled by runtime_pm in
phy-twl4030-usb, we can simply take a runtime_pm reference to the USB
phy whenever the charger wants to use it as a current source.

So this patch reverts the above commit, and adds the necessary
runtime_pm calls.

Acked-by: Lee Jones <lee.jones@linaro.org>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/mfd/twl-core.c          |    9 ++++-----
 drivers/power/twl4030_charger.c |   18 +++++-------------
 2 files changed, 9 insertions(+), 18 deletions(-)

diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c
index 489674a2497e..831696ee2472 100644
--- a/drivers/mfd/twl-core.c
+++ b/drivers/mfd/twl-core.c
@@ -788,9 +788,8 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
 		static struct regulator_consumer_supply usb1v8 = {
 			.supply =	"usb1v8",
 		};
-		static struct regulator_consumer_supply usb3v1[] = {
-			{ .supply =	"usb3v1" },
-			{ .supply =	"bci3v1" },
+		static struct regulator_consumer_supply usb3v1 = {
+			.supply =	"usb3v1",
 		};
 
 	/* First add the regulators so that they can be used by transceiver */
@@ -818,7 +817,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
 				return PTR_ERR(child);
 
 			child = add_regulator_linked(TWL4030_REG_VUSB3V1,
-						      &usb_fixed, usb3v1, 2,
+						      &usb_fixed, &usb3v1, 1,
 						      features);
 			if (IS_ERR(child))
 				return PTR_ERR(child);
@@ -838,7 +837,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
 		if (IS_ENABLED(CONFIG_REGULATOR_TWL4030) && child) {
 			usb1v5.dev_name = dev_name(child);
 			usb1v8.dev_name = dev_name(child);
-			usb3v1[0].dev_name = dev_name(child);
+			usb3v1.dev_name = dev_name(child);
 		}
 	}
 
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 7b6c4000cd30..94c7a4d9400a 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -22,7 +22,6 @@
 #include <linux/power_supply.h>
 #include <linux/notifier.h>
 #include <linux/usb/otg.h>
-#include <linux/regulator/machine.h>
 
 #define TWL4030_BCIMSTATEC	0x02
 #define TWL4030_BCIICHG		0x08
@@ -94,7 +93,6 @@ struct twl4030_bci {
 	struct work_struct	work;
 	int			irq_chg;
 	int			irq_bci;
-	struct regulator	*usb_reg;
 	int			usb_enabled;
 
 	unsigned long		event;
@@ -208,7 +206,7 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 {
 	int ret;
 
-	if (enable) {
+	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
 		/* Check for USB charger connected */
 		if (!twl4030_bci_have_vbus(bci))
 			return -ENODEV;
@@ -222,14 +220,9 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 			return -EACCES;
 		}
 
-		/* Need to keep regulator on */
+		/* Need to keep phy powered */
 		if (!bci->usb_enabled) {
-			ret = regulator_enable(bci->usb_reg);
-			if (ret) {
-				dev_err(bci->dev,
-					"Failed to enable regulator\n");
-				return ret;
-			}
+			pm_runtime_get_sync(bci->transceiver->dev);
 			bci->usb_enabled = 1;
 		}
 
@@ -244,7 +237,8 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 	} else {
 		ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
 		if (bci->usb_enabled) {
-			regulator_disable(bci->usb_reg);
+			pm_runtime_mark_last_busy(bci->transceiver->dev);
+			pm_runtime_put_autosuspend(bci->transceiver->dev);
 			bci->usb_enabled = 0;
 		}
 	}
@@ -602,8 +596,6 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props);
 	bci->usb.get_property = twl4030_bci_get_property;
 
-	bci->usb_reg = regulator_get(bci->dev, "bci3v1");
-
 	ret = devm_power_supply_register(&pdev->dev, &bci->usb);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register usb: %d\n", ret);



^ permalink raw reply related

* [PATCH 04/14] twl4030_charger: trust phy to determine when USB power is available.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

The usb phy driver already determines when VBUS is available,
so repeating the test in the charger driver is pointless duplication.

On probe, process the last event from the phy, and from then on,
do whatever the phy tells us without double-checking.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |   33 ++++++---------------------------
 1 file changed, 6 insertions(+), 27 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 94c7a4d9400a..0e62a5ae56a3 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -178,28 +178,6 @@ static int twl4030_is_battery_present(struct twl4030_bci *bci)
 }
 
 /*
- * Check if VBUS power is present
- */
-static int twl4030_bci_have_vbus(struct twl4030_bci *bci)
-{
-	int ret;
-	u8 hwsts;
-
-	ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &hwsts,
-			      TWL4030_PM_MASTER_STS_HW_CONDITIONS);
-	if (ret < 0)
-		return 0;
-
-	dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts);
-
-	/* in case we also have STS_USB_ID, VBUS is driven by TWL itself */
-	if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID))
-		return 1;
-
-	return 0;
-}
-
-/*
  * Enable/Disable USB Charge functionality.
  */
 static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
@@ -207,10 +185,6 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 	int ret;
 
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
-		/* Check for USB charger connected */
-		if (!twl4030_bci_have_vbus(bci))
-			return -ENODEV;
-
 		/*
 		 * Until we can find out what current the device can provide,
 		 * require a module param to enable USB charging.
@@ -649,7 +623,12 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
 
 	twl4030_charger_enable_ac(true);
-	twl4030_charger_enable_usb(bci, true);
+	if (!IS_ERR_OR_NULL(bci->transceiver))
+		twl4030_bci_usb_ncb(&bci->usb_nb,
+				    bci->transceiver->last_event,
+				    NULL);
+	else
+		twl4030_charger_enable_usb(bci, false);
 	if (pdata)
 		twl4030_charger_enable_backup(pdata->bb_uvolt,
 					      pdata->bb_uamp);

^ permalink raw reply related

* [PATCH 05/14] twl4030_charger: split uA calculation into a function.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

We will need this calculation in other places, so
create functions to map between register value and uA value.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |   48 ++++++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 13 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 0e62a5ae56a3..c9a7efbb9141 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -178,6 +178,40 @@ static int twl4030_is_battery_present(struct twl4030_bci *bci)
 }
 
 /*
+ * TI provided formulas:
+ * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
+ * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
+ * Here we use integer approximation of:
+ * CGAIN == 0: val * 1.6618 - 0.85 * 1000
+ * CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2
+ */
+/*
+ * convert twl register value for currents into uA
+ */
+static int regval2ua(int regval, bool cgain)
+{
+	if (cgain)
+		return (regval * 16618 - 8500 * 1000) / 5;
+	else
+		return (regval * 16618 - 8500 * 1000) / 10;
+}
+
+/*
+ * convert uA currents into twl register value
+ */
+static int ua2regval(int ua, bool cgain)
+{
+	int ret;
+	if (cgain)
+		ua /= 2;
+	ret = (ua * 10 + 8500 * 1000) / 16618;
+	/* rounding problems */
+	if (ret < 512)
+		ret = 512;
+	return ret;
+}
+
+/*
  * Enable/Disable USB Charge functionality.
  */
 static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
@@ -366,14 +400,6 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 	return NOTIFY_OK;
 }
 
-/*
- * TI provided formulas:
- * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
- * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
- * Here we use integer approximation of:
- * CGAIN == 0: val * 1.6618 - 0.85
- * CGAIN == 1: (val * 1.6618 - 0.85) * 2
- */
 static int twl4030_charger_get_current(void)
 {
 	int curr;
@@ -388,11 +414,7 @@ static int twl4030_charger_get_current(void)
 	if (ret)
 		return ret;
 
-	ret = (curr * 16618 - 850 * 10000) / 10;
-	if (bcictl1 & TWL4030_CGAIN)
-		ret *= 2;
-
-	return ret;
+	return regval2ua(curr, bcictl1 & TWL4030_CGAIN);
 }
 
 /*

^ permalink raw reply related

* [PATCH 07/14] twl4030_charger: distinguish between USB current and 'AC' current
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

The twl4030 charger has two current sources, 'USB' and 'AC'
(presumably "Accessory Charger").

If 'AC' is providing current, we should set the current limit
differently to when it isn't (and so USB is used).
So split 'cur' into 'usb_cur' and 'ac_cur' and use accordingly.

Now we must review the current setting on any interrupt or USB
event which might indicate that the charger-source has changed.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |   36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 02d677ef63e2..37f2f40991ee 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -22,6 +22,7 @@
 #include <linux/power_supply.h>
 #include <linux/notifier.h>
 #include <linux/usb/otg.h>
+#include <linux/i2c/twl4030-madc.h>
 
 #define TWL4030_BCIMSTATEC	0x02
 #define TWL4030_BCIICHG		0x08
@@ -101,10 +102,13 @@ struct twl4030_bci {
 	int			usb_enabled;
 
 	/*
-	 * ichg values in uA. If any are 'large', we set CGAIN to
-	 * '1' which doubles the range for half the precision.
+	 * ichg_* and *_cur values in uA. If any are 'large', we set
+	 * CGAIN to '1' which doubles the range for half the
+	 * precision.
 	 */
-	unsigned int		ichg_eoc, ichg_lo, ichg_hi, cur;
+	unsigned int		ichg_eoc, ichg_lo, ichg_hi;
+	unsigned int		usb_cur, ac_cur;
+	bool			ac_is_active;
 
 	unsigned long		event;
 };
@@ -225,11 +229,24 @@ static int ua2regval(int ua, bool cgain)
 static int twl4030_charger_update_current(struct twl4030_bci *bci)
 {
 	int status;
+	int cur;
 	unsigned reg, cur_reg;
 	u8 bcictl1, oldreg, fullreg;
 	bool cgain = false;
 	u8 boot_bci;
 
+	/*
+	 * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
+	 * and AC is enabled, set current for 'ac'
+	 */
+	if (twl4030_get_madc_conversion(11) > 4500) {
+		cur = bci->ac_cur;
+		bci->ac_is_active = true;
+	} else {
+		cur = bci->usb_cur;
+		bci->ac_is_active = false;
+	}
+
 	/* First, check thresholds and see if cgain is needed */
 	if (bci->ichg_eoc >= 200000)
 		cgain = true;
@@ -237,7 +254,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 		cgain = true;
 	if (bci->ichg_hi >= 820000)
 		cgain = true;
-	if (bci->cur > 852000)
+	if (cur > 852000)
 		cgain = true;
 
 	status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
@@ -318,7 +335,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 	 * And finally, set the current.  This is stored in
 	 * two registers.
 	 */
-	reg = ua2regval(bci->cur, cgain);
+	reg = ua2regval(cur, cgain);
 	/* we have only 10 bits */
 	if (reg > 0x3ff)
 		reg = 0x3ff;
@@ -371,6 +388,8 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
 
+		twl4030_charger_update_current(bci);
+
 		/* Need to keep phy powered */
 		if (!bci->usb_enabled) {
 			pm_runtime_get_sync(bci->transceiver->dev);
@@ -463,6 +482,7 @@ static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
 	struct twl4030_bci *bci = arg;
 
 	dev_dbg(bci->dev, "CHG_PRES irq\n");
+	twl4030_charger_update_current(bci);
 	power_supply_changed(&bci->ac);
 	power_supply_changed(&bci->usb);
 
@@ -495,6 +515,7 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
 		power_supply_changed(&bci->ac);
 		power_supply_changed(&bci->usb);
 	}
+	twl4030_charger_update_current(bci);
 
 	/* various monitoring events, for now we just log them here */
 	if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
@@ -708,10 +729,11 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->ichg_eoc = 80100; /* Stop charging when current drops to here */
 	bci->ichg_lo = 241000; /* Low threshold */
 	bci->ichg_hi = 500000; /* High threshold */
+	bci->ac_cur = 500000; /* 500mA */
 	if (allow_usb)
-		bci->cur = 500000;  /* 500mA */
+		bci->usb_cur = 500000;  /* 500mA */
 	else
-		bci->cur = 100000;  /* 100mA */
+		bci->usb_cur = 100000;  /* 100mA */
 
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);

^ permalink raw reply related

* [PATCH 06/14] twl4030_charger: allow fine control of charger current.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

The twl4030 allows control of the incoming current.
Part of this control is a 'CGAIN' setting which doubles
the range for half the precision.  This control affects
several different current setting, so all need to be updated
at once when CGAIN is changed.

With this patch, all of these current setting are managed
by the driver, but most are left at their default settings.

The current drawn is set to 500mA if the allow_usb module parameter is
set, and to 100mA otherwise.
More fine control will appear in later patches.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |  168 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 160 insertions(+), 8 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index c9a7efbb9141..02d677ef63e2 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -31,6 +31,11 @@
 #define TWL4030_BCIMFSTS4	0x10
 #define TWL4030_BCICTL1		0x23
 #define TWL4030_BB_CFG		0x12
+#define TWL4030_BCIIREF1	0x27
+#define TWL4030_BCIIREF2	0x28
+#define TWL4030_BCIMFKEY	0x11
+#define TWL4030_BCIMFTH8	0x1d
+#define TWL4030_BCIMFTH9	0x1e
 
 #define TWL4030_BCIMFSTS1	0x01
 
@@ -95,6 +100,12 @@ struct twl4030_bci {
 	int			irq_bci;
 	int			usb_enabled;
 
+	/*
+	 * ichg values in uA. If any are 'large', we set CGAIN to
+	 * '1' which doubles the range for half the precision.
+	 */
+	unsigned int		ichg_eoc, ichg_lo, ichg_hi, cur;
+
 	unsigned long		event;
 };
 
@@ -211,6 +222,146 @@ static int ua2regval(int ua, bool cgain)
 	return ret;
 }
 
+static int twl4030_charger_update_current(struct twl4030_bci *bci)
+{
+	int status;
+	unsigned reg, cur_reg;
+	u8 bcictl1, oldreg, fullreg;
+	bool cgain = false;
+	u8 boot_bci;
+
+	/* First, check thresholds and see if cgain is needed */
+	if (bci->ichg_eoc >= 200000)
+		cgain = true;
+	if (bci->ichg_lo >= 400000)
+		cgain = true;
+	if (bci->ichg_hi >= 820000)
+		cgain = true;
+	if (bci->cur > 852000)
+		cgain = true;
+
+	status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+	if (status < 0)
+		return status;
+	if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci,
+			    TWL4030_PM_MASTER_BOOT_BCI) < 0)
+		boot_bci = 0;
+	boot_bci &= 7;
+
+	if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN))
+		/* Need to turn for charging while we change the
+		 * CGAIN bit.  Leave it off while everything is
+		 * updated.
+		 */
+		twl4030_clear_set_boot_bci(boot_bci, 0);
+
+	/*
+	 * For ichg_eoc, the hardware only supports reg values matching
+	 * 100XXXX000, and requires the XXXX be stored in the high nibble
+	 * of TWL4030_BCIMFTH8
+	 */
+	reg = ua2regval(bci->ichg_eoc, cgain);
+	if (reg > 0x278)
+		reg = 0x278;
+	if (reg < 0x200)
+		reg = 0x200;
+	reg = (reg >> 3) & 0xf;
+	fullreg = reg << 4;
+
+	/*
+	 * For ichg_lo, reg value must match 10XXXX0000.
+	 * XXXX is stored in low nibble of TWL4030_BCIMFTH8
+	 */
+	reg = ua2regval(bci->ichg_lo, cgain);
+	if (reg > 0x2F0)
+		reg = 0x2F0;
+	if (reg < 0x200)
+		reg = 0x200;
+	reg = (reg >> 4) & 0xf;
+	fullreg |= reg;
+
+	/* ichg_eoc and ichg_lo live in same register */
+	status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg);
+	if (status < 0)
+		return status;
+	if (oldreg != fullreg) {
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+				 fullreg, TWL4030_BCIMFTH8);
+	}
+
+	/* ichg_hi threshold must be 1XXXX01100 (I think) */
+	reg = ua2regval(bci->ichg_hi, cgain);
+	if (reg > 0x3E0)
+		reg = 0x3E0;
+	if (reg < 0x200)
+		reg = 0x200;
+	fullreg = (reg >> 5) & 0xF;
+	fullreg <<= 4;
+	status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg);
+	if (status < 0)
+		return status;
+	if ((oldreg & 0xF0) != fullreg) {
+		fullreg |= (oldreg & 0x0F);
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+				 fullreg, TWL4030_BCIMFTH9);
+	}
+
+	/*
+	 * And finally, set the current.  This is stored in
+	 * two registers.
+	 */
+	reg = ua2regval(bci->cur, cgain);
+	/* we have only 10 bits */
+	if (reg > 0x3ff)
+		reg = 0x3ff;
+	status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg);
+	if (status < 0)
+		return status;
+	cur_reg = oldreg;
+	status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg);
+	if (status < 0)
+		return status;
+	cur_reg |= oldreg << 8;
+	if (reg != oldreg) {
+		/* disable write protection for one write access for
+		 * BCIIREF */
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+					  (reg & 0x100) ? 3 : 2,
+					  TWL4030_BCIIREF2);
+		if (status < 0)
+			return status;
+		/* disable write protection for one write access for
+		 * BCIIREF */
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+					  reg & 0xff,
+					  TWL4030_BCIIREF1);
+	}
+	if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) {
+		/* Flip CGAIN and re-enable charging */
+		bcictl1 ^= TWL4030_CGAIN;
+		twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+				 bcictl1, TWL4030_BCICTL1);
+		twl4030_clear_set_boot_bci(0, boot_bci);
+	}
+	return 0;
+}
+
 /*
  * Enable/Disable USB Charge functionality.
  */
@@ -219,14 +370,6 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 	int ret;
 
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
-		/*
-		 * Until we can find out what current the device can provide,
-		 * require a module param to enable USB charging.
-		 */
-		if (!allow_usb) {
-			dev_warn(bci->dev, "USB charging is disabled.\n");
-			return -EACCES;
-		}
 
 		/* Need to keep phy powered */
 		if (!bci->usb_enabled) {
@@ -562,6 +705,14 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (!pdata)
 		pdata = twl4030_bci_parse_dt(&pdev->dev);
 
+	bci->ichg_eoc = 80100; /* Stop charging when current drops to here */
+	bci->ichg_lo = 241000; /* Low threshold */
+	bci->ichg_hi = 500000; /* High threshold */
+	if (allow_usb)
+		bci->cur = 500000;  /* 500mA */
+	else
+		bci->cur = 100000;  /* 100mA */
+
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);
 	bci->irq_bci = platform_get_irq(pdev, 1);
@@ -644,6 +795,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (ret < 0)
 		dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
 
+	twl4030_charger_update_current(bci);
 	twl4030_charger_enable_ac(true);
 	if (!IS_ERR_OR_NULL(bci->transceiver))
 		twl4030_bci_usb_ncb(&bci->usb_nb,

^ permalink raw reply related

* [PATCH 08/14] twl4030_charger: allow max_current to be managed via sysfs.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

'max_current' sysfs attributes are created which allow the
max to be set.
Whenever a current source changes, the default is restored.
This will be followed by a uevent, so user-space can decide to
update again.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 .../ABI/testing/sysfs-class-power-twl4030          |   15 ++++
 drivers/power/twl4030_charger.c                    |   72 ++++++++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-twl4030

diff --git a/Documentation/ABI/testing/sysfs-class-power-twl4030 b/Documentation/ABI/testing/sysfs-class-power-twl4030
new file mode 100644
index 000000000000..06092209d851
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-power-twl4030
@@ -0,0 +1,15 @@
+What: /sys/class/power_supply/twl4030_ac/max_current
+      /sys/class/power_supply/twl4030_usb/max_current
+Description:
+	Read/Write limit on current which which may
+	be drawn from the ac (Accessory Charger) or
+	USB port.
+
+	Value is in micro-Amps.
+
+	Value is set automatically to an appropriate
+	value when a cable is plugged on unplugged.
+
+	Value can the set by writing to the attribute.
+	The change will only persist until the next
+	plug event.  These event are reported via udev.
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 37f2f40991ee..df031b0123d0 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -482,6 +482,8 @@ static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
 	struct twl4030_bci *bci = arg;
 
 	dev_dbg(bci->dev, "CHG_PRES irq\n");
+	/* reset current on each 'plug' event */
+	bci->ac_cur = 500000;
 	twl4030_charger_update_current(bci);
 	power_supply_changed(&bci->ac);
 	power_supply_changed(&bci->usb);
@@ -536,6 +538,63 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
 	return IRQ_HANDLED;
 }
 
+/*
+ * Provide "max_current" attribute in sysfs.
+ */
+static ssize_t
+twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t n)
+{
+	struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+	int cur = 0;
+	int status = 0;
+	status = kstrtoint(buf, 10, &cur);
+	if (status)
+		return status;
+	if (cur < 0)
+		return -EINVAL;
+	if (dev == bci->ac.dev)
+		bci->ac_cur = cur;
+	else
+		bci->usb_cur = cur;
+
+	twl4030_charger_update_current(bci);
+	return n;
+}
+
+/*
+ * sysfs max_current show
+ */
+static ssize_t twl4030_bci_max_current_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	int status = 0;
+	int cur = -1;
+	u8 bcictl1;
+	struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+
+	if (dev == bci->ac.dev) {
+		if (!bci->ac_is_active)
+			cur = bci->ac_cur;
+	} else {
+		if (bci->ac_is_active)
+			cur = bci->usb_cur;
+	}
+	if (cur < 0) {
+		cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1);
+		if (cur < 0)
+			return cur;
+		status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+		if (status < 0)
+			return status;
+		cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN);
+	}
+	return scnprintf(buf, PAGE_SIZE, "%u\n", cur);
+}
+
+static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show,
+			twl4030_bci_max_current_store);
+
 static void twl4030_bci_usb_work(struct work_struct *data)
 {
 	struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
@@ -558,6 +617,12 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 
 	dev_dbg(bci->dev, "OTG notify %lu\n", val);
 
+	/* reset current on each 'plug' event */
+	if (allow_usb)
+		bci->usb_cur = 500000;
+	else
+		bci->usb_cur = 100000;
+
 	bci->event = val;
 	schedule_work(&bci->work);
 
@@ -818,6 +883,11 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
 
 	twl4030_charger_update_current(bci);
+	if (device_create_file(bci->usb.dev, &dev_attr_max_current))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
+	if (device_create_file(bci->ac.dev, &dev_attr_max_current))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
+
 	twl4030_charger_enable_ac(true);
 	if (!IS_ERR_OR_NULL(bci->transceiver))
 		twl4030_bci_usb_ncb(&bci->usb_nb,
@@ -842,6 +912,8 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 	twl4030_charger_enable_usb(bci, false);
 	twl4030_charger_enable_backup(0, 0);
 
+	device_remove_file(bci->usb.dev, &dev_attr_max_current);
+	device_remove_file(bci->ac.dev, &dev_attr_max_current);
 	/* mask interrupts */
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR1A);

^ permalink raw reply related

* [PATCH 09/14] twl4030_charger: only draw USB current as negotiated with host.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

If the phy has been told what current it can draw, it tells us
and now we use that number.

Note that 'vbus_draw' is in mA, while usb_cur is in uA.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |    5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index df031b0123d0..2042e7619954 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -602,6 +602,7 @@ static void twl4030_bci_usb_work(struct work_struct *data)
 	switch (bci->event) {
 	case USB_EVENT_VBUS:
 	case USB_EVENT_CHARGER:
+	case USB_EVENT_ENUMERATED:
 		twl4030_charger_enable_usb(bci, true);
 		break;
 	case USB_EVENT_NONE:
@@ -614,6 +615,7 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 			       void *priv)
 {
 	struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, usb_nb);
+	unsigned int *vbus_draw = priv;
 
 	dev_dbg(bci->dev, "OTG notify %lu\n", val);
 
@@ -624,6 +626,9 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 		bci->usb_cur = 100000;
 
 	bci->event = val;
+	if (val == USB_EVENT_ENUMERATED && vbus_draw &&
+	    *vbus_draw * 1000 > bci->usb_cur)
+		bci->usb_cur = *vbus_draw * 1000;
 	schedule_work(&bci->work);
 
 	return NOTIFY_OK;

^ permalink raw reply related

* [PATCH 10/14] twl4030_charger: enable manual enable/disable of usb charging.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

'off' or 'auto' to

 /sys/class/power/twl4030_usb/mode

will now enable or disable charging from USB port.  Normally this is
enabled on 'plug' and disabled on 'unplug'.
Unplug will still disable charging.  'plug' will only enable it if
'auto' if selected.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 .../ABI/testing/sysfs-class-power-twl4030          |   11 ++++
 drivers/power/twl4030_charger.c                    |   59 ++++++++++++++++++++
 2 files changed, 70 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power-twl4030 b/Documentation/ABI/testing/sysfs-class-power-twl4030
index 06092209d851..390db7a55231 100644
--- a/Documentation/ABI/testing/sysfs-class-power-twl4030
+++ b/Documentation/ABI/testing/sysfs-class-power-twl4030
@@ -13,3 +13,14 @@ Description:
 	Value can the set by writing to the attribute.
 	The change will only persist until the next
 	plug event.  These event are reported via udev.
+
+
+What: /sys/class/power_supply/twl4030_usb/mode
+Description:
+	Changing mode for USB port.
+	Writing to this can disable charging.
+
+	Possible values are:
+		"auto" - draw power as appropriate for detected
+			 power source and battery status.
+		"off"  - do not draw any power.
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 2042e7619954..802cdd6d3e00 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -109,10 +109,16 @@ struct twl4030_bci {
 	unsigned int		ichg_eoc, ichg_lo, ichg_hi;
 	unsigned int		usb_cur, ac_cur;
 	bool			ac_is_active;
+	int			usb_mode; /* charging mode requested */
+#define	CHARGE_OFF	0
+#define	CHARGE_AUTO	1
 
 	unsigned long		event;
 };
 
+/* strings for 'usb_mode' values */
+static char *modes[] = { "off", "auto" };
+
 /*
  * clear and set bits on an given register on a given module
  */
@@ -386,6 +392,8 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 {
 	int ret;
 
+	if (bci->usb_mode == CHARGE_OFF)
+		enable = false;
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
 
 		twl4030_charger_update_current(bci);
@@ -634,6 +642,53 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 	return NOTIFY_OK;
 }
 
+/*
+ * sysfs charger enabled store
+ */
+static ssize_t
+twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t n)
+{
+	struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+	int mode;
+	int status;
+
+	if (sysfs_streq(buf, modes[0]))
+		mode = 0;
+	else if (sysfs_streq(buf, modes[1]))
+		mode = 1;
+	else
+		return -EINVAL;
+	twl4030_charger_enable_usb(bci, false);
+	bci->usb_mode = mode;
+	status = twl4030_charger_enable_usb(bci, true);
+	return (status == 0) ? n : status;
+}
+
+/*
+ * sysfs charger enabled show
+ */
+static ssize_t
+twl4030_bci_mode_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+	int len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(modes); i++)
+		if (bci->usb_mode == i)
+			len += snprintf(buf+len, PAGE_SIZE-len,
+					"[%s] ", modes[i]);
+		else
+			len += snprintf(buf+len, PAGE_SIZE-len,
+					"%s ", modes[i]);
+	buf[len-1] = '\n';
+	return len;
+}
+static DEVICE_ATTR(mode, 0644, twl4030_bci_mode_show,
+		   twl4030_bci_mode_store);
+
 static int twl4030_charger_get_current(void)
 {
 	int curr;
@@ -804,6 +859,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		bci->usb_cur = 500000;  /* 500mA */
 	else
 		bci->usb_cur = 100000;  /* 100mA */
+	bci->usb_mode = CHARGE_AUTO;
 
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);
@@ -890,6 +946,8 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	twl4030_charger_update_current(bci);
 	if (device_create_file(bci->usb.dev, &dev_attr_max_current))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
+	if (device_create_file(bci->usb.dev, &dev_attr_mode))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
 	if (device_create_file(bci->ac.dev, &dev_attr_max_current))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
 
@@ -918,6 +976,7 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 	twl4030_charger_enable_backup(0, 0);
 
 	device_remove_file(bci->usb.dev, &dev_attr_max_current);
+	device_remove_file(bci->usb.dev, &dev_attr_mode);
 	device_remove_file(bci->ac.dev, &dev_attr_max_current);
 	/* mask interrupts */
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,



^ permalink raw reply related

* [PATCH 11/14] twl4030_charger: add software controlled linear charging mode.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, GTA04 owners,
	inux-pm-u79uwXL29TY76Z2rM5mHXA, Pavel Machek,
	linux-omap-u79uwXL29TY76Z2rM5mHXA, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit-wvvUuzkyo1EYVZTmpyfIwg@public.gmane.org>

From: NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>

Add a 'continuous' option for usb charging which enables
the "linear" charging mode of the twl4030.

Linear charging does a good job with not-so-reliable power sources.
Auto mode does not work well as it switches off when voltage drops
momentarily.  Care must be taken not to over-charge.

It was used with a bike hub dynamo since a year or so. In that case
there are automatically charging stops when the cyclist needs a break.

Orignal-by: Andreas Kemnade <andreas-cLv4Z9ELZ06ZuzBka8ofvg@public.gmane.org>
Signed-off-by: NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>
---
 .../ABI/testing/sysfs-class-power-twl4030          |    9 +++
 drivers/power/twl4030_charger.c                    |   55 ++++++++++++++++++--
 2 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-class-power-twl4030 b/Documentation/ABI/testing/sysfs-class-power-twl4030
index 390db7a55231..c27e8ca8c9db 100644
--- a/Documentation/ABI/testing/sysfs-class-power-twl4030
+++ b/Documentation/ABI/testing/sysfs-class-power-twl4030
@@ -24,3 +24,12 @@ Description:
 		"auto" - draw power as appropriate for detected
 			 power source and battery status.
 		"off"  - do not draw any power.
+		"continuous"
+		       - activate mode described as "linear" in
+		         TWL data sheets.  This uses whatever
+			 current is available and doesn't switch off
+			 when voltage drops.
+
+			 This is useful for unstable power sources
+			 such as bicycle dynamo, but care should
+			 be taken that battery is not over-charged.
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 802cdd6d3e00..2c8d85102def 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -24,6 +24,8 @@
 #include <linux/usb/otg.h>
 #include <linux/i2c/twl4030-madc.h>
 
+#define TWL4030_BCIMDEN		0x00
+#define TWL4030_BCIMDKEY	0x01
 #define TWL4030_BCIMSTATEC	0x02
 #define TWL4030_BCIICHG		0x08
 #define TWL4030_BCIVAC		0x0a
@@ -35,13 +37,16 @@
 #define TWL4030_BCIIREF1	0x27
 #define TWL4030_BCIIREF2	0x28
 #define TWL4030_BCIMFKEY	0x11
+#define TWL4030_BCIMFEN3	0x14
 #define TWL4030_BCIMFTH8	0x1d
 #define TWL4030_BCIMFTH9	0x1e
+#define TWL4030_BCIWDKEY	0x21
 
 #define TWL4030_BCIMFSTS1	0x01
 
 #define TWL4030_BCIAUTOWEN	BIT(5)
 #define TWL4030_CONFIG_DONE	BIT(4)
+#define TWL4030_CVENAC		BIT(2)
 #define TWL4030_BCIAUTOUSB	BIT(1)
 #define TWL4030_BCIAUTOAC	BIT(0)
 #define TWL4030_CGAIN		BIT(5)
@@ -112,12 +117,13 @@ struct twl4030_bci {
 	int			usb_mode; /* charging mode requested */
 #define	CHARGE_OFF	0
 #define	CHARGE_AUTO	1
+#define	CHARGE_LINEAR	2
 
 	unsigned long		event;
 };
 
 /* strings for 'usb_mode' values */
-static char *modes[] = { "off", "auto" };
+static char *modes[] = { "off", "auto", "continuous" };
 
 /*
  * clear and set bits on an given register on a given module
@@ -404,16 +410,42 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 			bci->usb_enabled = 1;
 		}
 
-		/* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
-		ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB);
-		if (ret < 0)
-			return ret;
+		if (bci->usb_mode == CHARGE_AUTO)
+			/* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
+			ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB);
 
 		/* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
 		ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0,
 			TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4);
+		if (bci->usb_mode == CHARGE_LINEAR) {
+			twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0);
+			/* Watch dog key: WOVF acknowledge */
+			ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33,
+					       TWL4030_BCIWDKEY);
+			/* 0x24 + EKEY6: off mode */
+			ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
+					       TWL4030_BCIMDKEY);
+			/* EKEY2: Linear charge: USB path */
+			ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26,
+					       TWL4030_BCIMDKEY);
+			/* WDKEY5: stop watchdog count */
+			ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3,
+					       TWL4030_BCIWDKEY);
+			/* enable MFEN3 access */
+			ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c,
+					       TWL4030_BCIMFKEY);
+			 /* ICHGEOCEN - end-of-charge monitor (current < 80mA)
+			  *                      (charging continues)
+			  * ICHGLOWEN - current level monitor (charge continues)
+			  * don't monitor over-current or heat save
+			  */
+			ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0,
+					       TWL4030_BCIMFEN3);
+		}
 	} else {
 		ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
+		ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
+					TWL4030_BCIMDKEY);
 		if (bci->usb_enabled) {
 			pm_runtime_mark_last_busy(bci->transceiver->dev);
 			pm_runtime_put_autosuspend(bci->transceiver->dev);
@@ -657,6 +689,8 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
 		mode = 0;
 	else if (sysfs_streq(buf, modes[1]))
 		mode = 1;
+	else if (sysfs_streq(buf, modes[2]))
+		mode = 2;
 	else
 		return -EINVAL;
 	twl4030_charger_enable_usb(bci, false);
@@ -755,6 +789,17 @@ static int twl4030_bci_get_property(struct power_supply *psy,
 		is_charging = state & TWL4030_MSTATEC_USB;
 	else
 		is_charging = state & TWL4030_MSTATEC_AC;
+	if (!is_charging) {
+		u8 s;
+		twl4030_bci_read(TWL4030_BCIMDEN, &s);
+		if (psy->type == POWER_SUPPLY_TYPE_USB)
+			is_charging = s & 1;
+		else
+			is_charging = s & 2;
+		if (is_charging)
+			/* A little white lie */
+			state = TWL4030_MSTATEC_QUICK1;
+	}
 
 	switch (psp) {
 	case POWER_SUPPLY_PROP_STATUS:

^ permalink raw reply related

* [PATCH 12/14] twl4030_charger: add ac/mode to match usb/mode
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

This allows AC charging to be turned off, much like usb charging.
"continuous" mode is not available though.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: NeilBrown <neilb@suse.de>
---
 .../ABI/testing/sysfs-class-power-twl4030          |   10 ++++++
 drivers/power/twl4030_charger.c                    |   35 +++++++++++++++-----
 2 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-class-power-twl4030 b/Documentation/ABI/testing/sysfs-class-power-twl4030
index c27e8ca8c9db..35a211b9a172 100644
--- a/Documentation/ABI/testing/sysfs-class-power-twl4030
+++ b/Documentation/ABI/testing/sysfs-class-power-twl4030
@@ -33,3 +33,13 @@ Description:
 			 This is useful for unstable power sources
 			 such as bicycle dynamo, but care should
 			 be taken that battery is not over-charged.
+
+What: /sys/class/power_supply/twl4030_ac/mode
+Description:
+	Changing mode for 'ac' port.
+	Writing to this can disable charging.
+
+	Possible values are:
+		"auto" - draw power as appropriate for detected
+			 power source and battery status.
+		"off"  - do not draw any power.
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 2c8d85102def..73c7cd96ebc1 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -114,7 +114,7 @@ struct twl4030_bci {
 	unsigned int		ichg_eoc, ichg_lo, ichg_hi;
 	unsigned int		usb_cur, ac_cur;
 	bool			ac_is_active;
-	int			usb_mode; /* charging mode requested */
+	int			usb_mode, ac_mode; /* charging mode requested */
 #define	CHARGE_OFF	0
 #define	CHARGE_AUTO	1
 #define	CHARGE_LINEAR	2
@@ -459,10 +459,13 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 /*
  * Enable/Disable AC Charge funtionality.
  */
-static int twl4030_charger_enable_ac(bool enable)
+static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable)
 {
 	int ret;
 
+	if (bci->ac_mode == CHARGE_OFF)
+		enable = false;
+
 	if (enable)
 		ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC);
 	else
@@ -693,9 +696,17 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
 		mode = 2;
 	else
 		return -EINVAL;
-	twl4030_charger_enable_usb(bci, false);
-	bci->usb_mode = mode;
-	status = twl4030_charger_enable_usb(bci, true);
+	if (dev == bci->ac.dev) {
+		if (mode == 2)
+			return -EINVAL;
+		twl4030_charger_enable_ac(bci, false);
+		bci->ac_mode = mode;
+		status = twl4030_charger_enable_ac(bci, true);
+	} else {
+		twl4030_charger_enable_usb(bci, false);
+		bci->usb_mode = mode;
+		status = twl4030_charger_enable_usb(bci, true);
+	}
 	return (status == 0) ? n : status;
 }
 
@@ -709,9 +720,13 @@ twl4030_bci_mode_show(struct device *dev,
 	struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
 	int len = 0;
 	int i;
+	int mode = bci->usb_mode;
+
+	if (dev == bci->ac.dev)
+		mode = bci->ac_mode;
 
 	for (i = 0; i < ARRAY_SIZE(modes); i++)
-		if (bci->usb_mode == i)
+		if (mode == i)
 			len += snprintf(buf+len, PAGE_SIZE-len,
 					"[%s] ", modes[i]);
 		else
@@ -905,6 +920,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	else
 		bci->usb_cur = 100000;  /* 100mA */
 	bci->usb_mode = CHARGE_AUTO;
+	bci->ac_mode = CHARGE_AUTO;
 
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);
@@ -993,10 +1009,12 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
 	if (device_create_file(bci->usb.dev, &dev_attr_mode))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
+	if (device_create_file(bci->ac.dev, &dev_attr_mode))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
 	if (device_create_file(bci->ac.dev, &dev_attr_max_current))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
 
-	twl4030_charger_enable_ac(true);
+	twl4030_charger_enable_ac(bci, true);
 	if (!IS_ERR_OR_NULL(bci->transceiver))
 		twl4030_bci_usb_ncb(&bci->usb_nb,
 				    bci->transceiver->last_event,
@@ -1016,13 +1034,14 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 {
 	struct twl4030_bci *bci = platform_get_drvdata(pdev);
 
-	twl4030_charger_enable_ac(false);
+	twl4030_charger_enable_ac(bci, false);
 	twl4030_charger_enable_usb(bci, false);
 	twl4030_charger_enable_backup(0, 0);
 
 	device_remove_file(bci->usb.dev, &dev_attr_max_current);
 	device_remove_file(bci->usb.dev, &dev_attr_mode);
 	device_remove_file(bci->ac.dev, &dev_attr_max_current);
+	device_remove_file(bci->ac.dev, &dev_attr_mode);
 	/* mask interrupts */
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR1A);

^ permalink raw reply related

* [PATCH 13/14] twl4030_charger: Increase current carefully while watching voltage.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

The USB Battery Charging spec (BC1.2) suggests a dedicated
charging port can deliver from 0.5 to 5.0A at between 4.75 and 5.25
volts.

To choose the "correct" current voltage setting requires a trial
and error approach: try to draw current and see if the voltage drops
too low.

Even with a configured Standard Downstream Port, it may not be possible
to reliably pull 500mA - depending on cable quality and source
quality I have reports of charging failure due to the voltage dropping
too low.

To address both these concerns, this patch introduce incremental
current setting.
The current pull from VBUS is increased in steps of 20mA every 100ms
until the target is reached or until the measure voltage drops below
4.75V.  If the voltage does go too low, the target current is reduced
by 20mA and kept there.

This applies to currents selected automatically, or to values
set via sysfs.  So setting a large value will cause the maximum
available to be used - up to the limit of 1.7A imposed by the
hardware.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |   68 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 60 insertions(+), 8 deletions(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index 73c7cd96ebc1..d0199495dddc 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -119,6 +119,18 @@ struct twl4030_bci {
 #define	CHARGE_AUTO	1
 #define	CHARGE_LINEAR	2
 
+	/* When setting the USB current we slowly increase the
+	 * requested current until target is reached or the voltage
+	 * drops below 4.75V.  In the latter case we step back one
+	 * step.
+	 */
+	unsigned int		usb_cur_target;
+	struct delayed_work	current_worker;
+#define	USB_CUR_STEP	20000	/* 20mA at a time */
+#define	USB_MIN_VOLT	4750000	/* 4.75V */
+#define	USB_CUR_DELAY	msecs_to_jiffies(100)
+#define	USB_MAX_CURRENT	1700000 /* TWL4030 caps at 1.7A */
+
 	unsigned long		event;
 };
 
@@ -257,6 +269,12 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 	} else {
 		cur = bci->usb_cur;
 		bci->ac_is_active = false;
+		if (cur > bci->usb_cur_target) {
+			cur = bci->usb_cur_target;
+			bci->usb_cur = cur;
+		}
+		if (cur < bci->usb_cur_target)
+			schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
 	}
 
 	/* First, check thresholds and see if cgain is needed */
@@ -391,6 +409,38 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 	return 0;
 }
 
+static void twl4030_current_worker(struct work_struct *data)
+{
+	int v;
+	int res;
+	struct twl4030_bci *bci = container_of(data, struct twl4030_bci,
+					       current_worker.work);
+
+	res = twl4030bci_read_adc_val(TWL4030_BCIVBUS);
+	if (res < 0)
+		v = 0;
+	else
+		/* BCIVBUS uses ADCIN8, 7/1023 V/step */
+		v = res * 6843;
+
+	printk("v=%d cur=%d target=%d\n", v, bci->usb_cur,
+	       bci->usb_cur_target);
+
+	if (v < USB_MIN_VOLT) {
+		/* Back up and stop adjusting. */
+		bci->usb_cur -= USB_CUR_STEP;
+		bci->usb_cur_target = bci->usb_cur;
+	} else if (bci->usb_cur >= bci->usb_cur_target ||
+		   bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) {
+		/* Reached target and voltage is OK - stop */
+		return;
+	} else {
+		bci->usb_cur += USB_CUR_STEP;
+		schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
+	}
+	twl4030_charger_update_current(bci);
+}
+
 /*
  * Enable/Disable USB Charge functionality.
  */
@@ -451,6 +501,7 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 			pm_runtime_put_autosuspend(bci->transceiver->dev);
 			bci->usb_enabled = 0;
 		}
+		bci->usb_cur = 0;
 	}
 
 	return ret;
@@ -599,7 +650,7 @@ twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr,
 	if (dev == bci->ac.dev)
 		bci->ac_cur = cur;
 	else
-		bci->usb_cur = cur;
+		bci->usb_cur_target = cur;
 
 	twl4030_charger_update_current(bci);
 	return n;
@@ -621,7 +672,7 @@ static ssize_t twl4030_bci_max_current_show(struct device *dev,
 			cur = bci->ac_cur;
 	} else {
 		if (bci->ac_is_active)
-			cur = bci->usb_cur;
+			cur = bci->usb_cur_target;
 	}
 	if (cur < 0) {
 		cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1);
@@ -664,14 +715,14 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 
 	/* reset current on each 'plug' event */
 	if (allow_usb)
-		bci->usb_cur = 500000;
+		bci->usb_cur_target = 500000;
 	else
-		bci->usb_cur = 100000;
+		bci->usb_cur_target = 100000;
 
 	bci->event = val;
 	if (val == USB_EVENT_ENUMERATED && vbus_draw &&
-	    *vbus_draw * 1000 > bci->usb_cur)
-		bci->usb_cur = *vbus_draw * 1000;
+	    *vbus_draw * 1000 > bci->usb_cur_target)
+		bci->usb_cur_target = *vbus_draw * 1000;
 	schedule_work(&bci->work);
 
 	return NOTIFY_OK;
@@ -916,9 +967,9 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->ichg_hi = 500000; /* High threshold */
 	bci->ac_cur = 500000; /* 500mA */
 	if (allow_usb)
-		bci->usb_cur = 500000;  /* 500mA */
+		bci->usb_cur_target = 500000;  /* 500mA */
 	else
-		bci->usb_cur = 100000;  /* 100mA */
+		bci->usb_cur_target = 100000;  /* 100mA */
 	bci->usb_mode = CHARGE_AUTO;
 	bci->ac_mode = CHARGE_AUTO;
 
@@ -976,6 +1027,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	}
 
 	INIT_WORK(&bci->work, twl4030_bci_usb_work);
+	INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
 
 	bci->usb_nb.notifier_call = twl4030_bci_usb_ncb;
 	if (bci->dev->of_node) {

^ permalink raw reply related

* [PATCH 14/14] twl4030_charger: assume a 'charger' can supply maximum current.
From: NeilBrown @ 2015-03-22 23:20 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: NeilBrown, linux-api, linux-kernel, GTA04 owners, inux-pm,
	Pavel Machek, linux-omap, Lee Jones
In-Reply-To: <20150322231635.23789.70149.stgit@notabene.brown>

From: NeilBrown <neilb@suse.de>

If it cannot, we will stop pulling more current when voltage drops.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 drivers/power/twl4030_charger.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index d0199495dddc..6f0dc971d84d 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -694,8 +694,10 @@ static void twl4030_bci_usb_work(struct work_struct *data)
 	struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
 
 	switch (bci->event) {
-	case USB_EVENT_VBUS:
 	case USB_EVENT_CHARGER:
+		bci->usb_cur = USB_MAX_CURRENT;
+		/* FALL THROUGH */
+	case USB_EVENT_VBUS:
 	case USB_EVENT_ENUMERATED:
 		twl4030_charger_enable_usb(bci, true);
 		break;



^ permalink raw reply related

* Re: [PATCH] audit.h: remove the macro AUDIT_ARCH_ARMEB definition
From: Li RongQing @ 2015-03-23  0:51 UTC (permalink / raw)
  To: Paul Moore
  Cc: Eric Paris, linux-audit-H+wXaHxf7aLQT0dZR+AlfA,
	linux-api-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <CAHC9VhR5VcaCtLG9hdVS2gZRWxVmdnpbK+fJwm6wCA8qyLebUQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On Fri, Mar 20, 2015 at 9:29 PM, Paul Moore <paul-r2n+y4ga6xFZroRs9YW3xA@public.gmane.org> wrote:
> On Fri, Mar 20, 2015 at 12:55 AM,  <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>> From: Li RongQing <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>>
>> After 2f9783669 [ARM: 7412/1: audit: use only AUDIT_ARCH_ARM regardless
>> of endianness], no kernel user uses this macro;
>>
>> Keeping this macro, only makes the compiling old version audit [before
>> changeset 931 Improve ARM and AARCH64 support] success, but the audit
>> program can not work with the kernel after 2f9783669 still,
>> since no syscall entry is enabled for AUDIT_ARCH_ARMEB in kernel.
>>
>> so remove it to force to use the latest audit program
>>
>> Signed-off-by: Li RongQing <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> ---
>> other workaround is to define AUDIT_ARCH_ARMEB as AUDIT_ARCH_ARM,
>> but it seems very strange
>>
>>  include/uapi/linux/audit.h | 1 -
>>  1 file changed, 1 deletion(-)
>
> Since this #define lives in the user visible headers I don't want to
> remove it and risk causing a userspace breakage.  Leaving the #define
> in the header, even if it is unused by modern userspace, is harmless.
>
it is harm, when I compile the audit-2.3.2 for a arm machine, whose linux kernel
is 3.14; no compile error, but audit does not work;  since the audit is



>
>
> --
> paul moore
> www.paul-moore.com

^ permalink raw reply

* Re: [PATCH] audit.h: remove the macro AUDIT_ARCH_ARMEB definition
From: Li RongQing @ 2015-03-23  0:55 UTC (permalink / raw)
  To: Paul Moore
  Cc: Eric Paris, linux-audit-H+wXaHxf7aLQT0dZR+AlfA,
	linux-api-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <CAJFZqHxdPEhX+9z-FYUMvTF_6LVgK=gOetq0zT4UTZSgUGRqCQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On Mon, Mar 23, 2015 at 8:51 AM, Li RongQing <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> On Fri, Mar 20, 2015 at 9:29 PM, Paul Moore <paul-r2n+y4ga6xFZroRs9YW3xA@public.gmane.org> wrote:
>> On Fri, Mar 20, 2015 at 12:55 AM,  <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>>> From: Li RongQing <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>>>
>>> After 2f9783669 [ARM: 7412/1: audit: use only AUDIT_ARCH_ARM regardless
>>> of endianness], no kernel user uses this macro;
>>>
>>> Keeping this macro, only makes the compiling old version audit [before
>>> changeset 931 Improve ARM and AARCH64 support] success, but the audit
>>> program can not work with the kernel after 2f9783669 still,
>>> since no syscall entry is enabled for AUDIT_ARCH_ARMEB in kernel.
>>>
>>> so remove it to force to use the latest audit program
>>>
>>> Signed-off-by: Li RongQing <roy.qing.li-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>>> ---
>>> other workaround is to define AUDIT_ARCH_ARMEB as AUDIT_ARCH_ARM,
>>> but it seems very strange
>>>
>>>  include/uapi/linux/audit.h | 1 -
>>>  1 file changed, 1 deletion(-)
>>
>> Since this #define lives in the user visible headers I don't want to
>> remove it and risk causing a userspace breakage.  Leaving the #define
>> in the header, even if it is


it is harm, when I compile the audit-2.3.2 for a arm machine, whose linux kernel
is 3.14; no compile error, but audit does not work;  spend one day debug to find
the root cause is  the audit used MACH_ARMEB, but kernel replaced MACH_ARMEB
 with MACH_ARM

 grep WITH_ARMEB ./lib/machinetab.h -A10
#ifdef WITH_ARMEB
_S(MACH_ARMEB,   "armeb"  )
_S(MACH_ARMEB,   "armv5tejl")
_S(MACH_ARMEB,   "armv5tel")
_S(MACH_ARMEB,   "armv6l")
_S(MACH_ARMEB,   "armv7l")
#endif

removal of MACH_ARMEB will let the user find this issue when compile, not
run.

-Roy

^ permalink raw reply

* Re: [PATCH v2 01/11] stm class: Introduce an abstraction for System Trace Module devices
From: Mathieu Poirier @ 2015-03-23  1:50 UTC (permalink / raw)
  To: Alexander Shishkin
  Cc: Greg Kroah-Hartman, linux-kernel@vger.kernel.org, Paul Bolle,
	peter.lachner, norbert.schulz, keven.boell, yann.fouassier,
	laurent.fert, linux-api, Pratik Patel, Chunyan Zhang, Kaixu Xia
In-Reply-To: <1427056381-27614-2-git-send-email-alexander.shishkin@linux.intel.com>

> +
> +[1] https://software.intel.com/sites/default/files/managed/d3/3c/intel-th-developer-manual.pdf
> +[2] http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0444b/index.html
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index c0cc96bab9..9850ab81cc 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -182,4 +182,6 @@ source "drivers/thunderbolt/Kconfig"
>

>  source "drivers/android/Kconfig"
>
> +source "drivers/hwtracing/stm/Kconfig"
> +
>  endmenu

When the coresight framework and drivers were submitted for review
people asked that I move the Kconfig options to
"arch/arm[64]/kernel.debug", resulting in coresight configurable
options showing up under the "Kernel Hacking" department.  To me the
request was not deprived of logic since if one is dealing with HW
tracing, some serious kernel hacking is likely happening.

Now that the Intel drivers are coming in, that we have a generic STM,
and "drivers/hwtracing" has already been created, we should take a
minute to ponder if tracers for various architecture should go under
"arch/XYX/kernel.debug" or if we should introduce a new "hwtracing"
submenu in the drivers list.

Because of the STM sources (which are bound to grow in numbers) I
_think_ it would be easier to have a new submenu in the drivers list
but I'm not strongly opinionated on the topic.  Please take a minute
to think about it and get back to me with your opinion.  I'd also be
interested to know what other community members think - it's
definitely not the first time this kind of dilemma happens...

Thanks,
Mathieu

> diff --git a/drivers/Makefile b/drivers/Makefile
> index 527a6da8d5..87d7c74e39 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -165,3 +165,4 @@ obj-$(CONFIG_RAS)           += ras/
>  obj-$(CONFIG_THUNDERBOLT)      += thunderbolt/
>  obj-$(CONFIG_CORESIGHT)                += coresight/
>  obj-$(CONFIG_ANDROID)          += android/
> +obj-$(CONFIG_STM)              += hwtracing/stm/
> diff --git a/drivers/hwtracing/stm/Kconfig b/drivers/hwtracing/stm/Kconfig
> new file mode 100644
> index 0000000000..90ed327461
> --- /dev/null
> +++ b/drivers/hwtracing/stm/Kconfig
> @@ -0,0 +1,8 @@
> +config STM
> +       tristate "System Trace Module devices"
> +       help
> +         A System Trace Module (STM) is a device exporting data in System
> +         Trace Protocol (STP) format as defined by MIPI STP standards.
> +         Examples of such devices are Intel Trace Hub and Coresight STM.
> +
> +         Say Y here to enable System Trace Module device support.
> diff --git a/drivers/hwtracing/stm/Makefile b/drivers/hwtracing/stm/Makefile
> new file mode 100644
> index 0000000000..adec701649
> --- /dev/null
> +++ b/drivers/hwtracing/stm/Makefile
> @@ -0,0 +1,3 @@
> +obj-$(CONFIG_STM)      += stm_core.o
> +
> +stm_core-y             := core.o policy.o
> diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c
> new file mode 100644
> index 0000000000..9e82634590
> --- /dev/null
> +++ b/drivers/hwtracing/stm/core.c
> @@ -0,0 +1,897 @@
> +/*
> + * System Trace Module (STM) infrastructure
> + * Copyright (c) 2014, Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + *
> + * STM class implements generic infrastructure for  System Trace Module devices
> + * as defined in MIPI STPv2 specification.
> + */
> +
> +#include <linux/uaccess.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/compat.h>
> +#include <linux/kdev_t.h>
> +#include <linux/srcu.h>
> +#include <linux/slab.h>
> +#include <linux/stm.h>
> +#include <linux/fs.h>
> +#include <linux/mm.h>
> +#include "stm.h"
> +
> +#include <uapi/linux/stm.h>
> +
> +static unsigned int stm_core_up;
> +
> +/*
> + * The SRCU here makes sure that STM device doesn't disappear from under a
> + * stm_source_write() caller, which may want to have as little overhead as
> + * possible.
> + */
> +static struct srcu_struct stm_source_srcu;
> +
> +static ssize_t masters_show(struct device *dev,
> +                           struct device_attribute *attr,
> +                           char *buf)
> +{
> +       struct stm_device *stm = dev_get_drvdata(dev);
> +       int ret;
> +
> +       ret = sprintf(buf, "%u %u\n", stm->data->sw_start, stm->data->sw_end);
> +
> +       return ret;
> +}
> +
> +static DEVICE_ATTR_RO(masters);
> +
> +static ssize_t channels_show(struct device *dev,
> +                            struct device_attribute *attr,
> +                            char *buf)
> +{
> +       struct stm_device *stm = dev_get_drvdata(dev);
> +       int ret;
> +
> +       ret = sprintf(buf, "%u\n", stm->data->sw_nchannels);
> +
> +       return ret;
> +}
> +
> +static DEVICE_ATTR_RO(channels);
> +
> +static struct attribute *stm_attrs[] = {
> +       &dev_attr_masters.attr,
> +       &dev_attr_channels.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group stm_group = {
> +       .attrs  = stm_attrs,
> +};
> +
> +static const struct attribute_group *stm_groups[] = {
> +       &stm_group,
> +       NULL,
> +};
> +
> +static struct class stm_class = {
> +       .name           = "stm",
> +       .dev_groups     = stm_groups,
> +};
> +
> +static int stm_dev_match(struct device *dev, const void *data)
> +{
> +       const char *name = data;
> +
> +       return sysfs_streq(name, dev_name(dev));
> +}
> +
> +/**
> + * stm_find_device() - find stm device by name
> + * @buf:       character buffer containing the name
> + * @len:       length of the name in @buf
> + *
> + * This is called from attributes' store methods, so it will
> + * also trim the trailing newline if necessary.
> + *
> + * Return:     device pointer or null if lookup failed.
> + */
> +struct device *stm_find_device(const char *buf, size_t len)
> +{
> +       if (!stm_core_up)
> +               return NULL;
> +
> +       return class_find_device(&stm_class, NULL, buf, stm_dev_match);
> +}
> +
> +/*
> + * Internally we only care about software-writable masters here, that is the
> + * ones in the range [stm_data->sw_start..stm_data..sw_end], however we need
> + * original master numbers to be visible externally, since they are the ones
> + * that will appear in the STP stream. Thus, the internal bookkeeping uses
> + * $master - stm_data->sw_start to reference master descriptors and such.
> + */
> +
> +#define __stm_master(_s, _m)                           \
> +       ((_s)->masters[(_m) - (_s)->data->sw_start])
> +
> +static inline struct stp_master *
> +stm_master(struct stm_device *stm, unsigned int idx)
> +{
> +       if (idx < stm->data->sw_start || idx > stm->data->sw_end)
> +               return NULL;
> +
> +       return __stm_master(stm, idx);
> +}
> +
> +static int stp_master_alloc(struct stm_device *stm, unsigned int idx)
> +{
> +       struct stp_master *master;
> +       size_t size;
> +
> +       size = ALIGN(stm->data->sw_nchannels, 8) / 8;
> +       size += sizeof(struct stp_master);
> +       master = kzalloc(size, GFP_ATOMIC);
> +       if (!master)
> +               return -ENOMEM;
> +
> +       master->nr_free = stm->data->sw_nchannels;
> +       __stm_master(stm, idx) = master;
> +
> +       return 0;
> +}
> +
> +static void stp_master_free(struct stm_device *stm, unsigned int idx)
> +{
> +       struct stp_master *master = stm_master(stm, idx);
> +
> +       if (!master)
> +               return;
> +
> +       __stm_master(stm, idx) = NULL;
> +       kfree(master);
> +}
> +
> +static void stm_output_claim(struct stm_device *stm, struct stm_output *output)
> +{
> +       struct stp_master *master = stm_master(stm, output->master);
> +
> +       if (WARN_ON_ONCE(master->nr_free < output->nr_chans))
> +               return;
> +
> +       bitmap_allocate_region(&master->chan_map[0], output->channel,
> +                              ilog2(output->nr_chans));
> +
> +       master->nr_free -= output->nr_chans;
> +}
> +
> +static void
> +stm_output_disclaim(struct stm_device *stm, struct stm_output *output)
> +{
> +       struct stp_master *master = stm_master(stm, output->master);
> +
> +       bitmap_release_region(&master->chan_map[0], output->channel,
> +                             ilog2(output->nr_chans));
> +
> +       master->nr_free += output->nr_chans;
> +}
> +
> +/*
> + * This is like bitmap_find_free_region(), except it can ignore @start bits
> + * at the beginning.
> + */
> +static int find_free_channels(unsigned long *bitmap, unsigned int start,
> +                             unsigned int end, unsigned int width)
> +{
> +       unsigned int pos;
> +       int i;
> +
> +       for (pos = start; pos < end + 1; pos = ALIGN(pos, width)) {
> +               pos = find_next_zero_bit(bitmap, end + 1, pos);
> +               if (pos + width > end + 1)
> +                       break;
> +
> +               if (pos & (width - 1))
> +                       continue;
> +
> +               for (i = 1; i < width && !test_bit(pos + i, bitmap); i++)
> +                       ;
> +               if (i == width)
> +                       return pos;
> +       }
> +
> +       return -1;
> +}
> +
> +static unsigned int
> +stm_find_master_chan(struct stm_device *stm, unsigned int width,
> +                    unsigned int *mstart, unsigned int mend,
> +                    unsigned int *cstart, unsigned int cend)
> +{
> +       struct stp_master *master;
> +       unsigned int midx;
> +       int pos, err;
> +
> +       for (midx = *mstart; midx <= mend; midx++) {
> +               if (!stm_master(stm, midx)) {
> +                       err = stp_master_alloc(stm, midx);
> +                       if (err)
> +                               return err;
> +               }
> +
> +               master = stm_master(stm, midx);
> +
> +               if (!master->nr_free)
> +                       continue;
> +
> +               pos = find_free_channels(master->chan_map, *cstart, cend,
> +                                        width);
> +               if (pos < 0)
> +                       continue;
> +
> +               *mstart = midx;
> +               *cstart = pos;
> +               return 0;
> +       }
> +
> +       return -ENOSPC;
> +}
> +
> +static int stm_output_assign(struct stm_device *stm, unsigned int width,
> +                            struct stp_policy_node *policy_node,
> +                            struct stm_output *output)
> +{
> +       unsigned int midx, cidx, mend, cend;
> +       int ret = -EBUSY;
> +
> +       if (width > stm->data->sw_nchannels)
> +               return -EINVAL;
> +
> +       if (policy_node) {
> +               stp_policy_node_get_ranges(policy_node,
> +                                          &midx, &mend, &cidx, &cend);
> +       } else {
> +               midx = stm->data->sw_start;
> +               cidx = 0;
> +               mend = stm->data->sw_end;
> +               cend = stm->data->sw_nchannels - 1;
> +       }
> +
> +       spin_lock(&stm->mc_lock);
> +       if (output->nr_chans)
> +               goto unlock;
> +
> +       ret = stm_find_master_chan(stm, width, &midx, mend, &cidx, cend);
> +       if (ret)
> +               goto unlock;
> +
> +       output->master = midx;
> +       output->channel = cidx;
> +       output->nr_chans = width;
> +       stm_output_claim(stm, output);
> +       dev_dbg(stm->dev, "assigned %u:%u (+%u)\n", midx, cidx, width);
> +
> +       ret = 0;
> +unlock:
> +       spin_unlock(&stm->mc_lock);
> +
> +       return ret;
> +}
> +
> +static void stm_output_free(struct stm_device *stm, struct stm_output *output)
> +{
> +       spin_lock(&stm->mc_lock);
> +       if (output->nr_chans)
> +               stm_output_disclaim(stm, output);
> +       spin_unlock(&stm->mc_lock);
> +}
> +
> +static int major_match(struct device *dev, const void *data)
> +{
> +       unsigned int major = *(unsigned int *)data;
> +
> +       return MAJOR(dev->devt) == major;
> +}
> +
> +static int stm_char_open(struct inode *inode, struct file *file)
> +{
> +       struct stm_file *stmf;
> +       struct device *dev;
> +       unsigned int major = imajor(inode);
> +       int err = -ENODEV;
> +
> +       dev = class_find_device(&stm_class, NULL, &major, major_match);
> +       if (!dev)
> +               return -ENODEV;
> +
> +       stmf = kzalloc(sizeof(*stmf), GFP_KERNEL);
> +       if (!stmf)
> +               return -ENOMEM;
> +
> +       stmf->stm = dev_get_drvdata(dev);
> +
> +       if (!try_module_get(stmf->stm->owner))
> +               goto err_free;
> +
> +       file->private_data = stmf;
> +
> +       return nonseekable_open(inode, file);
> +
> +err_free:
> +       kfree(stmf);
> +
> +       return err;
> +}
> +
> +static int stm_char_release(struct inode *inode, struct file *file)
> +{
> +       struct stm_file *stmf = file->private_data;
> +
> +       stm_output_free(stmf->stm, &stmf->output);
> +       module_put(stmf->stm->owner);
> +       kfree(stmf);
> +
> +       return 0;
> +}
> +
> +static int stm_file_assign(struct stm_file *stmf, char *id, unsigned int width)
> +{
> +       struct stm_device *stm = stmf->stm;
> +       int ret;
> +
> +       mutex_lock(&stm->policy_mutex);
> +       if (stm->policy)
> +               stmf->policy_node = stp_policy_node_lookup(stm->policy, id);
> +
> +       ret = stm_output_assign(stm, width, stmf->policy_node, &stmf->output);
> +       mutex_unlock(&stm->policy_mutex);
> +
> +       return ret;
> +}
> +
> +static ssize_t stm_char_write(struct file *file, const char __user *buf,
> +                             size_t count, loff_t *ppos)
> +{
> +       struct stm_file *stmf = file->private_data;
> +       struct stm_device *stm = stmf->stm;
> +       char *kbuf;
> +       int err;
> +
> +       /*
> +        * if no m/c have been assigned to this writer up to this
> +        * point, use "default" policy entry
> +        */
> +       if (!stmf->output.nr_chans) {
> +               err = stm_file_assign(stmf, "default", 1);
> +               /*
> +                * EBUSY means that somebody else just assigned this
> +                * output, which is just fine for write()
> +                */
> +               if (err && err != -EBUSY)
> +                       return err;
> +       }
> +
> +       kbuf = kmalloc(count + 1, GFP_KERNEL);
> +       if (!kbuf)
> +               return -ENOMEM;
> +
> +       err = copy_from_user(kbuf, buf, count);
> +       if (err) {
> +               kfree(kbuf);
> +               return -EFAULT;
> +       }
> +
> +       stm->data->write(stm->data, stmf->output.master,
> +                        stmf->output.channel, kbuf, count);
> +
> +
> +       kfree(kbuf);
> +
> +       return count;
> +}
> +
> +static int stm_char_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> +       struct stm_file *stmf = file->private_data;
> +       struct stm_device *stm = stmf->stm;
> +       unsigned long size, phys;
> +
> +       if (!stm->data->mmio_addr)
> +               return -EOPNOTSUPP;
> +
> +       if (vma->vm_pgoff)
> +               return -EINVAL;
> +
> +       size = vma->vm_end - vma->vm_start;
> +
> +       if (stmf->output.nr_chans * stm->data->sw_mmiosz != size)
> +               return -EINVAL;
> +
> +       phys = stm->data->mmio_addr(stm->data, stmf->output.master,
> +                                   stmf->output.channel,
> +                                   stmf->output.nr_chans);
> +
> +       if (!phys)
> +               return -EINVAL;
> +
> +       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> +       vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
> +       vm_iomap_memory(vma, phys, size);
> +
> +       return 0;
> +}
> +
> +static int stm_char_policy_set_ioctl(struct stm_file *stmf, void __user *arg)
> +{
> +       struct stm_device *stm = stmf->stm;
> +       struct stp_policy_id *id;
> +       int ret = -EINVAL;
> +       u32 size;
> +
> +       if (stmf->output.nr_chans)
> +               return -EBUSY;
> +
> +       if (copy_from_user(&size, arg, sizeof(size)))
> +               return -EFAULT;
> +
> +       if (size >= PATH_MAX + sizeof(*id))
> +               return -EINVAL;
> +
> +       /* size + 1 to make sure the .id string at the bottom is terminated */
> +       id = kzalloc(size + 1, GFP_KERNEL);
> +       if (!id)
> +               return -ENOMEM;
> +
> +       if (copy_from_user(id, arg, size)) {
> +               ret = -EFAULT;
> +               goto err_free;
> +       }
> +
> +       if (id->__reserved_0 || id->__reserved_1)
> +               goto err_free;
> +
> +       if (id->width < 1 ||
> +           id->width > PAGE_SIZE / stm->data->sw_mmiosz)
> +               goto err_free;
> +
> +       ret = stm_file_assign(stmf, id->id, id->width);
> +       if (ret)
> +               goto err_free;
> +
> +       if (stm->data->link)
> +               stm->data->link(stm->data, stmf->output.master,
> +                               stmf->output.channel);
> +
> +       ret = 0;
> +
> +err_free:
> +       kfree(id);
> +
> +       return ret;
> +}
> +
> +static int stm_char_policy_get_ioctl(struct stm_file *stmf, void __user *arg)
> +{
> +       struct stp_policy_id id = {
> +               .size           = sizeof(id),
> +               .master         = stmf->output.master,
> +               .channel        = stmf->output.channel,
> +               .width          = stmf->output.nr_chans,
> +               .__reserved_0   = 0,
> +               .__reserved_1   = 0,
> +       };
> +
> +       return copy_to_user(arg, &id, id.size) ? -EFAULT : 0;
> +}
> +
> +static long
> +stm_char_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +       struct stm_file *stmf = file->private_data;
> +       struct stm_data *stm_data = stmf->stm->data;
> +       int err = -ENOTTY;
> +
> +       switch (cmd) {
> +       case STP_POLICY_ID_SET:
> +               err = stm_char_policy_set_ioctl(stmf, (void __user *)arg);
> +               if (err)
> +                       return err;
> +
> +               return stm_char_policy_get_ioctl(stmf, (void __user *)arg);
> +
> +       case STP_POLICY_ID_GET:
> +               return stm_char_policy_get_ioctl(stmf, (void __user *)arg);
> +
> +       default:
> +               if (stm_data->ioctl)
> +                       err = stm_data->ioctl(stm_data, cmd, arg);
> +
> +               break;
> +       }
> +
> +       return err;
> +}
> +
> +#ifdef CONFIG_COMPAT
> +static long
> +stm_char_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +       return stm_char_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> +}
> +#else
> +#define stm_char_compat_ioctl  NULL
> +#endif
> +
> +static const struct file_operations stm_fops = {
> +       .open           = stm_char_open,
> +       .release        = stm_char_release,
> +       .write          = stm_char_write,
> +       .mmap           = stm_char_mmap,
> +       .unlocked_ioctl = stm_char_ioctl,
> +       .compat_ioctl   = stm_char_compat_ioctl,
> +       .llseek         = no_llseek,
> +};
> +
> +int stm_register_device(struct device *parent, struct stm_data *stm_data,
> +                       struct module *owner)
> +{
> +       struct stm_device *stm;
> +       struct device *dev;
> +       unsigned int nmasters;
> +       int err = -ENOMEM;
> +
> +       if (!stm_core_up)
> +               return -EPROBE_DEFER;
> +
> +       if (!stm_data->write || !stm_data->sw_nchannels)
> +               return -EINVAL;
> +
> +       nmasters = stm_data->sw_end - stm_data->sw_start;
> +       stm = kzalloc(sizeof(*stm) + nmasters * sizeof(void *), GFP_KERNEL);
> +       if (!stm)
> +               return -ENOMEM;
> +
> +       stm->major = register_chrdev(0, stm_data->name, &stm_fops);
> +       if (stm->major < 0)
> +               goto err_free;
> +
> +       dev = device_create(&stm_class, parent, MKDEV(stm->major, 0), NULL,
> +                           "%s", stm_data->name);
> +       if (IS_ERR(dev)) {
> +               err = PTR_ERR(dev);
> +               goto err_device;
> +       }
> +
> +       spin_lock_init(&stm->link_lock);
> +       INIT_LIST_HEAD(&stm->link_list);
> +
> +       spin_lock_init(&stm->mc_lock);
> +       mutex_init(&stm->policy_mutex);
> +       stm->sw_nmasters = nmasters;
> +       stm->owner = owner;
> +       stm->data = stm_data;
> +       stm->dev = dev;
> +       stm_data->stm = stm;
> +
> +       dev_set_drvdata(dev, stm);
> +
> +       return 0;
> +
> +err_device:
> +       device_unregister(dev);
> +err_free:
> +       kfree(stm);
> +
> +       return err;
> +}
> +EXPORT_SYMBOL_GPL(stm_register_device);
> +
> +static void stm_source_link_drop(struct stm_source_device *src);
> +
> +void stm_unregister_device(struct stm_data *stm_data)
> +{
> +       struct stm_device *stm = stm_data->stm;
> +       struct stm_source_device *src, *iter;
> +       int i;
> +
> +       spin_lock(&stm->link_lock);
> +       list_for_each_entry_safe(src, iter, &stm->link_list, link_entry) {
> +               stm_source_link_drop(src);
> +       }
> +       spin_unlock(&stm->link_lock);
> +
> +       synchronize_srcu(&stm_source_srcu);
> +
> +       unregister_chrdev(stm->major, stm_data->name);
> +
> +       if (stm->policy)
> +               stp_policy_unbind(stm->policy);
> +
> +       for (i = 0; i < stm->sw_nmasters; i++)
> +               stp_master_free(stm, i);
> +
> +       device_unregister(stm->dev);
> +       kfree(stm);
> +       stm_data->stm = NULL;
> +}
> +EXPORT_SYMBOL_GPL(stm_unregister_device);
> +
> +/**
> + * stm_source_link_add() - connect an stm_source device to an stm device
> + * @src:       stm_source device
> + * @stm:       stm device
> + *
> + * This function establishes a link from stm_source to an stm device so that
> + * the former can send out trace data to the latter.
> + *
> + * Return:     0 on success, -errno otherwise.
> + */
> +static int stm_source_link_add(struct stm_source_device *src,
> +                              struct stm_device *stm)
> +{
> +       int err;
> +
> +       spin_lock(&stm->link_lock);
> +       spin_lock(&src->link_lock);
> +
> +       /* src->link is dereferenced under stm_source_srcu but not the list */
> +       rcu_assign_pointer(src->link, stm);
> +       list_add_tail(&src->link_entry, &stm->link_list);
> +
> +       spin_unlock(&src->link_lock);
> +       spin_unlock(&stm->link_lock);
> +
> +       if (stm->policy) {
> +               char *id = kstrdup(src->data->name, GFP_KERNEL);
> +
> +               if (id) {
> +                       src->policy_node =
> +                               stp_policy_node_lookup(stm->policy, id);
> +
> +                       kfree(id);
> +               }
> +       }
> +
> +       err = stm_output_assign(stm, src->data->nr_chans,
> +                               src->policy_node, &src->output);
> +       if (err)
> +               return err;
> +
> +       /* this is to notify the STM device that a new link has been made */
> +       if (stm->data->link)
> +               stm->data->link(stm->data, src->output.master,
> +                               src->output.channel);
> +
> +       /* this is to let the source carry out all necessary preparations */
> +       if (src->data->link)
> +               src->data->link(src->data);
> +
> +       return 0;
> +}
> +
> +/**
> + * stm_source_link_drop() - detach stm_source from its stm device
> + * @src:       stm_source device
> + *
> + * Unlinking means disconnecting from source's STM device; after this
> + * writes will be unsuccessful until it is linked to a new STM device.
> + *
> + * This will happen on "stm_source_link" sysfs attribute write to undo
> + * the existing link (if any), or on linked STM device's de-registration.
> + */
> +static void stm_source_link_drop(struct stm_source_device *src)
> +{
> +       int idx = srcu_read_lock(&stm_source_srcu);
> +
> +       if (src->link && src->data->unlink)
> +               src->data->unlink(src->data);
> +
> +       srcu_read_unlock(&stm_source_srcu, idx);
> +
> +       spin_lock(&src->link_lock);
> +       if (src->link) {
> +               stm_output_free(src->link, &src->output);
> +               list_del_init(&src->link_entry);
> +               rcu_assign_pointer(src->link, NULL);
> +       }
> +       spin_unlock(&src->link_lock);
> +}
> +
> +static ssize_t stm_source_link_show(struct device *dev,
> +                                   struct device_attribute *attr,
> +                                   char *buf)
> +{
> +       struct stm_source_device *src = dev_get_drvdata(dev);
> +       int idx, ret;
> +
> +       idx = srcu_read_lock(&stm_source_srcu);
> +       ret = sprintf(buf, "%s\n",
> +                     src->link ? dev_name(src->link->dev) : "<none>");
> +       srcu_read_unlock(&stm_source_srcu, idx);
> +
> +       return ret;
> +}
> +
> +static ssize_t stm_source_link_store(struct device *dev,
> +                                    struct device_attribute *attr,
> +                                    const char *buf, size_t count)
> +{
> +       struct stm_source_device *src = dev_get_drvdata(dev);
> +       struct stm_device *link;
> +       struct device *linkdev;
> +       int err;
> +
> +       stm_source_link_drop(src);
> +
> +       linkdev = stm_find_device(buf, count);
> +       if (!linkdev)
> +               return -EINVAL;
> +
> +       link = dev_get_drvdata(linkdev);
> +
> +       err = stm_source_link_add(src, link);
> +
> +       return err ? : count;
> +}
> +
> +static DEVICE_ATTR_RW(stm_source_link);
> +
> +static struct attribute *stm_source_attrs[] = {
> +       &dev_attr_stm_source_link.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group stm_source_group = {
> +       .attrs  = stm_source_attrs,
> +};
> +
> +static const struct attribute_group *stm_source_groups[] = {
> +       &stm_source_group,
> +       NULL,
> +};
> +
> +static struct class stm_source_class = {
> +       .name           = "stm_source",
> +       .dev_groups     = stm_source_groups,
> +};
> +
> +/**
> + * stm_source_register_device() - register an stm_source device
> + * @parent:    parent device
> + * @data:      device description structure
> + *
> + * This will create a device of stm_source class that can write
> + * data to an stm device once linked.
> + *
> + * Return:     0 on success, -errno otherwise.
> + */
> +int stm_source_register_device(struct device *parent,
> +                              struct stm_source_data *data)
> +{
> +       struct stm_source_device *src;
> +       struct device *dev;
> +
> +       if (!stm_core_up)
> +               return -EPROBE_DEFER;
> +
> +       src = kzalloc(sizeof(*src), GFP_KERNEL);
> +       if (!src)
> +               return -ENOMEM;
> +
> +       dev = device_create(&stm_source_class, parent, MKDEV(0, 0), NULL, "%s",
> +                           data->name);
> +       if (IS_ERR(dev)) {
> +               kfree(src);
> +               return PTR_ERR(dev);
> +       }
> +
> +       spin_lock_init(&src->link_lock);
> +       INIT_LIST_HEAD(&src->link_entry);
> +       src->dev = dev;
> +       src->data = data;
> +       data->src = src;
> +       dev_set_drvdata(dev, src);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL_GPL(stm_source_register_device);
> +
> +/**
> + * stm_source_unregister_device() - unregister an stm_source device
> + * @data:      device description that was used to register the device
> + *
> + * This will remove a previously created stm_source device from the system.
> + */
> +void stm_source_unregister_device(struct stm_source_data *data)
> +{
> +       struct stm_source_device *src = data->src;
> +
> +       stm_source_link_drop(src);
> +
> +       device_destroy(&stm_source_class, src->dev->devt);
> +
> +       kfree(src);
> +}
> +EXPORT_SYMBOL_GPL(stm_source_unregister_device);
> +
> +int stm_source_write(struct stm_source_data *data, unsigned int chan,
> +                    const char *buf, size_t count)
> +{
> +       struct stm_source_device *src = data->src;
> +       struct stm_device *stm;
> +       int idx;
> +
> +       if (!src->output.nr_chans)
> +               return -ENODEV;
> +
> +       if (chan >= src->output.nr_chans)
> +               return -EINVAL;
> +
> +       idx = srcu_read_lock(&stm_source_srcu);
> +
> +       stm = srcu_dereference(src->link, &stm_source_srcu);
> +       if (stm)
> +               count = stm->data->write(stm->data, src->output.master,
> +                                        src->output.channel + chan, buf,
> +                                        count);
> +       else
> +               count = -ENODEV;
> +
> +       srcu_read_unlock(&stm_source_srcu, idx);
> +
> +       return count;
> +}
> +EXPORT_SYMBOL_GPL(stm_source_write);
> +
> +static int __init stm_core_init(void)
> +{
> +       int err;
> +
> +       err = class_register(&stm_class);
> +       if (err)
> +               return err;
> +
> +       err = class_register(&stm_source_class);
> +       if (err)
> +               goto err_stm;
> +
> +       err = stp_configfs_init();
> +       if (err)
> +               goto err_src;
> +
> +       init_srcu_struct(&stm_source_srcu);
> +
> +       stm_core_up++;
> +
> +       return 0;
> +
> +err_src:
> +       class_unregister(&stm_source_class);
> +err_stm:
> +       class_unregister(&stm_class);
> +
> +       return err;
> +}
> +
> +postcore_initcall(stm_core_init);
> +
> +static void __exit stm_core_exit(void)
> +{
> +       class_unregister(&stm_source_class);
> +       class_unregister(&stm_class);
> +       stp_configfs_exit();
> +}
> +
> +module_exit(stm_core_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("System Trace Module device class");
> +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");
> diff --git a/drivers/hwtracing/stm/policy.c b/drivers/hwtracing/stm/policy.c
> new file mode 100644
> index 0000000000..b5c59a0e0c
> --- /dev/null
> +++ b/drivers/hwtracing/stm/policy.c
> @@ -0,0 +1,467 @@
> +/*
> + * System Trace Module (STM) master/channel allocation policy management
> + * Copyright (c) 2014, Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + *
> + * A master/channel allocation policy allows mapping string identifiers to
> + * master and channel ranges, where allocation can be done.
> + */
> +
> +#define pr_fmt(fmt)    KBUILD_MODNAME ": " fmt
> +
> +#include <linux/types.h>
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/configfs.h>
> +#include <linux/slab.h>
> +#include <linux/stm.h>
> +#include "stm.h"
> +
> +/*
> + * STP Master/Channel allocation policy configfs layout.
> + */
> +
> +struct stp_policy {
> +       struct config_group     group;
> +       struct stm_device       *stm;
> +};
> +
> +struct stp_policy_node {
> +       struct config_group     group;
> +       struct stm_device       *stm;
> +       struct stp_policy       *policy;
> +       unsigned int            first_master;
> +       unsigned int            last_master;
> +       unsigned int            first_channel;
> +       unsigned int            last_channel;
> +};
> +
> +void stp_policy_node_get_ranges(struct stp_policy_node *policy_node,
> +                               unsigned int *mstart, unsigned int *mend,
> +                               unsigned int *cstart, unsigned int *cend)
> +{
> +       *mstart = policy_node->first_master;
> +       *mend   = policy_node->last_master;
> +       *cstart = policy_node->first_channel;
> +       *cend   = policy_node->last_channel;
> +}
> +
> +static inline char *stp_policy_node_name(struct stp_policy_node *policy_node)
> +{
> +       return policy_node->group.cg_item.ci_name ? : "<none>";
> +}
> +
> +static inline struct stp_policy *to_stp_policy(struct config_item *item)
> +{
> +       return item ?
> +               container_of(to_config_group(item), struct stp_policy, group) :
> +               NULL;
> +}
> +
> +static inline struct stp_policy_node *
> +to_stp_policy_node(struct config_item *item)
> +{
> +       return item ?
> +               container_of(to_config_group(item), struct stp_policy_node,
> +                            group) :
> +               NULL;
> +}
> +
> +static ssize_t stp_policy_node_masters_show(struct stp_policy_node *policy_node,
> +                                           char *page)
> +{
> +       ssize_t count;
> +
> +       count = sprintf(page, "%u %u\n", policy_node->first_master,
> +                       policy_node->last_master);
> +
> +       return count;
> +}
> +
> +static ssize_t
> +stp_policy_node_masters_store(struct stp_policy_node *policy_node,
> +                             const char *page, size_t count)
> +{
> +       struct stm_device *stm = policy_node->stm;
> +       unsigned int first, last;
> +       char *p = (char *) page;
> +
> +       if (sscanf(p, "%u %u", &first, &last) != 2)
> +               return -EINVAL;
> +
> +       /* must be within [sw_start..sw_end], which is an inclusive range */
> +       if (first > INT_MAX || last > INT_MAX || first > last ||
> +           first < stm->data->sw_start ||
> +           last > stm->data->sw_end)
> +               return -ERANGE;
> +
> +       policy_node->first_master = first;
> +       policy_node->last_master = last;
> +
> +       return count;
> +}
> +
> +static ssize_t
> +stp_policy_node_channels_show(struct stp_policy_node *policy_node, char *page)
> +{
> +       ssize_t count;
> +
> +       count = sprintf(page, "%u %u\n", policy_node->first_channel,
> +                       policy_node->last_channel);
> +
> +       return count;
> +}
> +
> +static ssize_t
> +stp_policy_node_channels_store(struct stp_policy_node *policy_node,
> +                              const char *page, size_t count)
> +{
> +       unsigned int first, last;
> +       char *p = (char *) page;
> +
> +       if (sscanf(p, "%u %u", &first, &last) != 2)
> +               return -EINVAL;
> +
> +       if (first > INT_MAX || last > INT_MAX || first > last ||
> +           last >= policy_node->stm->data->sw_nchannels)
> +               return -ERANGE;
> +
> +       policy_node->first_channel = first;
> +       policy_node->last_channel = last;
> +
> +       return count;
> +}
> +
> +static void stp_policy_node_release(struct config_item *item)
> +{
> +       kfree(to_stp_policy_node(item));
> +}
> +
> +struct stp_policy_node_attribute {
> +       struct configfs_attribute       attr;
> +       ssize_t (*show)(struct stp_policy_node *, char *);
> +       ssize_t (*store)(struct stp_policy_node *, const char *, size_t);
> +};
> +
> +static ssize_t stp_policy_node_attr_show(struct config_item *item,
> +                                        struct configfs_attribute *attr,
> +                                        char *page)
> +{
> +       struct stp_policy_node *policy_node = to_stp_policy_node(item);
> +       struct stp_policy_node_attribute *pn_attr =
> +               container_of(attr, struct stp_policy_node_attribute, attr);
> +       ssize_t count = 0;
> +
> +       if (pn_attr->show)
> +               count = pn_attr->show(policy_node, page);
> +
> +       return count;
> +}
> +
> +static ssize_t stp_policy_node_attr_store(struct config_item *item,
> +                                         struct configfs_attribute *attr,
> +                                         const char *page, size_t len)
> +{
> +       struct stp_policy_node *policy_node = to_stp_policy_node(item);
> +       struct stp_policy_node_attribute *pn_attr =
> +               container_of(attr, struct stp_policy_node_attribute, attr);
> +       ssize_t count = -EINVAL;
> +
> +       if (pn_attr->store)
> +               count = pn_attr->store(policy_node, page, len);
> +
> +       return count;
> +}
> +
> +static struct configfs_item_operations stp_policy_node_item_ops = {
> +       .release                = stp_policy_node_release,
> +       .show_attribute         = stp_policy_node_attr_show,
> +       .store_attribute        = stp_policy_node_attr_store,
> +};
> +
> +static struct stp_policy_node_attribute stp_policy_node_attr_range = {
> +       .attr   = {
> +               .ca_owner = THIS_MODULE,
> +               .ca_name = "masters",
> +               .ca_mode = S_IRUGO | S_IWUSR,
> +       },
> +       .show   = stp_policy_node_masters_show,
> +       .store  = stp_policy_node_masters_store,
> +};
> +
> +static struct stp_policy_node_attribute stp_policy_node_attr_channels = {
> +       .attr   = {
> +               .ca_owner = THIS_MODULE,
> +               .ca_name = "channels",
> +               .ca_mode = S_IRUGO | S_IWUSR,
> +       },
> +       .show   = stp_policy_node_channels_show,
> +       .store  = stp_policy_node_channels_store,
> +};
> +
> +static struct configfs_attribute *stp_policy_node_attrs[] = {
> +       &stp_policy_node_attr_range.attr,
> +       &stp_policy_node_attr_channels.attr,
> +       NULL,
> +};
> +
> +static struct config_item_type stp_policy_type;
> +static struct config_item_type stp_policy_node_type;
> +
> +static struct config_group *
> +stp_policy_node_make(struct config_group *group, const char *name)
> +{
> +       struct stp_policy_node *policy_node, *parent_node;
> +       struct stp_policy *policy;
> +
> +       if (group->cg_item.ci_type == &stp_policy_type) {
> +               policy = container_of(group, struct stp_policy, group);
> +       } else {
> +               parent_node = container_of(group, struct stp_policy_node,
> +                                          group);
> +               policy = parent_node->policy;
> +       }
> +
> +       if (!policy->stm)
> +               return ERR_PTR(-ENODEV);
> +
> +       policy_node = kzalloc(sizeof(struct stp_policy_node), GFP_KERNEL);
> +       if (!policy_node)
> +               return ERR_PTR(-ENOMEM);
> +
> +       config_group_init_type_name(&policy_node->group, name,
> +                                   &stp_policy_node_type);
> +
> +       policy_node->policy = policy;
> +       policy_node->stm = policy->stm;
> +
> +       /* default values for the attributes */
> +       policy_node->first_master = policy->stm->data->sw_start;
> +       policy_node->last_master = policy->stm->data->sw_end;
> +       policy_node->first_channel = 0;
> +       policy_node->last_channel = policy->stm->data->sw_nchannels - 1;
> +
> +       return &policy_node->group;
> +}
> +
> +static void
> +stp_policy_node_drop(struct config_group *group, struct config_item *item)
> +{
> +       config_item_put(item);
> +}
> +
> +static struct configfs_group_operations stp_policy_node_group_ops = {
> +       .make_group     = stp_policy_node_make,
> +       .drop_item      = stp_policy_node_drop,
> +};
> +
> +static struct config_item_type stp_policy_node_type = {
> +       .ct_item_ops    = &stp_policy_node_item_ops,
> +       .ct_group_ops   = &stp_policy_node_group_ops,
> +       .ct_attrs       = stp_policy_node_attrs,
> +       .ct_owner       = THIS_MODULE,
> +};
> +
> +/*
> + * Root group: policies.
> + */
> +static struct configfs_attribute stp_policy_attr_device = {
> +       .ca_owner = THIS_MODULE,
> +       .ca_name = "device",
> +       .ca_mode = S_IRUGO | S_IWUSR,
> +};
> +
> +static struct configfs_attribute *stp_policy_attrs[] = {
> +       &stp_policy_attr_device,
> +       NULL,
> +};
> +
> +static ssize_t stp_policy_attr_show(struct config_item *item,
> +                                   struct configfs_attribute *attr,
> +                                   char *page)
> +{
> +       struct stp_policy *policy = to_stp_policy(item);
> +
> +       return sprintf(page, "%s\n",
> +                      (policy && policy->stm) ?
> +                      policy->stm->data->name :
> +                      "<none>");
> +}
> +
> +static ssize_t stp_policy_attr_store(struct config_item *item,
> +                                    struct configfs_attribute *attr,
> +                                    const char *page, size_t len)
> +{
> +       struct stp_policy *policy = to_stp_policy(item);
> +       ssize_t count = -EINVAL;
> +       struct device *dev;
> +
> +       dev = stm_find_device(page, len);
> +       if (dev) {
> +               count = len;
> +               if (policy->stm)
> +                       put_device(policy->stm->dev);
> +
> +               policy->stm = dev_get_drvdata(dev);
> +
> +               mutex_lock(&policy->stm->policy_mutex);
> +               policy->stm->policy = policy;
> +               mutex_unlock(&policy->stm->policy_mutex);
> +       }
> +
> +       return count;
> +}
> +
> +void stp_policy_unbind(struct stp_policy *policy)
> +{
> +       put_device(policy->stm->dev);
> +
> +       mutex_lock(&policy->stm->policy_mutex);
> +       policy->stm->policy = NULL;
> +       mutex_unlock(&policy->stm->policy_mutex);
> +
> +       policy->stm = NULL;
> +}
> +
> +static void stp_policy_release(struct config_item *item)
> +{
> +       struct stp_policy *policy = to_stp_policy(item);
> +
> +       stp_policy_unbind(policy);
> +       kfree(policy);
> +}
> +
> +static struct configfs_item_operations stp_policy_item_ops = {
> +       .release                = stp_policy_release,
> +       .show_attribute         = stp_policy_attr_show,
> +       .store_attribute        = stp_policy_attr_store,
> +};
> +
> +static struct configfs_group_operations stp_policy_group_ops = {
> +       .make_group     = stp_policy_node_make,
> +};
> +
> +static struct config_item_type stp_policy_type = {
> +       .ct_item_ops    = &stp_policy_item_ops,
> +       .ct_group_ops   = &stp_policy_group_ops,
> +       .ct_attrs       = stp_policy_attrs,
> +       .ct_owner       = THIS_MODULE,
> +};
> +
> +static struct config_group *
> +stp_policies_make(struct config_group *group, const char *name)
> +{
> +       struct stp_policy *policy;
> +
> +       policy = kzalloc(sizeof(*policy), GFP_KERNEL);
> +       if (!policy)
> +               return ERR_PTR(-ENOMEM);
> +
> +       config_group_init_type_name(&policy->group, name,
> +                                   &stp_policy_type);
> +       policy->stm = NULL;
> +
> +       return &policy->group;
> +}
> +
> +static struct configfs_group_operations stp_policies_group_ops = {
> +       .make_group     = stp_policies_make,
> +};
> +
> +static struct config_item_type stp_policies_type = {
> +       .ct_group_ops   = &stp_policies_group_ops,
> +       .ct_owner       = THIS_MODULE,
> +};
> +
> +static struct configfs_subsystem stp_policy_subsys = {
> +       .su_group = {
> +               .cg_item = {
> +                       .ci_namebuf     = "stp-policy",
> +                       .ci_type        = &stp_policies_type,
> +               },
> +       },
> +};
> +
> +/*
> + * Lock the policy mutex from the outside
> + */
> +static struct stp_policy_node *
> +__stp_policy_node_lookup(struct stp_policy *policy, char *s)
> +{
> +       struct stp_policy_node *policy_node, *ret;
> +       struct list_head *head = &policy->group.cg_children;
> +       struct config_item *item;
> +       char *start, *end = s;
> +
> +       if (list_empty(head))
> +               return NULL;
> +
> +       /* return the first entry if everything else fails */
> +       item = list_entry(head->next, struct config_item, ci_entry);
> +       ret = to_stp_policy_node(item);
> +
> +next:
> +       for (;;) {
> +               start = strsep(&end, "/");
> +               if (!start)
> +                       break;
> +
> +               if (!*start)
> +                       continue;
> +
> +               list_for_each_entry(item, head, ci_entry) {
> +                       policy_node = to_stp_policy_node(item);
> +
> +                       if (!strcmp(start,
> +                                   policy_node->group.cg_item.ci_name)) {
> +                               ret = policy_node;
> +
> +                               if (!end)
> +                                       goto out;
> +
> +                               head = &policy_node->group.cg_children;
> +                               goto next;
> +                       }
> +               }
> +               break;
> +       }
> +
> +out:
> +       return ret;
> +}
> +
> +struct stp_policy_node *
> +stp_policy_node_lookup(struct stp_policy *policy, char *s)
> +{
> +       struct stp_policy_node *policy_node;
> +
> +       mutex_lock(&stp_policy_subsys.su_mutex);
> +       policy_node = __stp_policy_node_lookup(policy, s);
> +       mutex_unlock(&stp_policy_subsys.su_mutex);
> +
> +       return policy_node;
> +}
> +
> +int __init stp_configfs_init(void)
> +{
> +       int err;
> +
> +       config_group_init(&stp_policy_subsys.su_group);
> +       mutex_init(&stp_policy_subsys.su_mutex);
> +       err = configfs_register_subsystem(&stp_policy_subsys);
> +
> +       return err;
> +}
> +
> +void __exit stp_configfs_exit(void)
> +{
> +       configfs_unregister_subsystem(&stp_policy_subsys);
> +}
> diff --git a/drivers/hwtracing/stm/stm.h b/drivers/hwtracing/stm/stm.h
> new file mode 100644
> index 0000000000..4d088a1400
> --- /dev/null
> +++ b/drivers/hwtracing/stm/stm.h
> @@ -0,0 +1,79 @@
> +/*
> + * System Trace Module (STM) infrastructure
> + * Copyright (c) 2014, Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + *
> + * STM class implements generic infrastructure for  System Trace Module devices
> + * as defined in MIPI STPv2 specification.
> + */
> +
> +#ifndef _CLASS_STM_H_
> +#define _CLASS_STM_H_
> +
> +struct stp_policy;
> +struct stp_policy_node;
> +
> +struct stp_policy_node *
> +stp_policy_node_lookup(struct stp_policy *policy, char *s);
> +void stp_policy_unbind(struct stp_policy *policy);
> +
> +void stp_policy_node_get_ranges(struct stp_policy_node *policy_node,
> +                               unsigned int *mstart, unsigned int *mend,
> +                               unsigned int *cstart, unsigned int *cend);
> +int stp_configfs_init(void);
> +void stp_configfs_exit(void);
> +
> +struct stp_master {
> +       unsigned int    nr_free;
> +       unsigned long   chan_map[0];
> +};
> +
> +struct stm_device {
> +       struct device           *dev;
> +       struct module           *owner;
> +       struct stp_policy       *policy;
> +       struct mutex            policy_mutex;
> +       int                     major;
> +       unsigned int            sw_nmasters;
> +       struct stm_data         *data;
> +       spinlock_t              link_lock;
> +       struct list_head        link_list;
> +       /* master allocation */
> +       spinlock_t              mc_lock;
> +       struct stp_master       *masters[0];
> +};
> +
> +struct stm_output {
> +       unsigned int            master;
> +       unsigned int            channel;
> +       unsigned int            nr_chans;
> +};
> +
> +struct stm_file {
> +       struct stm_device       *stm;
> +       struct stp_policy_node  *policy_node;
> +       struct stm_output       output;
> +};
> +
> +struct device *stm_find_device(const char *name, size_t len);
> +
> +struct stm_source_device {
> +       struct device           *dev;
> +       struct stm_source_data  *data;
> +       spinlock_t              link_lock;
> +       struct stm_device       *link;
> +       struct list_head        link_entry;
> +       /* one output per stm_source device */
> +       struct stp_policy_node  *policy_node;
> +       struct stm_output       output;
> +};
> +
> +#endif /* _CLASS_STM_H_ */
> diff --git a/include/linux/stm.h b/include/linux/stm.h
> new file mode 100644
> index 0000000000..976c94d8f1
> --- /dev/null
> +++ b/include/linux/stm.h
> @@ -0,0 +1,102 @@
> +/*
> + * System Trace Module (STM) infrastructure apis
> + * Copyright (C) 2014 Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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 _STM_H_
> +#define _STM_H_
> +
> +#include <linux/device.h>
> +
> +struct stp_policy;
> +
> +struct stm_device;
> +
> +/**
> + * struct stm_data - STM device description and callbacks
> + * @name:              device name
> + * @stm:               internal structure, only used by stm class code
> + * @sw_start:          first STP master available to software
> + * @sw_end:            last STP master available to software
> + * @sw_nchannels:      number of STP channels per master
> + * @sw_mmiosz:         size of one channel's IO space, for mmap, optional
> + * @write:             write callback
> + * @mmio_addr:         mmap callback, optional
> + * @link:              called when a new stm_source gets linked to us, optional
> + * @unlink:            likewise for unlinking, again optional
> + * @ioctl:             ioctl callback for device-specific commands
> + *
> + * Fill out this structure before calling stm_register_device() to create
> + * an STM device and stm_unregister_device() to destroy it. It will also be
> + * passed back to @write(), @mmio_addr(), @link(), @unlink() and @ioctl()
> + * callbacks.
> + *
> + * Normally, an STM device will have a range of masters available to software
> + * and the rest being statically assigned to various hardware trace sources.
> + * The former is defined by the the range [@sw_start..@sw_end] of the device
> + * description. That is, the lowest master that can be allocated to software
> + * writers is @sw_start and data from this writer will appear is @sw_start
> + * master in the STP stream.
> + */
> +struct stm_data {
> +       const char              *name;
> +       struct stm_device       *stm;
> +       unsigned int            sw_start;
> +       unsigned int            sw_end;
> +       unsigned int            sw_nchannels;
> +       unsigned int            sw_mmiosz;
> +       ssize_t                 (*write)(struct stm_data *, unsigned int,
> +                                        unsigned int, const char *, size_t);
> +       phys_addr_t             (*mmio_addr)(struct stm_data *, unsigned int,
> +                                            unsigned int, unsigned int);
> +       void                    (*link)(struct stm_data *, unsigned int,
> +                                       unsigned int);
> +       void                    (*unlink)(struct stm_data *, unsigned int,
> +                                         unsigned int);
> +       long                    (*ioctl)(struct stm_data *, unsigned int,
> +                                        unsigned long);
> +};
> +
> +int stm_register_device(struct device *parent, struct stm_data *stm_data,
> +                       struct module *owner);
> +void stm_unregister_device(struct stm_data *stm_data);
> +
> +struct stm_source_device;
> +
> +/**
> + * struct stm_source_data - STM source device description and callbacks
> + * @name:      device name, will be used for policy lookup
> + * @src:       internal structure, only used by stm class code
> + * @nr_chans:  number of channels to allocate
> + * @link:      called when this source gets linked to an STM device
> + * @unlink:    called when this source is about to get unlinked from its STM
> + *
> + * Fill in this structure before calling stm_source_register_device() to
> + * register a source device. Also pass it to unregister and write calls.
> + */
> +struct stm_source_data {
> +       const char              *name;
> +       struct stm_source_device *src;
> +       unsigned int            percpu;
> +       unsigned int            nr_chans;
> +       int                     (*link)(struct stm_source_data *data);
> +       void                    (*unlink)(struct stm_source_data *data);
> +};
> +
> +int stm_source_register_device(struct device *parent,
> +                              struct stm_source_data *data);
> +void stm_source_unregister_device(struct stm_source_data *data);
> +
> +int stm_source_write(struct stm_source_data *data, unsigned int chan,
> +                    const char *buf, size_t count);
> +
> +#endif /* _STM_H_ */
> diff --git a/include/uapi/linux/stm.h b/include/uapi/linux/stm.h
> new file mode 100644
> index 0000000000..042b58b53b
> --- /dev/null
> +++ b/include/uapi/linux/stm.h
> @@ -0,0 +1,47 @@
> +/*
> + * System Trace Module (STM) userspace interfaces
> + * Copyright (c) 2014, Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + *
> + * STM class implements generic infrastructure for  System Trace Module devices
> + * as defined in MIPI STPv2 specification.
> + */
> +
> +#ifndef _UAPI_LINUX_STM_H
> +#define _UAPI_LINUX_STM_H
> +
> +/**
> + * struct stp_policy_id - identification for the STP policy
> + * @size:      size of the structure including real id[] length
> + * @master:    assigned master
> + * @channel:   first assigned channel
> + * @width:     number of requested channels
> + * @id:                identification string
> + *
> + * User must calculate the total size of the structure and put it into
> + * @size field, fill out the @id and desired @width. In return, kernel
> + * fills out @master, @channel and @width.
> + */
> +struct stp_policy_id {
> +       __u32           size;
> +       __u16           master;
> +       __u16           channel;
> +       __u16           width;
> +       /* padding */
> +       __u16           __reserved_0;
> +       __u32           __reserved_1;
> +       char            id[0];
> +};
> +
> +#define STP_POLICY_ID_SET      _IOWR('%', 0, struct stp_policy_id)
> +#define STP_POLICY_ID_GET      _IOR('%', 1, struct stp_policy_id)
> +
> +#endif /* _UAPI_LINUX_STM_H */
> --
> 2.1.4
>

^ permalink raw reply

* Re: [PATCH v10 tip 3/9] tracing: attach BPF programs to kprobes
From: Masami Hiramatsu @ 2015-03-23  2:14 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Ingo Molnar, Steven Rostedt, Namhyung Kim,
	Arnaldo Carvalho de Melo, Jiri Olsa, David S. Miller,
	Daniel Borkmann, Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1427053150-32213-4-git-send-email-ast@plumgrid.com>

(2015/03/23 4:39), Alexei Starovoitov wrote:
> User interface:
> struct perf_event_attr attr = {.type = PERF_TYPE_TRACEPOINT, .config = event_id, ...};
> event_fd = perf_event_open(&attr,...);
> ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
> 
> prog_fd is a file descriptor associated with BPF program previously loaded.
> event_id is an ID of created kprobe
> 
> close(event_fd) - automatically detaches BPF program from it
> 
> BPF programs can call in-kernel helper functions to:
> - lookup/update/delete elements in maps
> - probe_read - wraper of probe_kernel_read() used to access any kernel
>   data structures
> 
> BPF programs receive 'struct pt_regs *' as an input
> ('struct pt_regs' is architecture dependent)
> and return 0 to ignore the event and 1 to store kprobe event into ring buffer.
> 
> Note, kprobes are _not_ a stable kernel ABI, so bpf programs attached to
> kprobes must be recompiled for every kernel version and user must supply correct
> LINUX_VERSION_CODE in attr.kern_version during bpf_prog_load() call.
> 

This looks OK to me :)

Reviewed-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>

Thanks!

> Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
> Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
> ---
>  include/linux/ftrace_event.h    |   11 ++++
>  include/uapi/linux/bpf.h        |    3 +
>  include/uapi/linux/perf_event.h |    1 +
>  kernel/bpf/syscall.c            |    7 ++-
>  kernel/events/core.c            |   59 ++++++++++++++++++
>  kernel/trace/Makefile           |    1 +
>  kernel/trace/bpf_trace.c        |  130 +++++++++++++++++++++++++++++++++++++++
>  kernel/trace/trace_kprobe.c     |    8 +++
>  8 files changed, 219 insertions(+), 1 deletion(-)
>  create mode 100644 kernel/trace/bpf_trace.c
> 
> diff --git a/include/linux/ftrace_event.h b/include/linux/ftrace_event.h
> index 77325e1a1816..0aa535bc9f05 100644
> --- a/include/linux/ftrace_event.h
> +++ b/include/linux/ftrace_event.h
> @@ -13,6 +13,7 @@ struct trace_array;
>  struct trace_buffer;
>  struct tracer;
>  struct dentry;
> +struct bpf_prog;
>  
>  struct trace_print_flags {
>  	unsigned long		mask;
> @@ -306,6 +307,7 @@ struct ftrace_event_call {
>  #ifdef CONFIG_PERF_EVENTS
>  	int				perf_refcount;
>  	struct hlist_head __percpu	*perf_events;
> +	struct bpf_prog			*prog;
>  
>  	int	(*perf_perm)(struct ftrace_event_call *,
>  			     struct perf_event *);
> @@ -551,6 +553,15 @@ event_trigger_unlock_commit_regs(struct ftrace_event_file *file,
>  		event_triggers_post_call(file, tt);
>  }
>  
> +#ifdef CONFIG_BPF_SYSCALL
> +unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx);
> +#else
> +static inline unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx)
> +{
> +	return 1;
> +}
> +#endif
> +
>  enum {
>  	FILTER_OTHER = 0,
>  	FILTER_STATIC_STRING,
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index 45da7ec7d274..b2948feeb70b 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -118,6 +118,7 @@ enum bpf_map_type {
>  enum bpf_prog_type {
>  	BPF_PROG_TYPE_UNSPEC,
>  	BPF_PROG_TYPE_SOCKET_FILTER,
> +	BPF_PROG_TYPE_KPROBE,
>  };
>  
>  /* flags for BPF_MAP_UPDATE_ELEM command */
> @@ -151,6 +152,7 @@ union bpf_attr {
>  		__u32		log_level;	/* verbosity level of verifier */
>  		__u32		log_size;	/* size of user buffer */
>  		__aligned_u64	log_buf;	/* user supplied buffer */
> +		__u32		kern_version;	/* checked when prog_type=kprobe */
>  	};
>  } __attribute__((aligned(8)));
>  
> @@ -162,6 +164,7 @@ enum bpf_func_id {
>  	BPF_FUNC_map_lookup_elem, /* void *map_lookup_elem(&map, &key) */
>  	BPF_FUNC_map_update_elem, /* int map_update_elem(&map, &key, &value, flags) */
>  	BPF_FUNC_map_delete_elem, /* int map_delete_elem(&map, &key) */
> +	BPF_FUNC_probe_read,      /* int bpf_probe_read(void *dst, int size, void *src) */
>  	__BPF_FUNC_MAX_ID,
>  };
>  
> diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
> index 3c8b45de57ec..ad4dade2a502 100644
> --- a/include/uapi/linux/perf_event.h
> +++ b/include/uapi/linux/perf_event.h
> @@ -382,6 +382,7 @@ struct perf_event_attr {
>  #define PERF_EVENT_IOC_SET_OUTPUT	_IO ('$', 5)
>  #define PERF_EVENT_IOC_SET_FILTER	_IOW('$', 6, char *)
>  #define PERF_EVENT_IOC_ID		_IOR('$', 7, __u64 *)
> +#define PERF_EVENT_IOC_SET_BPF		_IOW('$', 8, __u32)
>  
>  enum perf_event_ioc_flags {
>  	PERF_IOC_FLAG_GROUP		= 1U << 0,
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 536edc2be307..504c10b990ef 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -16,6 +16,7 @@
>  #include <linux/file.h>
>  #include <linux/license.h>
>  #include <linux/filter.h>
> +#include <linux/version.h>
>  
>  static LIST_HEAD(bpf_map_types);
>  
> @@ -467,7 +468,7 @@ struct bpf_prog *bpf_prog_get(u32 ufd)
>  }
>  
>  /* last field in 'union bpf_attr' used by this command */
> -#define	BPF_PROG_LOAD_LAST_FIELD log_buf
> +#define	BPF_PROG_LOAD_LAST_FIELD kern_version
>  
>  static int bpf_prog_load(union bpf_attr *attr)
>  {
> @@ -492,6 +493,10 @@ static int bpf_prog_load(union bpf_attr *attr)
>  	if (attr->insn_cnt >= BPF_MAXINSNS)
>  		return -EINVAL;
>  
> +	if (type == BPF_PROG_TYPE_KPROBE &&
> +	    attr->kern_version != LINUX_VERSION_CODE)
> +		return -EINVAL;
> +
>  	/* plain bpf_prog allocation */
>  	prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);
>  	if (!prog)
> diff --git a/kernel/events/core.c b/kernel/events/core.c
> index 2709063eb26b..3a45e7f6b2df 100644
> --- a/kernel/events/core.c
> +++ b/kernel/events/core.c
> @@ -42,6 +42,8 @@
>  #include <linux/module.h>
>  #include <linux/mman.h>
>  #include <linux/compat.h>
> +#include <linux/bpf.h>
> +#include <linux/filter.h>
>  
>  #include "internal.h"
>  
> @@ -3402,6 +3404,7 @@ errout:
>  }
>  
>  static void perf_event_free_filter(struct perf_event *event);
> +static void perf_event_free_bpf_prog(struct perf_event *event);
>  
>  static void free_event_rcu(struct rcu_head *head)
>  {
> @@ -3411,6 +3414,7 @@ static void free_event_rcu(struct rcu_head *head)
>  	if (event->ns)
>  		put_pid_ns(event->ns);
>  	perf_event_free_filter(event);
> +	perf_event_free_bpf_prog(event);
>  	kfree(event);
>  }
>  
> @@ -3923,6 +3927,7 @@ static inline int perf_fget_light(int fd, struct fd *p)
>  static int perf_event_set_output(struct perf_event *event,
>  				 struct perf_event *output_event);
>  static int perf_event_set_filter(struct perf_event *event, void __user *arg);
> +static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd);
>  
>  static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned long arg)
>  {
> @@ -3976,6 +3981,9 @@ static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned lon
>  	case PERF_EVENT_IOC_SET_FILTER:
>  		return perf_event_set_filter(event, (void __user *)arg);
>  
> +	case PERF_EVENT_IOC_SET_BPF:
> +		return perf_event_set_bpf_prog(event, arg);
> +
>  	default:
>  		return -ENOTTY;
>  	}
> @@ -6436,6 +6444,49 @@ static void perf_event_free_filter(struct perf_event *event)
>  	ftrace_profile_free_filter(event);
>  }
>  
> +static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
> +{
> +	struct bpf_prog *prog;
> +
> +	if (event->attr.type != PERF_TYPE_TRACEPOINT)
> +		return -EINVAL;
> +
> +	if (event->tp_event->prog)
> +		return -EEXIST;
> +
> +	if (!(event->tp_event->flags & TRACE_EVENT_FL_KPROBE))
> +		/* bpf programs can only be attached to kprobes */
> +		return -EINVAL;
> +
> +	prog = bpf_prog_get(prog_fd);
> +	if (IS_ERR(prog))
> +		return PTR_ERR(prog);
> +
> +	if (prog->aux->prog_type != BPF_PROG_TYPE_KPROBE) {
> +		/* valid fd, but invalid bpf program type */
> +		bpf_prog_put(prog);
> +		return -EINVAL;
> +	}
> +
> +	event->tp_event->prog = prog;
> +
> +	return 0;
> +}
> +
> +static void perf_event_free_bpf_prog(struct perf_event *event)
> +{
> +	struct bpf_prog *prog;
> +
> +	if (!event->tp_event)
> +		return;
> +
> +	prog = event->tp_event->prog;
> +	if (prog) {
> +		event->tp_event->prog = NULL;
> +		bpf_prog_put(prog);
> +	}
> +}
> +
>  #else
>  
>  static inline void perf_tp_register(void)
> @@ -6451,6 +6502,14 @@ static void perf_event_free_filter(struct perf_event *event)
>  {
>  }
>  
> +static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
> +{
> +	return -ENOENT;
> +}
> +
> +static void perf_event_free_bpf_prog(struct perf_event *event)
> +{
> +}
>  #endif /* CONFIG_EVENT_TRACING */
>  
>  #ifdef CONFIG_HAVE_HW_BREAKPOINT
> diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
> index 98f26588255e..c575a300103b 100644
> --- a/kernel/trace/Makefile
> +++ b/kernel/trace/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o
>  endif
>  obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
>  obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
> +obj-$(CONFIG_BPF_SYSCALL) += bpf_trace.o
>  obj-$(CONFIG_KPROBE_EVENT) += trace_kprobe.o
>  obj-$(CONFIG_TRACEPOINTS) += power-traces.o
>  ifeq ($(CONFIG_PM),y)
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> new file mode 100644
> index 000000000000..f1e87da91da3
> --- /dev/null
> +++ b/kernel/trace/bpf_trace.c
> @@ -0,0 +1,130 @@
> +/* Copyright (c) 2011-2015 PLUMgrid, http://plumgrid.com
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of version 2 of the GNU General Public
> + * License as published by the Free Software Foundation.
> + */
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include <linux/bpf.h>
> +#include <linux/filter.h>
> +#include <linux/uaccess.h>
> +#include "trace.h"
> +
> +static DEFINE_PER_CPU(int, bpf_prog_active);
> +
> +/**
> + * trace_call_bpf - invoke BPF program
> + * @prog: BPF program
> + * @ctx: opaque context pointer
> + *
> + * kprobe handlers execute BPF programs via this helper.
> + * Can be used from static tracepoints in the future.
> + *
> + * Return: BPF programs always return an integer which is interpreted by
> + * kprobe handler as:
> + * 0 - return from kprobe (event is filtered out)
> + * 1 - store kprobe event into ring buffer
> + * Other values are reserved and currently alias to 1
> + */
> +unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx)
> +{
> +	unsigned int ret;
> +
> +	if (in_nmi()) /* not supported yet */
> +		return 1;
> +
> +	preempt_disable();
> +
> +	if (unlikely(__this_cpu_inc_return(bpf_prog_active) != 1)) {
> +		/*
> +		 * since some bpf program is already running on this cpu,
> +		 * don't call into another bpf program (same or different)
> +		 * and don't send kprobe event into ring-buffer,
> +		 * so return zero here
> +		 */
> +		ret = 0;
> +		goto out;
> +	}
> +
> +	rcu_read_lock();
> +	ret = BPF_PROG_RUN(prog, ctx);
> +	rcu_read_unlock();
> +
> + out:
> +	__this_cpu_dec(bpf_prog_active);
> +	preempt_enable();
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(trace_call_bpf);
> +
> +static u64 bpf_probe_read(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
> +{
> +	void *dst = (void *) (long) r1;
> +	int size = (int) r2;
> +	void *unsafe_ptr = (void *) (long) r3;
> +
> +	return probe_kernel_read(dst, unsafe_ptr, size);
> +}
> +
> +static const struct bpf_func_proto bpf_probe_read_proto = {
> +	.func		= bpf_probe_read,
> +	.gpl_only	= true,
> +	.ret_type	= RET_INTEGER,
> +	.arg1_type	= ARG_PTR_TO_STACK,
> +	.arg2_type	= ARG_CONST_STACK_SIZE,
> +	.arg3_type	= ARG_ANYTHING,
> +};
> +
> +static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func_id)
> +{
> +	switch (func_id) {
> +	case BPF_FUNC_map_lookup_elem:
> +		return &bpf_map_lookup_elem_proto;
> +	case BPF_FUNC_map_update_elem:
> +		return &bpf_map_update_elem_proto;
> +	case BPF_FUNC_map_delete_elem:
> +		return &bpf_map_delete_elem_proto;
> +	case BPF_FUNC_probe_read:
> +		return &bpf_probe_read_proto;
> +	default:
> +		return NULL;
> +	}
> +}
> +
> +/* bpf+kprobe programs can access fields of 'struct pt_regs' */
> +static bool kprobe_prog_is_valid_access(int off, int size, enum bpf_access_type type)
> +{
> +	/* check bounds */
> +	if (off < 0 || off >= sizeof(struct pt_regs))
> +		return false;
> +
> +	/* only read is allowed */
> +	if (type != BPF_READ)
> +		return false;
> +
> +	/* disallow misaligned access */
> +	if (off % size != 0)
> +		return false;
> +
> +	return true;
> +}
> +
> +static struct bpf_verifier_ops kprobe_prog_ops = {
> +	.get_func_proto  = kprobe_prog_func_proto,
> +	.is_valid_access = kprobe_prog_is_valid_access,
> +};
> +
> +static struct bpf_prog_type_list kprobe_tl = {
> +	.ops	= &kprobe_prog_ops,
> +	.type	= BPF_PROG_TYPE_KPROBE,
> +};
> +
> +static int __init register_kprobe_prog_ops(void)
> +{
> +	bpf_register_prog_type(&kprobe_tl);
> +	return 0;
> +}
> +late_initcall(register_kprobe_prog_ops);
> diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
> index 8fa549f6f528..dc3462507d7c 100644
> --- a/kernel/trace/trace_kprobe.c
> +++ b/kernel/trace/trace_kprobe.c
> @@ -1134,11 +1134,15 @@ static void
>  kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
>  {
>  	struct ftrace_event_call *call = &tk->tp.call;
> +	struct bpf_prog *prog = call->prog;
>  	struct kprobe_trace_entry_head *entry;
>  	struct hlist_head *head;
>  	int size, __size, dsize;
>  	int rctx;
>  
> +	if (prog && !trace_call_bpf(prog, regs))
> +		return;
> +
>  	head = this_cpu_ptr(call->perf_events);
>  	if (hlist_empty(head))
>  		return;
> @@ -1165,11 +1169,15 @@ kretprobe_perf_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
>  		    struct pt_regs *regs)
>  {
>  	struct ftrace_event_call *call = &tk->tp.call;
> +	struct bpf_prog *prog = call->prog;
>  	struct kretprobe_trace_entry_head *entry;
>  	struct hlist_head *head;
>  	int size, __size, dsize;
>  	int rctx;
>  
> +	if (prog && !trace_call_bpf(prog, regs))
> +		return;
> +
>  	head = this_cpu_ptr(call->perf_events);
>  	if (hlist_empty(head))
>  		return;
> 


-- 
Masami HIRAMATSU
Software Platform Research Dept. Linux Technology Research Center
Hitachi, Ltd., Yokohama Research Laboratory
E-mail: masami.hiramatsu.pt@hitachi.com

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox