// SPDX-License-Identifier: GPL-2.0-only /* ################################################################################ # # r8126 is the Linux device driver released for Realtek 5 Gigabit Ethernet # controllers with PCI-Express interface. # # Copyright(c) 2024 Realtek Semiconductor Corp. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, see . # # Author: # Realtek NIC software team # No. 2, Innovation Road II, Hsinchu Science Park, Hsinchu 300, Taiwan # ################################################################################ */ /************************************************************************************ * This product is covered by one or more of the following patents: * US6,570,884, US6,115,776, and US6,327,625. ***********************************************************************************/ #include #include #include #include "r8126_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[RTL8126_VER_SIZE]; __le32 fw_start; __le32 fw_len; u8 chksum; } __packed; #if LINUX_VERSION_CODE < KERNEL_VERSION(4,16,0) #define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER)) #endif #define FW_OPCODE_SIZE sizeof_field(struct rtl8126_fw_phy_action, code[0]) static bool rtl8126_fw_format_ok(struct rtl8126_fw *rtl_fw) { const struct firmware *fw = rtl_fw->fw; struct fw_info *fw_info = (struct fw_info *)fw->data; struct rtl8126_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, RTL8126_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, RTL8126_VER_SIZE); pa->code = (__le32 *)fw->data; pa->size = fw->size / FW_OPCODE_SIZE; } return true; } static bool rtl8126_fw_data_ok(struct rtl8126_fw *rtl_fw) { struct rtl8126_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 rtl8126_fw_write_firmware(struct rtl8126_private *tp, struct rtl8126_fw *rtl_fw) { struct rtl8126_fw_phy_action *pa = &rtl_fw->phy_action; rtl8126_fw_write_t fw_write = rtl_fw->phy_write; rtl8126_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: mdelay(data); break; } } } void rtl8126_fw_release_firmware(struct rtl8126_fw *rtl_fw) { release_firmware(rtl_fw->fw); } int rtl8126_fw_request_firmware(struct rtl8126_fw *rtl_fw) { int rc; rc = request_firmware(&rtl_fw->fw, rtl_fw->fw_name, rtl_fw->dev); if (rc < 0) goto out; if (!rtl8126_fw_format_ok(rtl_fw) || !rtl8126_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; }