linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
@ 2021-04-26 19:18 Jack Pham
  2021-04-26 19:26 ` Jack Pham
  0 siblings, 1 reply; 7+ messages in thread
From: Jack Pham @ 2021-04-26 19:18 UTC (permalink / raw)
  To: Heikki Krogerus, Greg KH
  Cc: Subbaraman Narayanamurthy, linux-usb, stable, Jack Pham

commit 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects
in PD mode") introduced retrieval of the PDOs when connected to a
PD-capable source. But only the first 4 PDOs are received since
that is the maximum number that can be fetched at a time given the
MESSAGE_IN length limitation (16 bytes). However, as per the PD spec
a connected source may advertise up to a maximum of 7 PDOs.

If such a source is connected it's possible the UCSI FW could have
negotiated a power contract with one of the PDOs at index greater
than 4, and would be reflected in the request data object's (RDO)
object position field. This would result in an out-of-bounds access
when the rdo_index() is used to index into the src_pdos array in
ucsi_psy_get_voltage_now().

We can resolve this by instead retrieving and storing up to the
maximum of 7 PDOs in the con->src_pdos array. This would involve
two calls to the GET_PDOS command.

Fixes: 992a60ed0d5e ("usb: typec: ucsi: register with power_supply class")
Fixes: 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects in PD mode")
Cc: stable@vger.kernel.org
Signed-off-by: Jack Pham <jackp@codeaurora.org>
---
 drivers/usb/typec/ucsi/ucsi.c | 41 +++++++++++++++++++++++++++--------
 drivers/usb/typec/ucsi/ucsi.h |  6 +++--
 2 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 244270755ae6..ac214b855986 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -495,7 +495,8 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
 	}
 }
 
-static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
+static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
+		u32 *pdos, int offset, int num_pdos)
 {
 	struct ucsi *ucsi = con->ucsi;
 	u64 command;
@@ -503,17 +504,39 @@ static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
 
 	command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num);
 	command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner);
-	command |= UCSI_GET_PDOS_NUM_PDOS(UCSI_MAX_PDOS - 1);
+	command |= UCSI_GET_PDOS_PDO_OFFSET(offset);
+	command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1);
 	command |= UCSI_GET_PDOS_SRC_PDOS;
-	ret = ucsi_send_command(ucsi, command, con->src_pdos,
-			       sizeof(con->src_pdos));
-	if (ret < 0) {
+	ret = ucsi_send_command(ucsi, command, pdos + offset,
+			num_pdos * sizeof(u32));
+	if (ret < 0)
 		dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret);
+	if (ret == 0 && offset == 0)
+		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
+
+	return ret;
+}
+
+static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner)
+{
+	int ret;
+
+	/* UCSI max payload means only getting at most 4 PDOs at a time */
+	ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS);
+	if (ret < 0)
 		return;
-	}
+
 	con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
-	if (ret == 0)
-		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
+	if (con->num_pdos < UCSI_MAX_PDOS)
+		return;
+
+	/* get the remaining PDOs, if any */
+	ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS,
+			PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
+	if (ret < 0)
+		return;
+
+	con->num_pdos += ret / sizeof(u32);
 }
 
 static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
@@ -522,7 +545,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
 	case UCSI_CONSTAT_PWR_OPMODE_PD:
 		con->rdo = con->status.request_data_obj;
 		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
-		ucsi_get_pdos(con, 1);
+		ucsi_get_src_pdos(con, 1);
 		break;
 	case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
 		con->rdo = 0;
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index 3920e20a9e9e..cee666790907 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -8,6 +8,7 @@
 #include <linux/power_supply.h>
 #include <linux/types.h>
 #include <linux/usb/typec.h>
+#include <linux/usb/pd.h>
 #include <linux/usb/role.h>
 
 /* -------------------------------------------------------------------------- */
@@ -134,7 +135,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
 
 /* GET_PDOS command bits */
 #define UCSI_GET_PDOS_PARTNER_PDO(_r_)		((u64)(_r_) << 23)
+#define UCSI_GET_PDOS_PDO_OFFSET(_r_)		((u64)(_r_) << 24)
 #define UCSI_GET_PDOS_NUM_PDOS(_r_)		((u64)(_r_) << 32)
+#define UCSI_MAX_PDOS				(4)
 #define UCSI_GET_PDOS_SRC_PDOS			((u64)1 << 34)
 
 /* -------------------------------------------------------------------------- */
@@ -302,7 +305,6 @@ struct ucsi {
 
 #define UCSI_MAX_SVID		5
 #define UCSI_MAX_ALTMODES	(UCSI_MAX_SVID * 6)
-#define UCSI_MAX_PDOS		(4)
 
 #define UCSI_TYPEC_VSAFE5V	5000
 #define UCSI_TYPEC_1_5_CURRENT	1500
@@ -330,7 +332,7 @@ struct ucsi_connector {
 	struct power_supply *psy;
 	struct power_supply_desc psy_desc;
 	u32 rdo;
-	u32 src_pdos[UCSI_MAX_PDOS];
+	u32 src_pdos[PDO_MAX_OBJECTS];
 	int num_pdos;
 
 	struct usb_role_switch *usb_role_sw;
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
  2021-04-26 19:18 [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4 Jack Pham
@ 2021-04-26 19:26 ` Jack Pham
  2021-04-26 20:21   ` Subbaraman Narayanamurthy
  2021-05-03  7:03   ` Heikki Krogerus
  0 siblings, 2 replies; 7+ messages in thread
From: Jack Pham @ 2021-04-26 19:26 UTC (permalink / raw)
  To: Heikki Krogerus, Greg KH
  Cc: Subbaraman Narayanamurthy, linux-usb, stable, Abhilash K V

Forgot to Cc: Abhilash who introduced the PDO code

On Mon, Apr 26, 2021 at 12:18:25PM -0700, Jack Pham wrote:
> commit 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects
> in PD mode") introduced retrieval of the PDOs when connected to a
> PD-capable source. But only the first 4 PDOs are received since
> that is the maximum number that can be fetched at a time given the
> MESSAGE_IN length limitation (16 bytes). However, as per the PD spec
> a connected source may advertise up to a maximum of 7 PDOs.
> 
> If such a source is connected it's possible the UCSI FW could have
> negotiated a power contract with one of the PDOs at index greater
> than 4, and would be reflected in the request data object's (RDO)
> object position field. This would result in an out-of-bounds access
> when the rdo_index() is used to index into the src_pdos array in
> ucsi_psy_get_voltage_now().
> 
> We can resolve this by instead retrieving and storing up to the
> maximum of 7 PDOs in the con->src_pdos array. This would involve
> two calls to the GET_PDOS command.
> 
> Fixes: 992a60ed0d5e ("usb: typec: ucsi: register with power_supply class")
> Fixes: 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects in PD mode")
> Cc: stable@vger.kernel.org
> Signed-off-by: Jack Pham <jackp@codeaurora.org>
> ---
>  drivers/usb/typec/ucsi/ucsi.c | 41 +++++++++++++++++++++++++++--------
>  drivers/usb/typec/ucsi/ucsi.h |  6 +++--
>  2 files changed, 36 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
> index 244270755ae6..ac214b855986 100644
> --- a/drivers/usb/typec/ucsi/ucsi.c
> +++ b/drivers/usb/typec/ucsi/ucsi.c
> @@ -495,7 +495,8 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
>  	}
>  }
>  
> -static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
> +static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
> +		u32 *pdos, int offset, int num_pdos)
>  {
>  	struct ucsi *ucsi = con->ucsi;
>  	u64 command;
> @@ -503,17 +504,39 @@ static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
>  
>  	command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num);
>  	command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner);
> -	command |= UCSI_GET_PDOS_NUM_PDOS(UCSI_MAX_PDOS - 1);
> +	command |= UCSI_GET_PDOS_PDO_OFFSET(offset);
> +	command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1);
>  	command |= UCSI_GET_PDOS_SRC_PDOS;
> -	ret = ucsi_send_command(ucsi, command, con->src_pdos,
> -			       sizeof(con->src_pdos));
> -	if (ret < 0) {
> +	ret = ucsi_send_command(ucsi, command, pdos + offset,
> +			num_pdos * sizeof(u32));
> +	if (ret < 0)
>  		dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret);
> +	if (ret == 0 && offset == 0)
> +		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
> +
> +	return ret;
> +}
> +
> +static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner)
> +{
> +	int ret;
> +
> +	/* UCSI max payload means only getting at most 4 PDOs at a time */
> +	ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS);
> +	if (ret < 0)
>  		return;
> -	}
> +
>  	con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
> -	if (ret == 0)
> -		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
> +	if (con->num_pdos < UCSI_MAX_PDOS)
> +		return;
> +
> +	/* get the remaining PDOs, if any */
> +	ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS,
> +			PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
> +	if (ret < 0)
> +		return;
> +
> +	con->num_pdos += ret / sizeof(u32);
>  }
>  
>  static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
> @@ -522,7 +545,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
>  	case UCSI_CONSTAT_PWR_OPMODE_PD:
>  		con->rdo = con->status.request_data_obj;
>  		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
> -		ucsi_get_pdos(con, 1);
> +		ucsi_get_src_pdos(con, 1);
>  		break;
>  	case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
>  		con->rdo = 0;
> diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
> index 3920e20a9e9e..cee666790907 100644
> --- a/drivers/usb/typec/ucsi/ucsi.h
> +++ b/drivers/usb/typec/ucsi/ucsi.h
> @@ -8,6 +8,7 @@
>  #include <linux/power_supply.h>
>  #include <linux/types.h>
>  #include <linux/usb/typec.h>
> +#include <linux/usb/pd.h>
>  #include <linux/usb/role.h>
>  
>  /* -------------------------------------------------------------------------- */
> @@ -134,7 +135,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
>  
>  /* GET_PDOS command bits */
>  #define UCSI_GET_PDOS_PARTNER_PDO(_r_)		((u64)(_r_) << 23)
> +#define UCSI_GET_PDOS_PDO_OFFSET(_r_)		((u64)(_r_) << 24)
>  #define UCSI_GET_PDOS_NUM_PDOS(_r_)		((u64)(_r_) << 32)
> +#define UCSI_MAX_PDOS				(4)
>  #define UCSI_GET_PDOS_SRC_PDOS			((u64)1 << 34)
>  
>  /* -------------------------------------------------------------------------- */
> @@ -302,7 +305,6 @@ struct ucsi {
>  
>  #define UCSI_MAX_SVID		5
>  #define UCSI_MAX_ALTMODES	(UCSI_MAX_SVID * 6)
> -#define UCSI_MAX_PDOS		(4)
>  
>  #define UCSI_TYPEC_VSAFE5V	5000
>  #define UCSI_TYPEC_1_5_CURRENT	1500
> @@ -330,7 +332,7 @@ struct ucsi_connector {
>  	struct power_supply *psy;
>  	struct power_supply_desc psy_desc;
>  	u32 rdo;
> -	u32 src_pdos[UCSI_MAX_PDOS];
> +	u32 src_pdos[PDO_MAX_OBJECTS];
>  	int num_pdos;
>  
>  	struct usb_role_switch *usb_role_sw;
> -- 
> 2.24.0
> 

-- 
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
  2021-04-26 19:26 ` Jack Pham
@ 2021-04-26 20:21   ` Subbaraman Narayanamurthy
  2021-04-26 20:42     ` Jack Pham
  2021-05-03  7:03   ` Heikki Krogerus
  1 sibling, 1 reply; 7+ messages in thread
From: Subbaraman Narayanamurthy @ 2021-04-26 20:21 UTC (permalink / raw)
  To: jackp; +Cc: abhilash.k.v, gregkh, heikki.krogerus, linux-usb, stable, subbaram

> If such a source is connected it's possible the UCSI FW could have

s/UCSI FW/PPM

> We can resolve this by instead retrieving and storing up to the
> maximum of 7 PDOs in the con->src_pdos array. This would involve
> two calls to the GET_PDOS command.
> 

This issue (see the signature below) is found by enabling UBSAN and
connecting a charger adapter that can advertise 5 PDOs and RPDO selected
by PPM is 5.

[  151.545106][   T70] Unexpected kernel BRK exception at EL1
[  151.545112][   T70] Internal error: BRK handler: f2005512 [#1] PREEMPT SMP
...
[  151.545499][   T70] pc : ucsi_psy_get_prop+0x208/0x20c
[  151.545507][   T70] lr : power_supply_show_property+0xc0/0x328
...
[  151.545542][   T70] Call trace:
[  151.545544][   T70]  ucsi_psy_get_prop+0x208/0x20c
[  151.545546][   T70]  power_supply_uevent+0x1a4/0x2f0
[  151.545550][   T70]  dev_uevent+0x200/0x384
[  151.545555][   T70]  kobject_uevent_env+0x1d4/0x7e8
[  151.545557][   T70]  power_supply_changed_work+0x174/0x31c
[  151.545562][   T70]  process_one_work+0x244/0x6f0
[  151.545564][   T70]  worker_thread+0x3e0/0xa64

> Fixes: 992a60ed0d5e ("usb: typec: ucsi: register with power_supply class")
> Fixes: 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects in PD mode")

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
  2021-04-26 20:21   ` Subbaraman Narayanamurthy
@ 2021-04-26 20:42     ` Jack Pham
  2021-04-26 22:36       ` Subbaraman Narayanamurthy
  0 siblings, 1 reply; 7+ messages in thread
From: Jack Pham @ 2021-04-26 20:42 UTC (permalink / raw)
  To: Subbaraman Narayanamurthy
  Cc: abhilash.k.v, gregkh, heikki.krogerus, linux-usb, stable

On Mon, Apr 26, 2021 at 01:21:31PM -0700, Subbaraman Narayanamurthy wrote:
> > If such a source is connected it's possible the UCSI FW could have
> 
> s/UCSI FW/PPM

Right, PPM is a more apt descriptor. Waiting to see if Heikki has any
feedback first before sending a V2.

> > We can resolve this by instead retrieving and storing up to the
> > maximum of 7 PDOs in the con->src_pdos array. This would involve
> > two calls to the GET_PDOS command.
> > 
> 
> This issue (see the signature below) is found by enabling UBSAN and
> connecting a charger adapter that can advertise 5 PDOs and RPDO selected
> by PPM is 5.
> 
> [  151.545106][   T70] Unexpected kernel BRK exception at EL1
> [  151.545112][   T70] Internal error: BRK handler: f2005512 [#1] PREEMPT SMP
> ...
> [  151.545499][   T70] pc : ucsi_psy_get_prop+0x208/0x20c
> [  151.545507][   T70] lr : power_supply_show_property+0xc0/0x328
> ...
> [  151.545542][   T70] Call trace:
> [  151.545544][   T70]  ucsi_psy_get_prop+0x208/0x20c
> [  151.545546][   T70]  power_supply_uevent+0x1a4/0x2f0
> [  151.545550][   T70]  dev_uevent+0x200/0x384
> [  151.545555][   T70]  kobject_uevent_env+0x1d4/0x7e8
> [  151.545557][   T70]  power_supply_changed_work+0x174/0x31c
> [  151.545562][   T70]  process_one_work+0x244/0x6f0
> [  151.545564][   T70]  worker_thread+0x3e0/0xa64

UBSAN FTW. Want me to copy this trace into the commit text as well?

Jack
-- 
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
  2021-04-26 20:42     ` Jack Pham
@ 2021-04-26 22:36       ` Subbaraman Narayanamurthy
  0 siblings, 0 replies; 7+ messages in thread
From: Subbaraman Narayanamurthy @ 2021-04-26 22:36 UTC (permalink / raw)
  To: jackp; +Cc: abhilash.k.v, gregkh, heikki.krogerus, linux-usb, stable, subbaram

> UBSAN FTW. Want me to copy this trace into the commit text as well?

Yes, I think it would be good as this issue is found by enabling UBSAN.
Also, it gives the context that by inserting a PD charger adapter that
can support more than 4 PDOs, device can crash because of an OOB access.

-Subbaraman

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
  2021-04-26 19:26 ` Jack Pham
  2021-04-26 20:21   ` Subbaraman Narayanamurthy
@ 2021-05-03  7:03   ` Heikki Krogerus
  2021-05-03  7:29     ` Jack Pham
  1 sibling, 1 reply; 7+ messages in thread
From: Heikki Krogerus @ 2021-05-03  7:03 UTC (permalink / raw)
  To: Jack Pham
  Cc: Greg KH, Subbaraman Narayanamurthy, linux-usb, stable, Abhilash K V

Hi,

On Mon, Apr 26, 2021 at 12:26:05PM -0700, Jack Pham wrote:
> Forgot to Cc: Abhilash who introduced the PDO code
> 
> On Mon, Apr 26, 2021 at 12:18:25PM -0700, Jack Pham wrote:
> > commit 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects
> > in PD mode") introduced retrieval of the PDOs when connected to a
> > PD-capable source. But only the first 4 PDOs are received since
> > that is the maximum number that can be fetched at a time given the
> > MESSAGE_IN length limitation (16 bytes). However, as per the PD spec
> > a connected source may advertise up to a maximum of 7 PDOs.
> > 
> > If such a source is connected it's possible the UCSI FW could have
> > negotiated a power contract with one of the PDOs at index greater
> > than 4, and would be reflected in the request data object's (RDO)
> > object position field. This would result in an out-of-bounds access
> > when the rdo_index() is used to index into the src_pdos array in
> > ucsi_psy_get_voltage_now().
> > 
> > We can resolve this by instead retrieving and storing up to the
> > maximum of 7 PDOs in the con->src_pdos array. This would involve
> > two calls to the GET_PDOS command.

This makes sense to me. A few nitpicks below. Besides those:

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> > Fixes: 992a60ed0d5e ("usb: typec: ucsi: register with power_supply class")
> > Fixes: 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects in PD mode")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Jack Pham <jackp@codeaurora.org>
> > ---
> >  drivers/usb/typec/ucsi/ucsi.c | 41 +++++++++++++++++++++++++++--------
> >  drivers/usb/typec/ucsi/ucsi.h |  6 +++--
> >  2 files changed, 36 insertions(+), 11 deletions(-)
> > 
> > diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
> > index 244270755ae6..ac214b855986 100644
> > --- a/drivers/usb/typec/ucsi/ucsi.c
> > +++ b/drivers/usb/typec/ucsi/ucsi.c
> > @@ -495,7 +495,8 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
> >  	}
> >  }
> >  
> > -static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
> > +static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
> > +		u32 *pdos, int offset, int num_pdos)

That should be aligned properly.

> >  {
> >  	struct ucsi *ucsi = con->ucsi;
> >  	u64 command;
> > @@ -503,17 +504,39 @@ static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
> >  
> >  	command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num);
> >  	command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner);
> > -	command |= UCSI_GET_PDOS_NUM_PDOS(UCSI_MAX_PDOS - 1);
> > +	command |= UCSI_GET_PDOS_PDO_OFFSET(offset);
> > +	command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1);
> >  	command |= UCSI_GET_PDOS_SRC_PDOS;
> > -	ret = ucsi_send_command(ucsi, command, con->src_pdos,
> > -			       sizeof(con->src_pdos));
> > -	if (ret < 0) {
> > +	ret = ucsi_send_command(ucsi, command, pdos + offset,
> > +			num_pdos * sizeof(u32));

That too. I think if you run scripts/checkpatch.pl it will complain
about those.

> > +	if (ret < 0)
> >  		dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret);
> > +	if (ret == 0 && offset == 0)
> > +		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner)
> > +{
> > +	int ret;
> > +
> > +	/* UCSI max payload means only getting at most 4 PDOs at a time */
> > +	ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS);
> > +	if (ret < 0)
> >  		return;
> > -	}
> > +
> >  	con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
> > -	if (ret == 0)
> > -		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
> > +	if (con->num_pdos < UCSI_MAX_PDOS)
> > +		return;
> > +
> > +	/* get the remaining PDOs, if any */
> > +	ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS,
> > +			PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
> > +	if (ret < 0)
> > +		return;
> > +
> > +	con->num_pdos += ret / sizeof(u32);
> >  }
> >  
> >  static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
> > @@ -522,7 +545,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
> >  	case UCSI_CONSTAT_PWR_OPMODE_PD:
> >  		con->rdo = con->status.request_data_obj;
> >  		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
> > -		ucsi_get_pdos(con, 1);
> > +		ucsi_get_src_pdos(con, 1);
> >  		break;
> >  	case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
> >  		con->rdo = 0;
> > diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
> > index 3920e20a9e9e..cee666790907 100644
> > --- a/drivers/usb/typec/ucsi/ucsi.h
> > +++ b/drivers/usb/typec/ucsi/ucsi.h
> > @@ -8,6 +8,7 @@
> >  #include <linux/power_supply.h>
> >  #include <linux/types.h>
> >  #include <linux/usb/typec.h>
> > +#include <linux/usb/pd.h>
> >  #include <linux/usb/role.h>
> >  
> >  /* -------------------------------------------------------------------------- */
> > @@ -134,7 +135,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
> >  
> >  /* GET_PDOS command bits */
> >  #define UCSI_GET_PDOS_PARTNER_PDO(_r_)		((u64)(_r_) << 23)
> > +#define UCSI_GET_PDOS_PDO_OFFSET(_r_)		((u64)(_r_) << 24)
> >  #define UCSI_GET_PDOS_NUM_PDOS(_r_)		((u64)(_r_) << 32)
> > +#define UCSI_MAX_PDOS				(4)
> >  #define UCSI_GET_PDOS_SRC_PDOS			((u64)1 << 34)
> >  
> >  /* -------------------------------------------------------------------------- */
> > @@ -302,7 +305,6 @@ struct ucsi {
> >  
> >  #define UCSI_MAX_SVID		5
> >  #define UCSI_MAX_ALTMODES	(UCSI_MAX_SVID * 6)
> > -#define UCSI_MAX_PDOS		(4)
> >  
> >  #define UCSI_TYPEC_VSAFE5V	5000
> >  #define UCSI_TYPEC_1_5_CURRENT	1500
> > @@ -330,7 +332,7 @@ struct ucsi_connector {
> >  	struct power_supply *psy;
> >  	struct power_supply_desc psy_desc;
> >  	u32 rdo;
> > -	u32 src_pdos[UCSI_MAX_PDOS];
> > +	u32 src_pdos[PDO_MAX_OBJECTS];
> >  	int num_pdos;
> >  
> >  	struct usb_role_switch *usb_role_sw;

thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
  2021-05-03  7:03   ` Heikki Krogerus
@ 2021-05-03  7:29     ` Jack Pham
  0 siblings, 0 replies; 7+ messages in thread
From: Jack Pham @ 2021-05-03  7:29 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Greg KH, Subbaraman Narayanamurthy, linux-usb, stable, Abhilash K V

On Mon, May 03, 2021 at 10:03:20AM +0300, Heikki Krogerus wrote:
> On Mon, Apr 26, 2021 at 12:26:05PM -0700, Jack Pham wrote:
> > Forgot to Cc: Abhilash who introduced the PDO code
> > 
> > On Mon, Apr 26, 2021 at 12:18:25PM -0700, Jack Pham wrote:
> > > commit 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects
> > > in PD mode") introduced retrieval of the PDOs when connected to a
> > > PD-capable source. But only the first 4 PDOs are received since
> > > that is the maximum number that can be fetched at a time given the
> > > MESSAGE_IN length limitation (16 bytes). However, as per the PD spec
> > > a connected source may advertise up to a maximum of 7 PDOs.
> > > 
> > > If such a source is connected it's possible the UCSI FW could have
> > > negotiated a power contract with one of the PDOs at index greater
> > > than 4, and would be reflected in the request data object's (RDO)
> > > object position field. This would result in an out-of-bounds access
> > > when the rdo_index() is used to index into the src_pdos array in
> > > ucsi_psy_get_voltage_now().
> > > 
> > > We can resolve this by instead retrieving and storing up to the
> > > maximum of 7 PDOs in the con->src_pdos array. This would involve
> > > two calls to the GET_PDOS command.
> 
> This makes sense to me. A few nitpicks below. Besides those:
> 
> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

Thanks Heikki! I'll send a V2 along with the commit text improvements
Subbaraman had suggested.

> > > Fixes: 992a60ed0d5e ("usb: typec: ucsi: register with power_supply class")
> > > Fixes: 4dbc6a4ef06d ("usb: typec: ucsi: save power data objects in PD mode")
> > > Cc: stable@vger.kernel.org
> > > Signed-off-by: Jack Pham <jackp@codeaurora.org>
> > > ---
> > >  drivers/usb/typec/ucsi/ucsi.c | 41 +++++++++++++++++++++++++++--------
> > >  drivers/usb/typec/ucsi/ucsi.h |  6 +++--
> > >  2 files changed, 36 insertions(+), 11 deletions(-)
> > > 
> > > diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
> > > index 244270755ae6..ac214b855986 100644
> > > --- a/drivers/usb/typec/ucsi/ucsi.c
> > > +++ b/drivers/usb/typec/ucsi/ucsi.c
> > > @@ -495,7 +495,8 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
> > >  	}
> > >  }
> > >  
> > > -static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
> > > +static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
> > > +		u32 *pdos, int offset, int num_pdos)
> 
> That should be aligned properly.

Will do.

> > >  {
> > >  	struct ucsi *ucsi = con->ucsi;
> > >  	u64 command;
> > > @@ -503,17 +504,39 @@ static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
> > >  
> > >  	command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num);
> > >  	command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner);
> > > -	command |= UCSI_GET_PDOS_NUM_PDOS(UCSI_MAX_PDOS - 1);
> > > +	command |= UCSI_GET_PDOS_PDO_OFFSET(offset);
> > > +	command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1);
> > >  	command |= UCSI_GET_PDOS_SRC_PDOS;
> > > -	ret = ucsi_send_command(ucsi, command, con->src_pdos,
> > > -			       sizeof(con->src_pdos));
> > > -	if (ret < 0) {
> > > +	ret = ucsi_send_command(ucsi, command, pdos + offset,
> > > +			num_pdos * sizeof(u32));
> 
> That too. I think if you run scripts/checkpatch.pl it will complain
> about those.

Got it.

Actually checkpatch didn't warn about this. But I'll align to follow the
style in the rest of this file.

> > > +	if (ret < 0)
> > >  		dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret);
> > > +	if (ret == 0 && offset == 0)
> > > +		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner)
> > > +{
> > > +	int ret;
> > > +
> > > +	/* UCSI max payload means only getting at most 4 PDOs at a time */
> > > +	ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS);
> > > +	if (ret < 0)
> > >  		return;
> > > -	}
> > > +
> > >  	con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
> > > -	if (ret == 0)
> > > -		dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
> > > +	if (con->num_pdos < UCSI_MAX_PDOS)
> > > +		return;
> > > +
> > > +	/* get the remaining PDOs, if any */
> > > +	ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS,
> > > +			PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
> > > +	if (ret < 0)
> > > +		return;
> > > +
> > > +	con->num_pdos += ret / sizeof(u32);
> > >  }
> > >  
> > >  static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
> > > @@ -522,7 +545,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
> > >  	case UCSI_CONSTAT_PWR_OPMODE_PD:
> > >  		con->rdo = con->status.request_data_obj;
> > >  		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
> > > -		ucsi_get_pdos(con, 1);
> > > +		ucsi_get_src_pdos(con, 1);
> > >  		break;
> > >  	case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
> > >  		con->rdo = 0;
> > > diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
> > > index 3920e20a9e9e..cee666790907 100644
> > > --- a/drivers/usb/typec/ucsi/ucsi.h
> > > +++ b/drivers/usb/typec/ucsi/ucsi.h
> > > @@ -8,6 +8,7 @@
> > >  #include <linux/power_supply.h>
> > >  #include <linux/types.h>
> > >  #include <linux/usb/typec.h>
> > > +#include <linux/usb/pd.h>
> > >  #include <linux/usb/role.h>
> > >  
> > >  /* -------------------------------------------------------------------------- */
> > > @@ -134,7 +135,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
> > >  
> > >  /* GET_PDOS command bits */
> > >  #define UCSI_GET_PDOS_PARTNER_PDO(_r_)		((u64)(_r_) << 23)
> > > +#define UCSI_GET_PDOS_PDO_OFFSET(_r_)		((u64)(_r_) << 24)
> > >  #define UCSI_GET_PDOS_NUM_PDOS(_r_)		((u64)(_r_) << 32)
> > > +#define UCSI_MAX_PDOS				(4)
> > >  #define UCSI_GET_PDOS_SRC_PDOS			((u64)1 << 34)
> > >  
> > >  /* -------------------------------------------------------------------------- */
> > > @@ -302,7 +305,6 @@ struct ucsi {
> > >  
> > >  #define UCSI_MAX_SVID		5
> > >  #define UCSI_MAX_ALTMODES	(UCSI_MAX_SVID * 6)
> > > -#define UCSI_MAX_PDOS		(4)
> > >  
> > >  #define UCSI_TYPEC_VSAFE5V	5000
> > >  #define UCSI_TYPEC_1_5_CURRENT	1500
> > > @@ -330,7 +332,7 @@ struct ucsi_connector {
> > >  	struct power_supply *psy;
> > >  	struct power_supply_desc psy_desc;
> > >  	u32 rdo;
> > > -	u32 src_pdos[UCSI_MAX_PDOS];
> > > +	u32 src_pdos[PDO_MAX_OBJECTS];
> > >  	int num_pdos;
> > >  
> > >  	struct usb_role_switch *usb_role_sw;

Thanks,
Jack
-- 
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2021-05-03  7:30 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-26 19:18 [PATCH] usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4 Jack Pham
2021-04-26 19:26 ` Jack Pham
2021-04-26 20:21   ` Subbaraman Narayanamurthy
2021-04-26 20:42     ` Jack Pham
2021-04-26 22:36       ` Subbaraman Narayanamurthy
2021-05-03  7:03   ` Heikki Krogerus
2021-05-03  7:29     ` Jack Pham

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).