[PATCH 1/2] staging: typec: tcpm: Validate source and sink caps
From: Badhri Jagan Sridharan
Date: Thu Sep 07 2017 - 21:25:13 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>
---
drivers/staging/typec/pd.h | 2 +
drivers/staging/typec/tcpm.c | 107 +++++++++++++++++++++++++++++++++++++++----
drivers/staging/typec/tcpm.h | 16 +++----
3 files changed, 108 insertions(+), 17 deletions(-)
diff --git a/drivers/staging/typec/pd.h b/drivers/staging/typec/pd.h
index 30b32ad72acd..122907f94348 100644
--- a/drivers/staging/typec/pd.h
+++ b/drivers/staging/typec/pd.h
@@ -139,6 +139,8 @@ enum pd_pdo_type {
#define PDO_FIXED_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT)
#define PDO_FIXED_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT)
+#define VSAFE5V 5000 /* mv units */
+
#define PDO_FIXED(mv, ma, flags) \
(PDO_TYPE(PDO_TYPE_FIXED) | (flags) | \
PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma))
diff --git a/drivers/staging/typec/tcpm.c b/drivers/staging/typec/tcpm.c
index 8af62e74d54c..58a2c279f7d1 100644
--- a/drivers/staging/typec/tcpm.c
+++ b/drivers/staging/typec/tcpm.c
@@ -1286,6 +1286,74 @@ static void vdm_state_machine_work(struct work_struct *work)
mutex_unlock(&port->lock);
}
+static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ unsigned int i;
+
+ /* Should at least contain vSafe5v */
+ if (nr_pdo < 1) {
+ tcpm_log_force(port,
+ " err: source/sink caps should atleast have vSafe5V");
+ return -EINVAL;
+ }
+
+ /* 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) {
+ tcpm_log_force(port,
+ " err: vSafe5V Fixed Supply Object Shall always be the first object");
+ return -EINVAL;
+ }
+
+ for (i = 1; i < nr_pdo; i++) {
+ if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) {
+ tcpm_log_force(port,
+ " err:PDOs should be in the following order: Fixed; Battery; Variable. pdo index:%u"
+ , i);
+ return -EINVAL;
+ } 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])) {
+ tcpm_log_force(port,
+ " err: Fixed supply pdos should be in increasing order, pdo index:%u"
+ , i);
+ return -EINVAL;
+ }
+ 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])) {
+ tcpm_log_force(port,
+ " err: Variable supply pdos should be in increasing order, pdo index:%u"
+ , i);
+ return -EINVAL;
+ }
+ break;
+ default:
+ tcpm_log_force(port, " Unknown pdo type");
+ }
+ }
+ }
+
+ return 0;
+}
+
/*
* PD (data, control) command handling functions
*/
@@ -1308,6 +1376,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
@@ -3475,9 +3546,13 @@ 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)) {
+ tcpm_log_force(port, "Invalid source caps");
+ return -EINVAL;
+ }
mutex_lock(&port->lock);
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo);
switch (port->state) {
@@ -3497,16 +3572,21 @@ 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)) {
+ tcpm_log_force(port, "Invalid source caps");
+ 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;
@@ -3525,6 +3605,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);
@@ -3560,7 +3641,16 @@ 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,
@@ -3620,7 +3710,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/drivers/staging/typec/tcpm.h b/drivers/staging/typec/tcpm.h
index 7e9a6b7b5cd6..ec7b9cc3bfef 100644
--- a/drivers/staging/typec/tcpm.h
+++ b/drivers/staging/typec/tcpm.h
@@ -140,14 +140,14 @@ 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,
- unsigned int nr_pdo);
-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_source_capabilities(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_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,
+ unsigned int max_snk_mw,
+ unsigned int operating_snk_mw);
void tcpm_vbus_change(struct tcpm_port *port);
void tcpm_cc_change(struct tcpm_port *port);
--
2.14.1.581.gf28d330327-goog