The firmware file is composed of the fw header and the commands. Each command has the following type. cmd(2 bytes) + length(2 bytes) + data(variable bytes) Before applying the firmware, the driver would check the fw header and each command. Signed-off-by: Hayes Wang <hayeswang@xxxxxxxxxxx> --- drivers/net/usb/r8152.c | 867 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 866 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c index 937d132..63542cc 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -21,10 +21,11 @@ #include <linux/list.h> #include <linux/ip.h> #include <linux/ipv6.h> +#include <linux/firmware.h> #include <net/ip6_checksum.h> /* Version Information */ -#define DRIVER_VERSION "v1.06.0 (2014/03/03)" +#define DRIVER_VERSION "v1.07.0 (2014/08/20)" #define DRIVER_AUTHOR "Realtek linux nic maintainers <nic_swsd@xxxxxxxxxxx>" #define DRIVER_DESC "Realtek RTL8152/RTL8153 Based USB Ethernet Adapters" #define MODULENAME "r8152" @@ -577,6 +578,16 @@ struct r8152 { void (*unload)(struct r8152 *); } rtl_ops; + struct rtl_fw { + const struct firmware *fw; + +#define RTL_VER_SIZE 32 + + char version[RTL_VER_SIZE]; + u8 *code; + size_t code_size; + } rtl_fw; + int intr_interval; u32 saved_wolopts; u32 msg_enable; @@ -1321,6 +1332,852 @@ err1: return -ENOMEM; } +#define FW_SIGNATURE 0x0bda8152 + +enum fw_cmd { + FW_CMD_INVALID = 0, + + FW_CMD_GENERIC_WRITE, + FW_CMD_WRITE_BYTE, + FW_CMD_WRITE_WORD, + FW_CMD_WRITE_DWORD, + FW_CMD_READ_BYTE, + FW_CMD_READ_WORD, + FW_CMD_READ_DWORD, + FW_CMD_W0W1_BYTE, + FW_CMD_W0W1_WORD, + FW_CMD_W0W1_DWORD, + FW_CMD_W0W1_CURRENT, + FW_CMD_WRITE_CURRENT_BYTE, + FW_CMD_WRITE_CURRENT_WORD, + FW_CMD_WRITE_CURRENT_DWORD, + FW_CMD_CMP, + FW_CMD_JMP, + FW_CMD_JE, + FW_CMD_JNE, + FW_CMD_JA, + FW_CMD_JAE, + FW_CMD_JB, + FW_CMD_JBE, + FW_CMD_CX, + FW_CMD_LOOP, + FW_CMD_LOOPE, + FW_CMD_LOOPNE, + FW_CMD_USLEEP, + + FW_CMD_END, + FW_CMD_MAX +}; + +struct fw_cmd_generic { + __le16 cmd; + __le16 length; +} __packed; + +struct fw_cmd_most_used { + __le16 type; + __le16 addr; +} __packed; + +struct fw_header { + __le32 signature; + char version[RTL_VER_SIZE]; + __le32 fw_start; + __le32 fw_len; +} __packed; + +static bool rtl_fw_format_ok(struct rtl_fw *rtl_fw) +{ + const struct firmware *fw = rtl_fw->fw; + struct fw_header *fw_header = (struct fw_header *)fw->data; + char *version = rtl_fw->version; + size_t i, size, start; + u8 checksum = 0; + bool rc = false; + + if (fw->size < sizeof(*fw_header)) + goto out; + + if (__le32_to_cpu(fw_header->signature) != FW_SIGNATURE) + goto out; + + start = le32_to_cpu(fw_header->fw_start); + if (start > fw->size) + goto out; + + size = le32_to_cpu(fw_header->fw_len); + if (size > (fw->size - start)) + goto out; + + for (i = 0; i < fw->size; i++) + checksum += fw->data[i]; + if (checksum != 0) + goto out; + + memcpy(version, fw_header->version, RTL_VER_SIZE); + + rtl_fw->code = (u8 *)(fw->data + start); + rtl_fw->code_size = size; + + version[RTL_VER_SIZE - 1] = 0; + + rc = true; +out: + return rc; +} + +static void rtl_fw_get_info2(u8 *d2, u16 *ptype, u16 *paddr) +{ + struct fw_cmd_most_used info2; + + memcpy(&info2, d2, sizeof(info2)); + *ptype = __le16_to_cpu(info2.type); + *paddr = __le16_to_cpu(info2.addr); +} + +static bool rtl_fw_data_ok(u8 *d, size_t total) +{ + u16 cmd, len, type, addr, byteen, size, cx = 0; + struct fw_cmd_generic op; + bool result = false; + __le16 le16_data; + __le32 le32_data; + size_t i = 0, j; + + while (i < total) { + if (i + sizeof(op) > total) + goto result_return; + + memcpy(&op, &d[i], sizeof(op)); + cmd = __le16_to_cpu(op.cmd); + len = __le16_to_cpu(op.length); + j = i + sizeof(op); + + switch (cmd) { + case FW_CMD_GENERIC_WRITE: + /* struct fw_cmd_generic_write { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le16 data_length; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + /* addr size must be the multiple of 4 */ + if (addr & 3) + goto result_return; + + byteen = type & 0xff; + type = type & ~0xff; + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + j += sizeof(le16_data); + size = __le16_to_cpu(le16_data); + + /* data size must be the multiple of 4 */ + if (size & 3) + goto result_return; + + size += sizeof(struct fw_cmd_most_used) + sizeof(size); + + break; + + case FW_CMD_WRITE_BYTE: + /* struct fw_cmd_generic_write { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * u8 data; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if (type & 0xff) + goto result_return; + + size = sizeof(struct fw_cmd_most_used) + 1; + break; + + case FW_CMD_WRITE_WORD: + /* struct fw_cmd_ocp_write_word { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le16 data; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if ((type & 0xff) || (addr & 1)) + goto result_return; + + size = sizeof(struct fw_cmd_most_used) + + sizeof(le16_data); + break; + + case FW_CMD_WRITE_DWORD: + /* struct fw_cmd_ocp_write_dword { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le32 data; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if ((type & 0xff) || (addr & 3)) + goto result_return; + + size = sizeof(struct fw_cmd_most_used) + + sizeof(le32_data); + break; + + case FW_CMD_READ_BYTE: + case FW_CMD_WRITE_CURRENT_BYTE: + /* struct fw_cmd_write_current { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if (type & 0xff) + goto result_return; + + size = sizeof(struct fw_cmd_most_used); + break; + + case FW_CMD_READ_WORD: + case FW_CMD_WRITE_CURRENT_WORD: + /* struct fw_cmd_write_current { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if ((type & 0xff) || (addr & 1)) + goto result_return; + + size = sizeof(struct fw_cmd_most_used); + break; + + case FW_CMD_READ_DWORD: + case FW_CMD_WRITE_CURRENT_DWORD: + /* struct fw_cmd_write_current { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if ((type & 0xff) || (addr & 3)) + goto result_return; + + size = sizeof(struct fw_cmd_most_used); + break; + + case FW_CMD_W0W1_BYTE: + /* struct fw_cmd_ocp_w0w1_byte { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * u8 w0; + * u8 w1; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if (type & 0xff) + goto result_return; + + size = sizeof(struct fw_cmd_most_used) + 2; + break; + + case FW_CMD_W0W1_WORD: + /* struct fw_cmd_ocp_w0w1_word { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le16 w0; + * __le16 w1; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if ((type & 0xff) || (addr & 1)) + goto result_return; + + size = sizeof(struct fw_cmd_most_used) + + sizeof(le16_data) * 2; + break; + + case FW_CMD_W0W1_DWORD: + /* struct fw_cmd_ocp_w0w1_dword { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le32 w0; + * __le32 w1; + * }; + */ + + if ((j + sizeof(struct fw_cmd_most_used)) > total) + goto result_return; + + rtl_fw_get_info2(&d[j], &type, &addr); + + if ((type & 0xff) || (addr & 3)) + goto result_return; + + size = sizeof(struct fw_cmd_most_used) + + sizeof(le32_data) * 2; + break; + + case FW_CMD_CMP: + /* struct fw_cmd_compare { + * struct fw_cmd_generic op; + * __le32 data; + * }; + */ + + size = sizeof(le32_data); + break; + + case FW_CMD_JMP: + case FW_CMD_JE: + case FW_CMD_JNE: + case FW_CMD_JA: + case FW_CMD_JAE: + case FW_CMD_JB: + case FW_CMD_JBE: + case FW_CMD_LOOP: + case FW_CMD_LOOPE: + case FW_CMD_LOOPNE: + /* struct fw_cmd_jump { + * struct fw_cmd_generic op; + * __le16 offset; + * }; + */ + + if (j + sizeof(le16_data) > total) + goto result_return; + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + j = (short)__le16_to_cpu(le16_data); + + j += sizeof(op) + len + i; + if (j < 0 || j > total) + goto result_return; + + size = sizeof(le16_data); + break; + + case FW_CMD_CX: + case FW_CMD_USLEEP: + /* struct fw_cmd_cx { + * struct fw_cmd_generic op; + * __le16 cx; + * }; + */ + + if (j + sizeof(le16_data) > total) + goto result_return; + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + cx = __le16_to_cpu(le16_data); + if (cx == 0) + goto result_return; + + size = sizeof(le16_data); + break; + + case FW_CMD_W0W1_CURRENT: + /* struct fw_cmd_ocp_w0w1_current { + * struct fw_cmd_generic op; + * __le32 w0; + * __le32 w1; + * }; + */ + size = sizeof(le32_data) * 2; + break; + + case FW_CMD_END: + size = 0; + goto result_return; + + default: + goto result_return; + } + + if (len != size) + goto result_return; + + i += sizeof(op) + len; + } + + if (i <= total) + result = true; + +result_return: + return result; +} + +static bool rtl_check_firmware(struct r8152 *tp, struct rtl_fw *fw) +{ + bool fw_ok = false; + + if (!rtl_fw_format_ok(&tp->rtl_fw)) + goto out; + + if (rtl_fw_data_ok(tp->rtl_fw.code, tp->rtl_fw.code_size)) + fw_ok = true; + +out: + return fw_ok; +} + +static void rtl_fw_write(struct r8152 *tp, u8 *d, size_t total) +{ + u16 cmd, len, type, addr, byteen, size, cx = 0, us; + u32 ocp_data = 0, compare = 0; + struct fw_cmd_generic op; + __le16 le16_data; + __le32 le32_data; + size_t i = 0, j; + + while (i < total) { + memcpy(&op, &d[i], sizeof(op)); + cmd = __le16_to_cpu(op.cmd); + len = __le16_to_cpu(op.length); + j = i + sizeof(op); + + switch (cmd) { + case FW_CMD_GENERIC_WRITE: + /* struct fw_cmd_generic_write { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le16 data_length; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + byteen = type & 0xff; + type = type & ~0xff; + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + j += sizeof(le16_data); + size = __le16_to_cpu(le16_data); + + generic_ocp_write(tp, addr, byteen, size, &d[j], type); + break; + + case FW_CMD_WRITE_BYTE: + /* struct fw_cmd_generic_write { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * u8 data; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + ocp_data = d[j]; + ocp_write_byte(tp, type, addr, ocp_data); + break; + + case FW_CMD_WRITE_WORD: + /* struct fw_cmd_ocp_write_word { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le16 data; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + ocp_data = __le16_to_cpu(le16_data); + + ocp_write_word(tp, type, addr, ocp_data); + break; + + case FW_CMD_WRITE_DWORD: + /* struct fw_cmd_ocp_write_dword { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le32 data; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + memcpy(&le32_data, &d[j], sizeof(le32_data)); + ocp_data = __le32_to_cpu(le32_data); + + ocp_write_dword(tp, type, addr, ocp_data); + break; + + case FW_CMD_READ_BYTE: + /* struct fw_cmd_ocp_read { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + ocp_data = ocp_read_byte(tp, type, addr); + break; + + case FW_CMD_READ_WORD: + /* struct fw_cmd_ocp_read { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + ocp_data = ocp_read_word(tp, type, addr); + break; + + case FW_CMD_READ_DWORD: + /* struct fw_cmd_ocp_read { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + ocp_data = ocp_read_dword(tp, type, addr); + break; + + case FW_CMD_W0W1_BYTE: + /* struct fw_cmd_ocp_w0w1_byte { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * u8 w0; + * u8 w1; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + ocp_data = ocp_read_byte(tp, type, addr); + ocp_data &= ~d[j++]; + ocp_data |= d[j++]; + ocp_write_byte(tp, type, addr, ocp_data); + break; + + case FW_CMD_W0W1_WORD: + /* struct fw_cmd_ocp_w0w1_word { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le16 w0; + * __le16 w1; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + ocp_data = ocp_read_word(tp, type, addr); + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + j += sizeof(le16_data); + ocp_data &= ~__le16_to_cpu(le16_data); + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + j += sizeof(le16_data); + ocp_data |= __le16_to_cpu(le16_data); + + ocp_write_word(tp, type, addr, ocp_data); + break; + + case FW_CMD_W0W1_DWORD: + /* struct fw_cmd_ocp_w0w1_dword { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * __le32 w0; + * __le32 w1; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + j += sizeof(struct fw_cmd_most_used); + + ocp_data = ocp_read_dword(tp, type, addr); + + memcpy(&le32_data, &d[j], sizeof(le32_data)); + j += sizeof(le32_data); + ocp_data &= ~__le32_to_cpu(le32_data); + + memcpy(&le32_data, &d[j], sizeof(le32_data)); + j += sizeof(le32_data); + ocp_data |= __le32_to_cpu(le32_data); + + ocp_write_dword(tp, type, addr, ocp_data); + break; + + case FW_CMD_W0W1_CURRENT: + /* struct fw_cmd_ocp_w0w1_current { + * struct fw_cmd_generic op; + * __le32 w0; + * __le32 w1; + * }; + */ + + memcpy(&le32_data, &d[j], sizeof(le32_data)); + j += sizeof(le32_data); + ocp_data &= ~__le32_to_cpu(le32_data); + + memcpy(&le32_data, &d[j], sizeof(le32_data)); + j += sizeof(le32_data); + ocp_data |= __le32_to_cpu(le32_data); + break; + + case FW_CMD_WRITE_CURRENT_BYTE: + /* struct fw_cmd_write_current { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + ocp_write_byte(tp, type, addr, ocp_data); + break; + + case FW_CMD_WRITE_CURRENT_WORD: + /* struct fw_cmd_write_current { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + ocp_write_word(tp, type, addr, ocp_data); + break; + + case FW_CMD_WRITE_CURRENT_DWORD: + /* struct fw_cmd_write_current { + * struct fw_cmd_generic op; + * struct fw_cmd_most_used info2; + * }; + */ + + rtl_fw_get_info2(&d[j], &type, &addr); + ocp_write_dword(tp, type, addr, ocp_data); + break; + + case FW_CMD_CMP: + /* struct fw_cmd_compare { + * struct fw_cmd_generic op; + * __le32 data; + * }; + */ + + memcpy(&le32_data, &d[j], sizeof(le32_data)); + compare = __le32_to_cpu(le32_data); + break; + + case FW_CMD_JMP: + /* struct fw_cmd_jump { + * struct fw_cmd_generic op; + * __le16 offset; + * }; + */ +do_jump: + memcpy(&le16_data, &d[j], sizeof(le16_data)); + j = (short)__le16_to_cpu(le16_data); + + i += sizeof(op) + len + j; + continue; + + case FW_CMD_JE: + if (ocp_data == compare) + goto do_jump; + + break; + + case FW_CMD_JNE: + if (ocp_data != compare) + goto do_jump; + + break; + + case FW_CMD_JA: + if (ocp_data > compare) + goto do_jump; + + break; + + case FW_CMD_JAE: + if (ocp_data >= compare) + goto do_jump; + + break; + + case FW_CMD_JB: + if (ocp_data < compare) + goto do_jump; + + break; + + case FW_CMD_JBE: + if (ocp_data <= compare) + goto do_jump; + + break; + + case FW_CMD_CX: + /* struct fw_cmd_cx { + * struct fw_cmd_generic op; + * __le16 cx; + * }; + */ + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + cx = __le16_to_cpu(le16_data); + break; + + case FW_CMD_LOOP: + if (--cx > 0) + goto do_jump; + + break; + + case FW_CMD_LOOPE: + if (--cx > 0 && ocp_data == compare) + goto do_jump; + + break; + + case FW_CMD_LOOPNE: + if (--cx > 0 && ocp_data != compare) + goto do_jump; + + break; + + case FW_CMD_USLEEP: + /* struct fw_cmd_usleep { + * struct fw_cmd_generic op; + * __le16 us; + * }; + */ + + memcpy(&le16_data, &d[j], sizeof(le16_data)); + us = __le16_to_cpu(le16_data); + usleep_range(us , us * 4); + break; + + case FW_CMD_END: + default: + return; + } + + i += sizeof(op) + len; + } +} + +static void rtl_release_firmware(struct r8152 *tp) +{ + if (!IS_ERR_OR_NULL(tp->rtl_fw.fw)) { + release_firmware(tp->rtl_fw.fw); + tp->rtl_fw.fw = NULL; + } +} + +static void rtl_request_firmware(struct r8152 *tp) +{ + char *fw_name = NULL; + + if (tp->rtl_fw.fw) + goto out_request; + + switch (tp->version) { + case RTL_VER_01: + fw_name = "rtl_nic/rtl8152-1.fw"; + break; + case RTL_VER_02: + fw_name = "rtl_nic/rtl8152-2.fw"; + break; + case RTL_VER_03: + fw_name = "rtl_nic/rtl8153-1.fw"; + break; + case RTL_VER_04: + fw_name = "rtl_nic/rtl8153-2.fw"; + break; + case RTL_VER_05: + fw_name = "rtl_nic/rtl8153-3.fw"; + break; + default: + goto out_request; + } + + if (request_firmware(&tp->rtl_fw.fw, fw_name, &tp->netdev->dev) < 0) + goto err_warn; + + if (!rtl_check_firmware(tp, &tp->rtl_fw)) { + netif_err(tp, ifup, tp->netdev, "invalid firwmare\n"); + goto err_release_firmware; + } + +out_request: + return; + +err_release_firmware: + release_firmware(tp->rtl_fw.fw); +err_warn: + netif_warn(tp, ifup, tp->netdev, "unable to load firmware patch %s\n", + fw_name); + tp->rtl_fw.fw = ERR_PTR(-ENOENT); + goto out_request; +} + +static void rtl_apply_firmware(struct r8152 *tp) +{ + if (!IS_ERR_OR_NULL(tp->rtl_fw.fw)) { + rtl_fw_write(tp, tp->rtl_fw.code, tp->rtl_fw.code_size); + tp->ocp_base = 0; + } +} + static struct tx_agg *r8152_get_tx_agg(struct r8152 *tp) { struct tx_agg *agg = NULL; @@ -2226,6 +3083,7 @@ static void r8152b_hw_phy_cfg(struct r8152 *tp) r8152b_disable_aldps(tp); + rtl_apply_firmware(tp); r8152b_enable_aldps(tp); set_bit(PHY_RESET, &tp->flags); @@ -2381,6 +3239,7 @@ static void r8153_hw_phy_cfg(struct r8152 *tp) r8152_mdio_write(tp, MII_BMCR, data); } + rtl_apply_firmware(tp); if (tp->version == RTL_VER_03) { data = ocp_reg_read(tp, OCP_EEE_CFG); @@ -2802,6 +3661,7 @@ static int rtl8152_open(struct net_device *netdev) tp->rtl_ops.disable(tp); } + rtl_request_firmware(tp); tp->rtl_ops.up(tp); rtl8152_set_speed(tp, AUTONEG_ENABLE, @@ -3130,6 +3990,10 @@ static void rtl8152_get_drvinfo(struct net_device *netdev, strlcpy(info->driver, MODULENAME, sizeof(info->driver)); strlcpy(info->version, DRIVER_VERSION, sizeof(info->version)); usb_make_path(tp->udev, info->bus_info, sizeof(info->bus_info)); + BUILD_BUG_ON(sizeof(info->fw_version) < sizeof(tp->rtl_fw.version)); + if (!IS_ERR_OR_NULL(tp->rtl_fw.fw)) + strlcpy(info->fw_version, tp->rtl_fw.version, + sizeof(info->fw_version)); } static @@ -3514,6 +4378,7 @@ static void rtl8152_disconnect(struct usb_interface *intf) tasklet_kill(&tp->tl); unregister_netdev(tp->netdev); tp->rtl_ops.unload(tp); + rtl_release_firmware(tp); free_netdev(tp->netdev); } } -- 1.9.3 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html