lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <D2EC7KK40YX5.C3G1SM3FEDJO@bootlin.com>
Date: Mon, 01 Jul 2024 18:19:12 +0200
From: Théo Lebrun <theo.lebrun@...tlin.com>
To: "Philipp Zabel" <p.zabel@...gutronix.de>, "Rob Herring"
 <robh@...nel.org>, "Krzysztof Kozlowski" <krzk+dt@...nel.org>, "Conor
 Dooley" <conor+dt@...nel.org>
Cc: <devicetree@...r.kernel.org>, <linux-kernel@...r.kernel.org>, "Vladimir
 Kondratiev" <vladimir.kondratiev@...ileye.com>,
 Grégory Clement <gregory.clement@...tlin.com>, "Thomas
 Petazzoni" <thomas.petazzoni@...tlin.com>, "Tawfik Bayouk"
 <tawfik.bayouk@...ileye.com>
Subject: Re: [PATCH 2/2] reset: eyeq: add platform driver

Hello Philipp,

On Mon Jul 1, 2024 at 10:59 AM CEST, Philipp Zabel wrote:
> On Fr, 2024-06-28 at 18:11 +0200, Théo Lebrun wrote:
> > Add Mobileye EyeQ reset controller driver, for EyeQ5, EyeQ6L and EyeQ6H
> > SoCs. Instances belong to a shared register region called OLB and gets
> > spawned as auxiliary device to the platform driver for clock.
> > 
> > There is one OLB instance for EyeQ5 and EyeQ6L. There are seven OLB
> > instances on EyeQ6H; three have a reset controller embedded:
> >  - West and east get handled by the same compatible.
> >  - Acc (accelerator) is another one.
> > 
> > Each instance vary in the number and types of reset domains.
> > Instances with single domain expect a single cell, others two.
> > 
> > Signed-off-by: Théo Lebrun <theo.lebrun@...tlin.com>
> > ---
> >  drivers/reset/Kconfig      |  13 ++
> >  drivers/reset/Makefile     |   1 +
> >  drivers/reset/reset-eyeq.c | 562 +++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 576 insertions(+)
> > 
> > diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
> > index 7112f5932609..14f3f4af0b10 100644
> > --- a/drivers/reset/Kconfig
> > +++ b/drivers/reset/Kconfig
> > @@ -66,6 +66,19 @@ config RESET_BRCMSTB_RESCAL
> >  	  This enables the RESCAL reset controller for SATA, PCIe0, or PCIe1 on
> >  	  BCM7216.
> >  
> > +config RESET_EYEQ
> > +	bool "Mobileye EyeQ reset controller"
> > +	depends on AUXILIARY_BUS
>
> This should
>
> 	select AUXILIARY_BUS
>
> instead.

Done, looking like this now:

config RESET_EYEQ
	bool "Mobileye EyeQ reset controller"
	depends on MACH_EYEQ5 || MACH_EYEQ6H || COMPILE_TEST
	select AUXILIARY_BUS
	default MACH_EYEQ5 || MACH_EYEQ6H
	help
	  ...

[...]

> > +#define EQR_MAX_DOMAIN_COUNT 3
> > +
> > +struct eqr_domain_descriptor {
> > +	enum eqr_domain_type	type;
> > +	u32			valid_mask;
> > +	unsigned int		offset;
> > +};
> > +
> > +struct eqr_match_data {
> > +	unsigned int				domain_count;
> > +	const struct eqr_domain_descriptor	*domains;
> > +};
> > +
> > +struct eqr_private {
> > +	struct mutex			mutexes[EQR_MAX_DOMAIN_COUNT];
>
> Is there any benefit from per-domain mutexes over just a single mutex?

Some domains can stay locked for pretty long in theory because of Logic
built-in self-test (LBIST). This is the reasoning behind
eqr_busy_wait_locked().

Other domains are not concerned by this behavior.

More concretely, on EyeQ5, SARCR and ACRP domains can lock their mutexes
for a relatively long time, and for good reasons. We wouldn't want the
PCIE domain  to suffer from that if it happens to (de)assert a reset at
the same time.

>
> > +	void __iomem			*base;
> > +	const struct eqr_match_data	*data;
> > +	struct reset_controller_dev	rcdev;
> > +};
> > +
> > +#define rcdev_to_priv(rcdev) container_of(rcdev, struct eqr_private, rcdev)
>
> Please use checkpatch --strict, and ideally mention when you ignore a
> warning on purpose. In this case, the macro parameter should named
> something else, because the last parameter to container_of must be
> "rcdev" verbatim. This only works by accident because the passed
> parameter also happens to be called called "rcdev" at all call sites.

I have let this CHECK from `checkpatch --strict` slip through indeed.
Other remaining messages, with explanations, are:

 - WARNING: added, moved or deleted file(s), does MAINTAINERS need
   updating?

   This is done in a single patch [0] in the MIPS series to avoid
   conflicts in between series.

 - CHECK: struct mutex definition without comment

   This is about the above mutexes field. Do you want a code comment
   about the reasoning for one mutex per domain?

 - WARNING: DT compatible string "[...]" appears un-documented

   Bindings are added in a single commit in the MIPS series [1],
   to avoid conflicts.

>
> > +static u32 eqr_double_readl(void __iomem *addr_a, void __iomem *addr_b,
> > +			    u32 *dest_a, u32 *dest_b)
> > +{
> > +	*dest_a = readl(addr_a);
> > +	*dest_b = readl(addr_b);
> > +	return 0; /* read_poll_timeout() op argument must return something. */
> > +}
> > +
> > +static int eqr_busy_wait_locked(struct eqr_private *priv, struct device *dev,
> > +				u32 domain, u32 offset, bool assert)
> > +{
> > +	enum eqr_domain_type domain_type = priv->data->domains[domain].type;
> > +	unsigned long sleep_us, timeout_us;
> > +	u32 val, mask, val0, val1;
> > +	void __iomem *base, *reg;
> > +	int ret;
> > +
> > +	lockdep_assert_held(&priv->mutexes[domain]);
> > +
> > +	base = priv->base + priv->data->domains[domain].offset;
> > +	sleep_us = eqr_timings[domain_type].sleep_us;
> > +	timeout_us = eqr_timings[domain_type].timeout_us;
>
> You can initialize these at the declaration.

Done, declarations will look like:

static int eqr_busy_wait_locked(struct eqr_private *priv, struct device *dev,
				u32 domain, u32 offset, bool assert)
{
	void __iomem *base = priv->base + priv->data->domains[domain].offset;
	enum eqr_domain_type domain_type = priv->data->domains[domain].type;
	unsigned long timeout_us = eqr_timings[domain_type].timeout_us;
	unsigned long sleep_us = eqr_timings[domain_type].sleep_us;
	u32 val, mask, val0, val1;
	void __iomem *reg;
	int ret;

	// ...
}

>
> > +
> > +	switch (domain_type) {
> > +	case EQR_EYEQ5_SARCR:
> > +		reg = base + EQR_EYEQ5_SARCR_STATUS;
> > +		mask = BIT(offset);
> > +
> > +		ret = readl_poll_timeout(reg, val, !(val & mask) == assert,
> > +					 sleep_us, timeout_us);
> > +		break;
> > +
> > +	case EQR_EYEQ5_ACRP:
> > +		reg = base + 4 * offset;
> > +		if (assert)
> > +			mask = EQR_EYEQ5_ACRP_ST_POWER_DOWN;
> > +		else
> > +			mask = EQR_EYEQ5_ACRP_ST_ACTIVE;
> > +
> > +		ret = readl_poll_timeout(reg, val, !!(val & mask),
> > +					 sleep_us, timeout_us);
> > +		break;
> > +
> > +	case EQR_EYEQ5_PCIE:
> > +		ret = 0; /* No busy waiting. */
> > +		break;
> > +
> > +	case EQR_EYEQ6H_SARCR:
> > +		/*
> > +		 * Wait until both bits change:
> > +		 *	readl(base + EQR_EYEQ6H_SARCR_RST_STATUS) & BIT(offset)
> > +		 *	readl(base + EQR_EYEQ6H_SARCR_CLK_STATUS) & BIT(offset)
> > +		 */
> > +		mask = BIT(offset);
> > +		ret = read_poll_timeout(eqr_double_readl, val,
> > +					(!(val0 & mask) == assert) &&
> > +						(!(val1 & mask) == assert),
>
> I'd remove a level of indentation here.

Indeed!

> > +					sleep_us, timeout_us, false,
> > +					base + EQR_EYEQ6H_SARCR_RST_STATUS,
> > +					base + EQR_EYEQ6H_SARCR_CLK_STATUS,
> > +					&val0, &val1);
>
> Calling these variables something like rst_status and clk_status, would
> make this a bit easier to parse.

It now looks like:

static int eqr_busy_wait_locked(struct eqr_private *priv, struct device *dev,
				u32 domain, u32 offset, bool assert)
{
	// ...

	switch (domain_type) {

	// ...

	case EQR_EYEQ6H_SARCR:
		/*
		 * Wait until both bits change:
		 *	readl(base + EQR_EYEQ6H_SARCR_RST_STATUS) & BIT(offset)
		 *	readl(base + EQR_EYEQ6H_SARCR_CLK_STATUS) & BIT(offset)
		 */
		mask = BIT(offset);
		ret = read_poll_timeout(eqr_double_readl, val,
					(!(rst_status & mask) == assert) &&
					(!(clk_status & mask) == assert),
					sleep_us, timeout_us, false,
					base + EQR_EYEQ6H_SARCR_RST_STATUS,
					base + EQR_EYEQ6H_SARCR_CLK_STATUS,
					&rst_status, &clk_status);
		break;

	// ...

	}

	// ...
}

>
> > +		break;
> > +
> > +	default:
> > +		WARN_ON(1);
> > +		ret = -EINVAL;
> > +		break;
> > +	}
> > +
> > +	if (ret == -ETIMEDOUT)
> > +		dev_dbg(dev, "%u-%u: timeout\n", domain, offset);
> > +	return ret;
> > +}
> > +
> > +static void eqr_assert_locked(struct eqr_private *priv, u32 domain, u32 offset)
> > +{
> > +	enum eqr_domain_type domain_type = priv->data->domains[domain].type;
> > +	void __iomem *base, *reg;
> > +	u32 val;
> > +
> > +	lockdep_assert_held(&priv->mutexes[domain]);
> > +
> > +	base = priv->base + priv->data->domains[domain].offset;
> > +
> > +	switch (domain_type) {
> > +	case EQR_EYEQ5_SARCR:
> > +		reg = base + EQR_EYEQ5_SARCR_REQUEST;
> > +		writel(readl(reg) & ~BIT(offset), reg);
> > +		break;
> > +
> > +	case EQR_EYEQ5_ACRP:
> > +		reg = base + 4 * offset;
> > +		writel(readl(reg) | EQR_EYEQ5_ACRP_PD_REQ, reg);
> > +		break;
> > +
> > +	case EQR_EYEQ5_PCIE:
> > +		writel(readl(base) & ~BIT(offset), base);
> > +		break;
> > +
> > +	case EQR_EYEQ6H_SARCR:
> > +		val = readl(base + EQR_EYEQ6H_SARCR_RST_REQUEST);
> > +		val &= ~BIT(offset);
> > +		writel(val, base + EQR_EYEQ6H_SARCR_RST_REQUEST);
> > +		writel(val, base + EQR_EYEQ6H_SARCR_CLK_REQUEST);
>
> This looks peculiar. Why is it ok to write the value read from
> RST_REQUEST into CLK_REQUEST?

What is abstracted away by the hardware on EyeQ5 is not anymore on
EyeQ6H. Previously a single register was used for requests and a single
register for status. Now there are two request registers and two status
registers.

Those registers *must be kept in sync*. The register name referencing
clock is not to be confused with the clock driver of the
system-controller. It is describing a register within the reset
controller.

This hardware interface is odd, I might add a comment?

[...]

> > +static int eqr_status(struct reset_controller_dev *rcdev, unsigned long id)
> > +{
> > +	u32 domain = FIELD_GET(ID_DOMAIN_MASK, id);
> > +	struct eqr_private *priv = rcdev_to_priv(rcdev);
> > +	enum eqr_domain_type domain_type = priv->data->domains[domain].type;
> > +	u32 offset = FIELD_GET(ID_OFFSET_MASK, id);
>
> I'd put domain and offset declaration next to each other.

Done:

static int eqr_status(struct reset_controller_dev *rcdev, unsigned long id)
{
	u32 domain = FIELD_GET(ID_DOMAIN_MASK, id);
	u32 offset = FIELD_GET(ID_OFFSET_MASK, id);
	struct eqr_private *priv = rcdev_to_priv(rcdev);
	enum eqr_domain_type domain_type = priv->data->domains[domain].type;
	void __iomem *base, *reg;

	// ...
}

I'll be sending a new revision in the week with those fixes.

Thanks Philipp,

[0]: https://lore.kernel.org/lkml/20240628-mbly-mips-v1-3-f53f5e4c422b@bootlin.com/
[1]: https://lore.kernel.org/lkml/20240628-mbly-mips-v1-1-f53f5e4c422b@bootlin.com/

--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ