| // SPDX-License-Identifier: GPL-2.0-only |
| /* r8169_firmware.c: RealTek 8169/8168/8101 ethernet driver. |
| * |
| * Copyright (c) 2002 ShuChen <shuchen@realtek.com.tw> |
| * Copyright (c) 2003 - 2007 Francois Romieu <romieu@fr.zoreil.com> |
| * Copyright (c) a lot of people too. Please respect their work. |
| * |
| * See MAINTAINERS file for support contact information. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/firmware.h> |
| |
| #include "r8169_firmware.h" |
| |
| enum rtl_fw_opcode { |
| PHY_READ = 0x0, |
| PHY_DATA_OR = 0x1, |
| PHY_DATA_AND = 0x2, |
| PHY_BJMPN = 0x3, |
| PHY_MDIO_CHG = 0x4, |
| PHY_CLEAR_READCOUNT = 0x7, |
| PHY_WRITE = 0x8, |
| PHY_READCOUNT_EQ_SKIP = 0x9, |
| PHY_COMP_EQ_SKIPN = 0xa, |
| PHY_COMP_NEQ_SKIPN = 0xb, |
| PHY_WRITE_PREVIOUS = 0xc, |
| PHY_SKIPN = 0xd, |
| PHY_DELAY_MS = 0xe, |
| }; |
| |
| struct fw_info { |
| u32 magic; |
| char version[RTL_VER_SIZE]; |
| __le32 fw_start; |
| __le32 fw_len; |
| u8 chksum; |
| } __packed; |
| |
| #define FW_OPCODE_SIZE sizeof_field(struct rtl_fw_phy_action, code[0]) |
| |
| static bool rtl_fw_format_ok(struct rtl_fw *rtl_fw) |
| { |
| const struct firmware *fw = rtl_fw->fw; |
| struct fw_info *fw_info = (struct fw_info *)fw->data; |
| struct rtl_fw_phy_action *pa = &rtl_fw->phy_action; |
| |
| if (fw->size < FW_OPCODE_SIZE) |
| return false; |
| |
| if (!fw_info->magic) { |
| size_t i, size, start; |
| u8 checksum = 0; |
| |
| if (fw->size < sizeof(*fw_info)) |
| return false; |
| |
| for (i = 0; i < fw->size; i++) |
| checksum += fw->data[i]; |
| if (checksum != 0) |
| return false; |
| |
| start = le32_to_cpu(fw_info->fw_start); |
| if (start > fw->size) |
| return false; |
| |
| size = le32_to_cpu(fw_info->fw_len); |
| if (size > (fw->size - start) / FW_OPCODE_SIZE) |
| return false; |
| |
| strscpy(rtl_fw->version, fw_info->version, RTL_VER_SIZE); |
| |
| pa->code = (__le32 *)(fw->data + start); |
| pa->size = size; |
| } else { |
| if (fw->size % FW_OPCODE_SIZE) |
| return false; |
| |
| strscpy(rtl_fw->version, rtl_fw->fw_name, RTL_VER_SIZE); |
| |
| pa->code = (__le32 *)fw->data; |
| pa->size = fw->size / FW_OPCODE_SIZE; |
| } |
| |
| return true; |
| } |
| |
| static bool rtl_fw_data_ok(struct rtl_fw *rtl_fw) |
| { |
| struct rtl_fw_phy_action *pa = &rtl_fw->phy_action; |
| size_t index; |
| |
| for (index = 0; index < pa->size; index++) { |
| u32 action = le32_to_cpu(pa->code[index]); |
| u32 val = action & 0x0000ffff; |
| u32 regno = (action & 0x0fff0000) >> 16; |
| |
| switch (action >> 28) { |
| case PHY_READ: |
| case PHY_DATA_OR: |
| case PHY_DATA_AND: |
| case PHY_CLEAR_READCOUNT: |
| case PHY_WRITE: |
| case PHY_WRITE_PREVIOUS: |
| case PHY_DELAY_MS: |
| break; |
| |
| case PHY_MDIO_CHG: |
| if (val > 1) |
| goto out; |
| break; |
| |
| case PHY_BJMPN: |
| if (regno > index) |
| goto out; |
| break; |
| case PHY_READCOUNT_EQ_SKIP: |
| if (index + 2 >= pa->size) |
| goto out; |
| break; |
| case PHY_COMP_EQ_SKIPN: |
| case PHY_COMP_NEQ_SKIPN: |
| case PHY_SKIPN: |
| if (index + 1 + regno >= pa->size) |
| goto out; |
| break; |
| |
| default: |
| dev_err(rtl_fw->dev, "Invalid action 0x%08x\n", action); |
| return false; |
| } |
| } |
| |
| return true; |
| out: |
| dev_err(rtl_fw->dev, "Out of range of firmware\n"); |
| return false; |
| } |
| |
| void rtl_fw_write_firmware(struct rtl8169_private *tp, struct rtl_fw *rtl_fw) |
| { |
| struct rtl_fw_phy_action *pa = &rtl_fw->phy_action; |
| rtl_fw_write_t fw_write = rtl_fw->phy_write; |
| rtl_fw_read_t fw_read = rtl_fw->phy_read; |
| int predata = 0, count = 0; |
| size_t index; |
| |
| for (index = 0; index < pa->size; index++) { |
| u32 action = le32_to_cpu(pa->code[index]); |
| u32 data = action & 0x0000ffff; |
| u32 regno = (action & 0x0fff0000) >> 16; |
| enum rtl_fw_opcode opcode = action >> 28; |
| |
| if (!action) |
| break; |
| |
| switch (opcode) { |
| case PHY_READ: |
| predata = fw_read(tp, regno); |
| count++; |
| break; |
| case PHY_DATA_OR: |
| predata |= data; |
| break; |
| case PHY_DATA_AND: |
| predata &= data; |
| break; |
| case PHY_BJMPN: |
| index -= (regno + 1); |
| break; |
| case PHY_MDIO_CHG: |
| if (data) { |
| fw_write = rtl_fw->mac_mcu_write; |
| fw_read = rtl_fw->mac_mcu_read; |
| } else { |
| fw_write = rtl_fw->phy_write; |
| fw_read = rtl_fw->phy_read; |
| } |
| |
| break; |
| case PHY_CLEAR_READCOUNT: |
| count = 0; |
| break; |
| case PHY_WRITE: |
| fw_write(tp, regno, data); |
| break; |
| case PHY_READCOUNT_EQ_SKIP: |
| if (count == data) |
| index++; |
| break; |
| case PHY_COMP_EQ_SKIPN: |
| if (predata == data) |
| index += regno; |
| break; |
| case PHY_COMP_NEQ_SKIPN: |
| if (predata != data) |
| index += regno; |
| break; |
| case PHY_WRITE_PREVIOUS: |
| fw_write(tp, regno, predata); |
| break; |
| case PHY_SKIPN: |
| index += regno; |
| break; |
| case PHY_DELAY_MS: |
| msleep(data); |
| break; |
| } |
| } |
| } |
| |
| void rtl_fw_release_firmware(struct rtl_fw *rtl_fw) |
| { |
| release_firmware(rtl_fw->fw); |
| } |
| |
| int rtl_fw_request_firmware(struct rtl_fw *rtl_fw) |
| { |
| int rc; |
| |
| rc = request_firmware(&rtl_fw->fw, rtl_fw->fw_name, rtl_fw->dev); |
| if (rc < 0) |
| goto out; |
| |
| if (!rtl_fw_format_ok(rtl_fw) || !rtl_fw_data_ok(rtl_fw)) { |
| release_firmware(rtl_fw->fw); |
| rc = -EINVAL; |
| goto out; |
| } |
| |
| return 0; |
| out: |
| dev_err(rtl_fw->dev, "Unable to load firmware %s (%d)\n", |
| rtl_fw->fw_name, rc); |
| return rc; |
| } |