Skip to content

Commit 5007e1b

Browse files
Badhri Jagan Sridharangregkh
authored andcommitted
typec: tcpm: Validate source and sink caps
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 <[email protected]> Acked-by: Heikki Krogerus <[email protected]> Reviewed-by: Guenter Roeck <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 4bda35a commit 5007e1b

File tree

3 files changed

+131
-17
lines changed

3 files changed

+131
-17
lines changed

drivers/usb/typec/tcpm.c

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,100 @@ static void vdm_state_machine_work(struct work_struct *work)
12471247
mutex_unlock(&port->lock);
12481248
}
12491249

1250+
enum pdo_err {
1251+
PDO_NO_ERR,
1252+
PDO_ERR_NO_VSAFE5V,
1253+
PDO_ERR_VSAFE5V_NOT_FIRST,
1254+
PDO_ERR_PDO_TYPE_NOT_IN_ORDER,
1255+
PDO_ERR_FIXED_NOT_SORTED,
1256+
PDO_ERR_VARIABLE_BATT_NOT_SORTED,
1257+
PDO_ERR_DUPE_PDO,
1258+
};
1259+
1260+
static const char * const pdo_err_msg[] = {
1261+
[PDO_ERR_NO_VSAFE5V] =
1262+
" err: source/sink caps should atleast have vSafe5V",
1263+
[PDO_ERR_VSAFE5V_NOT_FIRST] =
1264+
" err: vSafe5V Fixed Supply Object Shall always be the first object",
1265+
[PDO_ERR_PDO_TYPE_NOT_IN_ORDER] =
1266+
" err: PDOs should be in the following order: Fixed; Battery; Variable",
1267+
[PDO_ERR_FIXED_NOT_SORTED] =
1268+
" err: Fixed supply pdos should be in increasing order of their fixed voltage",
1269+
[PDO_ERR_VARIABLE_BATT_NOT_SORTED] =
1270+
" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
1271+
[PDO_ERR_DUPE_PDO] =
1272+
" err: Variable/Batt supply pdos cannot have same min/max voltage",
1273+
};
1274+
1275+
static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
1276+
unsigned int nr_pdo)
1277+
{
1278+
unsigned int i;
1279+
1280+
/* Should at least contain vSafe5v */
1281+
if (nr_pdo < 1)
1282+
return PDO_ERR_NO_VSAFE5V;
1283+
1284+
/* The vSafe5V Fixed Supply Object Shall always be the first object */
1285+
if (pdo_type(pdo[0]) != PDO_TYPE_FIXED ||
1286+
pdo_fixed_voltage(pdo[0]) != VSAFE5V)
1287+
return PDO_ERR_VSAFE5V_NOT_FIRST;
1288+
1289+
for (i = 1; i < nr_pdo; i++) {
1290+
if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) {
1291+
return PDO_ERR_PDO_TYPE_NOT_IN_ORDER;
1292+
} else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) {
1293+
enum pd_pdo_type type = pdo_type(pdo[i]);
1294+
1295+
switch (type) {
1296+
/*
1297+
* The remaining Fixed Supply Objects, if
1298+
* present, shall be sent in voltage order;
1299+
* lowest to highest.
1300+
*/
1301+
case PDO_TYPE_FIXED:
1302+
if (pdo_fixed_voltage(pdo[i]) <=
1303+
pdo_fixed_voltage(pdo[i - 1]))
1304+
return PDO_ERR_FIXED_NOT_SORTED;
1305+
break;
1306+
/*
1307+
* The Battery Supply Objects and Variable
1308+
* supply, if present shall be sent in Minimum
1309+
* Voltage order; lowest to highest.
1310+
*/
1311+
case PDO_TYPE_VAR:
1312+
case PDO_TYPE_BATT:
1313+
if (pdo_min_voltage(pdo[i]) <
1314+
pdo_min_voltage(pdo[i - 1]))
1315+
return PDO_ERR_VARIABLE_BATT_NOT_SORTED;
1316+
else if ((pdo_min_voltage(pdo[i]) ==
1317+
pdo_min_voltage(pdo[i - 1])) &&
1318+
(pdo_max_voltage(pdo[i]) ==
1319+
pdo_min_voltage(pdo[i - 1])))
1320+
return PDO_ERR_DUPE_PDO;
1321+
break;
1322+
default:
1323+
tcpm_log_force(port, " Unknown pdo type");
1324+
}
1325+
}
1326+
}
1327+
1328+
return PDO_NO_ERR;
1329+
}
1330+
1331+
static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
1332+
unsigned int nr_pdo)
1333+
{
1334+
enum pdo_err err_index = tcpm_caps_err(port, pdo, nr_pdo);
1335+
1336+
if (err_index != PDO_NO_ERR) {
1337+
tcpm_log_force(port, " %s", pdo_err_msg[err_index]);
1338+
return -EINVAL;
1339+
}
1340+
1341+
return 0;
1342+
}
1343+
12501344
/*
12511345
* PD (data, control) command handling functions
12521346
*/
@@ -1269,6 +1363,9 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
12691363

12701364
tcpm_log_source_caps(port);
12711365

1366+
tcpm_validate_caps(port, port->source_caps,
1367+
port->nr_source_caps);
1368+
12721369
/*
12731370
* This message may be received even if VBUS is not
12741371
* present. This is quite unexpected; see USB PD
@@ -3435,9 +3532,12 @@ static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo,
34353532
return nr_vdo;
34363533
}
34373534

3438-
void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
3439-
unsigned int nr_pdo)
3535+
int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
3536+
unsigned int nr_pdo)
34403537
{
3538+
if (tcpm_validate_caps(port, pdo, nr_pdo))
3539+
return -EINVAL;
3540+
34413541
mutex_lock(&port->lock);
34423542
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo);
34433543
switch (port->state) {
@@ -3457,16 +3557,20 @@ void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
34573557
break;
34583558
}
34593559
mutex_unlock(&port->lock);
3560+
return 0;
34603561
}
34613562
EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities);
34623563

3463-
void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
3464-
unsigned int nr_pdo,
3465-
unsigned int max_snk_mv,
3466-
unsigned int max_snk_ma,
3467-
unsigned int max_snk_mw,
3468-
unsigned int operating_snk_mw)
3564+
int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
3565+
unsigned int nr_pdo,
3566+
unsigned int max_snk_mv,
3567+
unsigned int max_snk_ma,
3568+
unsigned int max_snk_mw,
3569+
unsigned int operating_snk_mw)
34693570
{
3571+
if (tcpm_validate_caps(port, pdo, nr_pdo))
3572+
return -EINVAL;
3573+
34703574
mutex_lock(&port->lock);
34713575
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo);
34723576
port->max_snk_mv = max_snk_mv;
@@ -3485,6 +3589,7 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
34853589
break;
34863590
}
34873591
mutex_unlock(&port->lock);
3592+
return 0;
34883593
}
34893594
EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
34903595

@@ -3520,7 +3625,15 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
35203625

35213626
init_completion(&port->tx_complete);
35223627
init_completion(&port->swap_complete);
3628+
tcpm_debugfs_init(port);
35233629

3630+
if (tcpm_validate_caps(port, tcpc->config->src_pdo,
3631+
tcpc->config->nr_src_pdo) ||
3632+
tcpm_validate_caps(port, tcpc->config->snk_pdo,
3633+
tcpc->config->nr_snk_pdo)) {
3634+
err = -EINVAL;
3635+
goto out_destroy_wq;
3636+
}
35243637
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
35253638
tcpc->config->nr_src_pdo);
35263639
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo,
@@ -3575,7 +3688,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
35753688
}
35763689
}
35773690

3578-
tcpm_debugfs_init(port);
35793691
mutex_lock(&port->lock);
35803692
tcpm_init(port);
35813693
mutex_unlock(&port->lock);

include/linux/usb/pd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ enum pd_pdo_type {
148148
(PDO_TYPE(PDO_TYPE_FIXED) | (flags) | \
149149
PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma))
150150

151+
#define VSAFE5V 5000 /* mv units */
152+
151153
#define PDO_BATT_MAX_VOLT_SHIFT 20 /* 50mV units */
152154
#define PDO_BATT_MIN_VOLT_SHIFT 10 /* 50mV units */
153155
#define PDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */

include/linux/usb/tcpm.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,14 @@ struct tcpm_port;
183183
struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc);
184184
void tcpm_unregister_port(struct tcpm_port *port);
185185

186-
void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
187-
unsigned int nr_pdo);
188-
void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
189-
unsigned int nr_pdo,
190-
unsigned int max_snk_mv,
191-
unsigned int max_snk_ma,
192-
unsigned int max_snk_mw,
193-
unsigned int operating_snk_mw);
186+
int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
187+
unsigned int nr_pdo);
188+
int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
189+
unsigned int nr_pdo,
190+
unsigned int max_snk_mv,
191+
unsigned int max_snk_ma,
192+
unsigned int max_snk_mw,
193+
unsigned int operating_snk_mw);
194194

195195
void tcpm_vbus_change(struct tcpm_port *port);
196196
void tcpm_cc_change(struct tcpm_port *port);

0 commit comments

Comments
 (0)