// SPDX-License-Identifier: GPL-2.0-only /* r8169_firmware.c: RealTek 8169/8168/8101 ethernet driver. * * Copyright (c) 2002 ShuChen * Copyright (c) 2003 - 2007 Francois Romieu * Copyright (c) a lot of people too. Please respect their work. * * See MAINTAINERS file for support contact information. */ #include #include #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(typeof(*((struct rtl_fw_phy_action *)0)->code)) 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 regno = (action & 0x0fff0000) >> 16; switch (action >> 28) { case PHY_READ: case PHY_DATA_OR: case PHY_DATA_AND: case PHY_MDIO_CHG: case PHY_CLEAR_READCOUNT: case PHY_WRITE: case PHY_WRITE_PREVIOUS: case PHY_DELAY_MS: 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 == 0) { fw_write = rtl_fw->phy_write; fw_read = rtl_fw->phy_read; } else if (data == 1) { fw_write = rtl_fw->mac_mcu_write; fw_read = rtl_fw->mac_mcu_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 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; }