Skip to content

Commit

Permalink
thunderbolt: Perform USB4 router NVM upgrade in two phases
Browse files Browse the repository at this point in the history
The currect code expects that the router returns back the status of the
NVM authentication immediately. When tested against a real USB4 device
what happens is that the router is reset and only after that the result
is updated in the ROUTER_CS_26 register status field. This also seems to
align better what the spec suggests.

For this reason do the same what we already do with the Thunderbolt 3
devices and perform the NVM upgrade in two phases. First start the
NVM_AUTH router operation and once the router is added back after the
reset read the status in ROUTER_CS_26 and expose it to the userspace
accordingly.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
  • Loading branch information
Mika Westerberg committed Nov 30, 2020
1 parent 463e48f commit 661b194
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 20 deletions.
20 changes: 17 additions & 3 deletions drivers/thunderbolt/switch.c
Original file line number Diff line number Diff line change
Expand Up @@ -2160,6 +2160,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)

fallthrough;
case 3:
case 4:
ret = tb_switch_set_uuid(sw);
if (ret)
return ret;
Expand All @@ -2175,6 +2176,22 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
break;
}

if (sw->no_nvm_upgrade)
return 0;

if (tb_switch_is_usb4(sw)) {
ret = usb4_switch_nvm_authenticate_status(sw, &status);
if (ret)
return ret;

if (status) {
tb_sw_info(sw, "switch flash authentication failed\n");
nvm_set_auth_status(sw, status);
}

return 0;
}

/* Root switch DMA port requires running firmware */
if (!tb_route(sw) && !tb_switch_is_icm(sw))
return 0;
Expand All @@ -2183,9 +2200,6 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
if (!sw->dma_port)
return 0;

if (sw->no_nvm_upgrade)
return 0;

/*
* If there is status already set then authentication failed
* when the dma_port_flash_update_auth() returned. Power cycling
Expand Down
1 change: 1 addition & 0 deletions drivers/thunderbolt/tb.h
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
const void *buf, size_t size);
int usb4_switch_nvm_authenticate(struct tb_switch *sw);
int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status);
bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
Expand Down
1 change: 1 addition & 0 deletions drivers/thunderbolt/tb_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ struct tb_regs_switch_header {
#define ROUTER_CS_9 0x09
#define ROUTER_CS_25 0x19
#define ROUTER_CS_26 0x1a
#define ROUTER_CS_26_OPCODE_MASK GENMASK(15, 0)
#define ROUTER_CS_26_STATUS_MASK GENMASK(29, 24)
#define ROUTER_CS_26_STATUS_SHIFT 24
#define ROUTER_CS_26_ONS BIT(30)
Expand Down
75 changes: 58 additions & 17 deletions drivers/thunderbolt/usb4.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status)
if (val & ROUTER_CS_26_ONS)
return -EOPNOTSUPP;

*status = (val & ROUTER_CS_26_STATUS_MASK) >> ROUTER_CS_26_STATUS_SHIFT;
if (status)
*status = (val & ROUTER_CS_26_STATUS_MASK) >>
ROUTER_CS_26_STATUS_SHIFT;
return 0;
}

Expand Down Expand Up @@ -634,32 +636,71 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
* @sw: USB4 router
*
* After the new NVM has been written via usb4_switch_nvm_write(), this
* function triggers NVM authentication process. If the authentication
* is successful the router is power cycled and the new NVM starts
* function triggers NVM authentication process. The router gets power
* cycled and if the authentication is successful the new NVM starts
* running. In case of failure returns negative errno.
*
* The caller should call usb4_switch_nvm_authenticate_status() to read
* the status of the authentication after power cycle. It should be the
* first router operation to avoid the status being lost.
*/
int usb4_switch_nvm_authenticate(struct tb_switch *sw)
{
u8 status = 0;
int ret;

ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, &status);
ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, NULL);
switch (ret) {
/*
* The router is power cycled once NVM_AUTH is started so it is
* expected to get any of the following errors back.
*/
case -EACCES:
case -ENOTCONN:
case -ETIMEDOUT:
return 0;

default:
return ret;
}
}

/**
* usb4_switch_nvm_authenticate_status() - Read status of last NVM authenticate
* @sw: USB4 router
* @status: Status code of the operation
*
* The function checks if there is status available from the last NVM
* authenticate router operation. If there is status then %0 is returned
* and the status code is placed in @status. Returns negative errno in case
* of failure.
*
* Must be called before any other router operation.
*/
int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status)
{
u16 opcode;
u32 val;
int ret;

ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
if (ret)
return ret;

switch (status) {
case 0x0:
tb_sw_dbg(sw, "NVM authentication successful\n");
return 0;
case 0x1:
return -EINVAL;
case 0x2:
return -EAGAIN;
case 0x3:
return -EOPNOTSUPP;
default:
return -EIO;
/* Check that the opcode is correct */
opcode = val & ROUTER_CS_26_OPCODE_MASK;
if (opcode == USB4_SWITCH_OP_NVM_AUTH) {
if (val & ROUTER_CS_26_OV)
return -EBUSY;
if (val & ROUTER_CS_26_ONS)
return -EOPNOTSUPP;

*status = (val & ROUTER_CS_26_STATUS_MASK) >>
ROUTER_CS_26_STATUS_SHIFT;
} else {
*status = 0;
}

return 0;
}

/**
Expand Down

0 comments on commit 661b194

Please sign in to comment.