[PATCH 1/2 v3] typec: tcpm: Validate source and sink caps

From: Badhri Jagan Sridharan
Date: Wed Oct 18 2017 - 16:23:25 EST


The source and sink caps should follow the following rules.
This patch validates whether the src_caps/snk_caps adheres
to it.

6.4.1 Capabilities Message
A Capabilities message (Source Capabilities message or Sink
Capabilities message) shall have at least one Power
Data Object for vSafe5V. The Capabilities message shall also
contain the sending Portâs information followed by up to
6 additional Power Data Objects. Power Data Objects in a
Capabilities message shall be sent in the following order:

1. The vSafe5V Fixed Supply Object shall always be the first object.
2. The remaining Fixed Supply Objects, if present, shall be sent
in voltage order; lowest to highest.
3. The Battery Supply Objects, if present shall be sent in Minimum
Voltage order; lowest to highest.
4. The Variable Supply (non-battery) Objects, if present, shall be
sent in Minimum Voltage order; lowest to highest.

Errors in source/sink_caps of the local port will prevent
the port registration. Whereas, errors in source caps of partner
device would only log them.

Signed-off-by: Badhri Jagan Sridharan <Badhri@xxxxxxxxxx>
---
Changelog since v1:
- rebased on top drivers/usb/typec/tcpm.c as suggested by
gregkh@xxxxxxxxxxxxxxxxxxx
- renamed nr_snk_*_pdo as suggested by dan.carpenter@xxxxxxxxxx
- removed stale comment as suggested by linux@xxxxxxxxxxxx
- removed the tests for nr_snk_*_pdo as suggested by
dan.carpenter@xxxxxxxxxx
- Fixed sytling as suggested by dan.carpenter@xxxxxxxxxx
- renamed tcpm_get_nr_type_pdos to nr_type_pdos as suggested by
dan.carpenter@xxxxxxxxxx
- fixed nr_type_pdos as suggested by dan.carpenter@xxxxxxxxxx
- tcpm_pd_select_pdo now checks for all matching variable/batt pdos
instead of the first matching one.

Changelog since v2:
- refactored error messages as suggested by
heikki.krogerus@xxxxxxxxxxxxxxx

drivers/usb/typec/tcpm.c | 116 +++++++++++++++++++++++++++++++++++++++++++----
include/linux/usb/pd.h | 2 +
include/linux/usb/tcpm.h | 4 +-
3 files changed, 111 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 8483d3e33853..e3f8b3da46b0 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -1256,6 +1256,86 @@ static void vdm_state_machine_work(struct work_struct *work)
mutex_unlock(&port->lock);
}

+static const char * const pdo_err[] = {
+ "",
+ " err: source/sink caps should atleast have vSafe5V",
+ " err: vSafe5V Fixed Supply Object Shall always be the first object",
+ " err: PDOs should be in the following order: Fixed; Battery; Variable",
+ " err: Fixed supply pdos should be in increasing order of their fixed voltage",
+ " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
+ " err: Variable/Batt supply pdos cannot have same min/max voltage",
+};
+
+static int tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ unsigned int i;
+
+ /* Should at least contain vSafe5v */
+ if (nr_pdo < 1)
+ return 1;
+
+ /* The vSafe5V Fixed Supply Object Shall always be the first object */
+ if (pdo_type(pdo[0]) != PDO_TYPE_FIXED ||
+ pdo_fixed_voltage(pdo[0]) != VSAFE5V)
+ return 2;
+
+ for (i = 1; i < nr_pdo; i++) {
+ if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) {
+ return 3;
+ } else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) {
+ enum pd_pdo_type type = pdo_type(pdo[i]);
+
+ switch (type) {
+ /*
+ * The remaining Fixed Supply Objects, if
+ * present, shall be sent in voltage order;
+ * lowest to highest.
+ */
+ case PDO_TYPE_FIXED:
+ if (pdo_fixed_voltage(pdo[i]) <=
+ pdo_fixed_voltage(pdo[i - 1]))
+ return 4;
+ break;
+ /*
+ * The Battery Supply Objects and Variable
+ * supply, if present shall be sent in Minimum
+ * Voltage order; lowest to highest.
+ */
+ case PDO_TYPE_VAR:
+ case PDO_TYPE_BATT:
+ if (pdo_min_voltage(pdo[i]) <
+ pdo_min_voltage(pdo[i - 1]))
+ return 5;
+ else if ((pdo_min_voltage(pdo[i]) ==
+ pdo_min_voltage(pdo[i - 1])) &&
+ (pdo_max_voltage(pdo[i]) ==
+ pdo_min_voltage(pdo[i - 1])))
+ return 6;
+ break;
+ default:
+ tcpm_log_force(port, " Unknown pdo type");
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ int err_index = tcpm_caps_err(port, pdo, nr_pdo);
+
+ if (err_index) {
+ tcpm_log_force(port, " %s", pdo_err[err_index]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
/*
* PD (data, control) command handling functions
*/
@@ -1278,6 +1358,9 @@ static void tcpm_pd_data_request(struct tcpm_port *port,

tcpm_log_source_caps(port);

+ tcpm_validate_caps(port, port->source_caps,
+ port->nr_source_caps);
+
/*
* This message may be received even if VBUS is not
* present. This is quite unexpected; see USB PD
@@ -3444,9 +3527,12 @@ static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo,
return nr_vdo;
}

-void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
- unsigned int nr_pdo)
+int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
{
+ if (tcpm_validate_caps(port, pdo, nr_pdo))
+ return -EINVAL;
+
mutex_lock(&port->lock);
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo);
switch (port->state) {
@@ -3466,16 +3552,20 @@ void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
break;
}
mutex_unlock(&port->lock);
+ return 0;
}
EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities);

-void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
- unsigned int nr_pdo,
- unsigned int max_snk_mv,
- unsigned int max_snk_ma,
- unsigned int max_snk_mw,
- unsigned int operating_snk_mw)
+int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo,
+ unsigned int max_snk_mv,
+ unsigned int max_snk_ma,
+ unsigned int max_snk_mw,
+ unsigned int operating_snk_mw)
{
+ if (tcpm_validate_caps(port, pdo, nr_pdo))
+ return -EINVAL;
+
mutex_lock(&port->lock);
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo);
port->max_snk_mv = max_snk_mv;
@@ -3494,6 +3584,7 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
break;
}
mutex_unlock(&port->lock);
+ return 0;
}
EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);

@@ -3529,7 +3620,15 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)

init_completion(&port->tx_complete);
init_completion(&port->swap_complete);
+ tcpm_debugfs_init(port);

+ if (tcpm_validate_caps(port, tcpc->config->src_pdo,
+ tcpc->config->nr_src_pdo) ||
+ tcpm_validate_caps(port, tcpc->config->snk_pdo,
+ tcpc->config->nr_snk_pdo)) {
+ err = -EINVAL;
+ goto out_destroy_wq;
+ }
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
tcpc->config->nr_src_pdo);
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo,
@@ -3584,7 +3683,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
}
}

- tcpm_debugfs_init(port);
mutex_lock(&port->lock);
tcpm_init(port);
mutex_unlock(&port->lock);
diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index e00051ced806..b3d41d7409b3 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -148,6 +148,8 @@ enum pd_pdo_type {
(PDO_TYPE(PDO_TYPE_FIXED) | (flags) | \
PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma))

+#define VSAFE5V 5000 /* mv units */
+
#define PDO_BATT_MAX_VOLT_SHIFT 20 /* 50mV units */
#define PDO_BATT_MIN_VOLT_SHIFT 10 /* 50mV units */
#define PDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 073197f0d2bb..bc419f93c90e 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -183,9 +183,9 @@ struct tcpm_port;
struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc);
void tcpm_unregister_port(struct tcpm_port *port);

-void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
+int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
unsigned int nr_pdo);
-void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
+int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
unsigned int nr_pdo,
unsigned int max_snk_mv,
unsigned int max_snk_ma,
--
2.15.0.rc1.287.g2b38de12cc-goog