Tool to set MAC address in EEPROM of RTL8168/8111E devices
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

539 lines
10 KiB

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <pci/pci.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
/* utility to program MAC address of RTL8168 / RTL8111E EEPROM
* (C) 2015 by Harald Welte <hwelte@sysmocom.de>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
static void die(char *msg, ...)
{
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
fputc('\n', stderr);
exit(1);
}
static int macaddr_parse(uint8_t *out, const char *in)
{
/* 00:00:00:00:00:00 */
char tmp[18];
char *tok;
unsigned int i = 0;
if (strlen(in) < 17)
return -1;
strncpy(tmp, in, sizeof(tmp)-1);
tmp[sizeof(tmp)-1] = '\0';
for (tok = strtok(tmp, ":"); tok && (i < 6); tok = strtok(NULL, ":")) {
unsigned long ul = strtoul(tok, NULL, 16);
out[i++] = ul & 0xff;
}
return 0;
}
/*
* RTL8188E SPI Access
*/
enum rtlspi_pin {
RTLSPI_CS,
RTLSPI_CK,
RTLSPI_SI,
RTLSPI_SO,
};
const uint8_t pin2bit[] = {
#if 0
/* according to RTL8111F Series EEPROM & eFuse data sheet */
[RTLSPI_CS] = 0x40,
[RTLSPI_CK] = 0x20,
[RTLSPI_SI] = 0x10,
[RTLSPI_SO] = 0x08,
#define RTLSPI_C0_REG 0x51
#else
/* according to non-mainline driver patch, works on 8111E */
[RTLSPI_CS] = 0x08,
[RTLSPI_CK] = 0x04,
[RTLSPI_SI] = 0x02,
[RTLSPI_SO] = 0x01,
#define RTLSPI_C0_REG 0x50
#endif
};
static void rtlspi_delay(void)
{
usleep(3);
}
/* this is ugly and should go */
int g_memfd;
void *g_mapped_base;
#define rtl_reg_readb(offs) *((uint8_t *) g_mapped_base + offs)
#define rtl_reg_writeb(offs, val) *((uint8_t *) g_mapped_base + offs) = (val)
int rtlspi_init(struct pci_dev *d)
{
size_t page_size = (size_t) sysconf (_SC_PAGESIZE);
g_memfd = open("/dev/mem", O_RDWR | O_SYNC);
if (g_memfd < 0) {
perror("open");
return -1;
}
g_mapped_base = mmap(0, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, g_memfd, d->base_addr[2] & PCI_ADDR_MEM_MASK);
if (g_mapped_base == MAP_FAILED) {
perror("mmap");
close(g_memfd);
return -1;
}
#if 0
uint8_t v;
v = rtl_reg_readb(0x52);
v &= ~0xc0;
rtl_reg_writeb(0x52, v);
#endif
return 0;
}
int rtlspi_fini(struct pci_dev *d)
{
munmap(g_mapped_base, d->size[2]);
close(g_memfd);
return 0;
}
int rtlspi_pin_set(struct pci_dev *d, enum rtlspi_pin pin, int lvl)
{
uint8_t val;
if (pin > (sizeof(pin2bit)/sizeof(pin2bit[0])))
return -1;
val = rtl_reg_readb(RTLSPI_C0_REG);
val |= 0x80;
val &= ~0x40;
if (lvl)
val |= pin2bit[pin];
else
val &= ~pin2bit[pin];
rtl_reg_writeb(RTLSPI_C0_REG, val);
return 0;
}
int rtlspi_pin_get(struct pci_dev *d, enum rtlspi_pin pin)
{
uint8_t val;
if (pin > (sizeof(pin2bit)/sizeof(pin2bit[0])))
return -1;
val = rtl_reg_readb(RTLSPI_C0_REG);
if (val & pin2bit[pin])
return 1;
else
return 0;
}
int rtlspi_xceive_bit(struct pci_dev *d, int lvl)
{
int rc;
/* set the bit */
rc = rtlspi_pin_set(d, RTLSPI_SI, lvl);
if (rc < 0)
return rc;
rtlspi_delay();
/* raising edge on CK */
rc = rtlspi_pin_set(d, RTLSPI_CK, 1);
if (rc < 0)
return rc;
rtlspi_delay();
/* falling edge of clock */
rc = rtlspi_pin_set(d, RTLSPI_CK, 0);
if (rc < 0)
return rc;
rc = rtlspi_pin_get(d, RTLSPI_SO);
rtlspi_delay();
return rc;
}
int rtlspi_xceive_bits(struct pci_dev *d, uint32_t data, int num_bits)
{
int i, rc;
uint32_t in = 0;
for (i = 0; i < num_bits; i ++) {
uint32_t bit_out = (data >> (num_bits - i -1)) & 1;
in <<= 1;
rc = rtlspi_xceive_bit(d, bit_out);
if (rc < 0)
return rc;
in |= rc & 1;
}
return in;
}
/*
* AT93 EEPROM
*/
enum at93_op {
AT93_OP_MISC = 0,
AT93_OP_WRITE = 1,
AT93_OP_READ = 2,
AT93_OP_ERASE = 3,
};
static int at93c46_op(struct pci_dev *d, enum at93_op op, uint8_t addr, uint16_t data)
{
uint32_t in;
int rc;
/* set initial state of pins: CS/SK/DI low */
rc = rtlspi_pin_set(d, RTLSPI_CS, 0);
rc |= rtlspi_pin_set(d, RTLSPI_CK, 0);
rc |= rtlspi_pin_set(d, RTLSPI_SI, 0);
if (rc)
return -1;
rtlspi_delay();
/* start with raising edge on CS */
rc = rtlspi_pin_set(d, RTLSPI_CS, 1);
rtlspi_delay();
/* start bit */
rc = rtlspi_xceive_bit(d, 1);
/* READ command bits */
rc = rtlspi_xceive_bit(d, op & 2);
rc = rtlspi_xceive_bit(d, op & 1);
/* send address */
rc = rtlspi_xceive_bits(d, addr, 6);
switch (op) {
case AT93_OP_ERASE:
case AT93_OP_MISC:
in = 0;
break;
default:
/* read/write data */
in = rtlspi_xceive_bits(d, data, 16);
break;
}
/* stop with falling edge on CS */
rc = rtlspi_pin_set(d, RTLSPI_CS, 0);
if (rc)
return -1;
return in;
}
/* read one word */
int at93c46_op_read(struct pci_dev *d, uint8_t addr)
{
return at93c46_op(d, AT93_OP_READ, addr, 0);
}
/* write one word */
int at93c46_op_write(struct pci_dev *d, uint8_t addr, uint16_t data)
{
int rc = at93c46_op(d, AT93_OP_WRITE, addr, data);
if (rc < 0)
return rc;
return 0;
}
/* endable write access */
int at93c46_op_ewen(struct pci_dev *d)
{
return at93c46_op(d, AT93_OP_MISC, 0x30, 0);
}
/* endable write access */
int at93c46_op_erase(struct pci_dev *d, uint8_t addr)
{
int i, rc;
rc = at93c46_op(d, AT93_OP_ERASE, addr, 0);
if (rc < 0)
return rc;
/* wait until ERASE cycle has finished */
rtlspi_delay();
rtlspi_pin_set(d, RTLSPI_CS, 1);
for (i = 0; i < 0xffff; i++) {
rc = rtlspi_pin_get(d, RTLSPI_SO);
//rc = rtlspi_xceive_bit(d, 0);
if (rc == 1)
break;
}
rtlspi_pin_set(d, RTLSPI_CS, 0);
if (i == 0xffff) {
fprintf(stderr, "timeout during ERASE\n");
return -1;
}
return 0;
}
/* disable write access */
int at93c46_op_ewds(struct pci_dev *d)
{
return at93c46_op(d, AT93_OP_MISC, 0x00, 0);
}
static int eeprom_magic_ok(struct pci_dev *p)
{
if (at93c46_op_read(p, 0) != 0x8129)
return 0;
if (at93c46_op_read(p, 1) != 0x10ec)
return 0;
if (at93c46_op_read(p, 2) != 0x8168)
return 0;
return 1;
}
static int eeprom_get_mac(struct pci_dev *p, uint8_t *mac_addr)
{
uint16_t tmp;
if (!eeprom_magic_ok(p))
return -1;
tmp = at93c46_op_read(p, 7);
mac_addr[0] = tmp & 0xff;
mac_addr[1] = tmp >> 8;
tmp = at93c46_op_read(p, 8);
mac_addr[2] = tmp & 0xff;
mac_addr[3] = tmp >> 8;
tmp = at93c46_op_read(p, 9);
mac_addr[4] = tmp & 0xff;
mac_addr[5] = tmp >> 8;
return 0;
}
static int eeprom_set_mac(struct pci_dev *p, const uint8_t *mac_addr)
{
uint16_t tmp;
int rc = 0;
if (!eeprom_magic_ok(p))
return -1;
printf("Writing new MAC address %02x:%02x:%02x:%02x:%02x:%02x...\n",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3],
mac_addr[4], mac_addr[5]);
at93c46_op_ewen(p);
tmp = mac_addr[0] | mac_addr[1] << 8;
rc |= at93c46_op_erase(p, 7);
rc |= at93c46_op_write(p, 7, tmp);
tmp = mac_addr[2] | mac_addr[3] << 8;
rc |= at93c46_op_erase(p, 8);
rc |= at93c46_op_write(p, 8, tmp);
tmp = mac_addr[4] | mac_addr[5] << 8;
rc |= at93c46_op_erase(p, 9);
rc |= at93c46_op_write(p, 9, tmp);
if (rc)
die("Error during EEPROM WRITE, MAC address is corrupt!\n");
at93c46_op_ewds(p);
printf("You need to COLD BOOT for the new address to be used\n");
return 0;
}
/* generate a full dump */
static int eeprom_backup(struct pci_dev *p)
{
uint8_t mac_addr[6];
char fname[PATH_MAX];
int i, rc, outfd;
rc = eeprom_get_mac(p, mac_addr);
if (rc < 0)
return rc;
snprintf(fname, sizeof(fname), "%02X%02X%02X%02X%02X%02X.backup",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
outfd = open(fname, O_CREAT|O_WRONLY);
if (!outfd)
die("Can't open/create %s\n", fname);
printf("Saving EEPROM backup to %s\n", fname);
for (i = 0; i < 64; i++) {
uint8_t tmp[2];
int rc = at93c46_op_read(p, i);
if (rc < 0)
die("Error reading EEPROM addr %d\n", i);
tmp[0] = rc & 0xff;
tmp[1] = rc >> 8;
if (write(outfd, tmp, 2) != 2)
die("Error writing to backup file %s\n", fname);
}
close(outfd);
return 0;
}
static void iterate_devices(struct pci_access *pa, char *filter_id, char *filter_slot,
const uint8_t *new_mac)
{
struct pci_filter filt;
struct pci_dev *p;
char *msg;
/* filter for specific devices only */
printf("building filter\n");
pci_filter_init(pa, &filt);
if (filter_id) {
msg = pci_filter_parse_id(&filt, filter_id);
if (msg)
die(msg);
}
if (filter_slot) {
msg = pci_filter_parse_slot(&filt, filter_slot);
if (msg)
die(msg);
}
printf("starting bus iteration\n");
for (p = pa->devices; p; p = p->next) {
uint8_t old_mac[6];
if (!pci_filter_match(&filt, p))
continue;
/* our own clumsy implementation of filtering */
if (p->vendor_id != 0x10ec || p->device_id != 0x8168)
continue;
printf("found matching device (%02x:%02x.%d), ", p->bus, p->dev, p->func);
printf("base_addr=0x%lx (len=%lu)\n", p->base_addr[2] & PCI_ADDR_MEM_MASK, p->size[2]);
if (rtlspi_init(p) < 0)
die("Cannot initialize RTL SPI mode\n");
if (!eeprom_magic_ok(p))
die("EEPROM Magic !OK\n");
if (eeprom_get_mac(p, old_mac) < 0)
die("Cannot read existing MAC addr from EEPROM\n");
printf("Existing/Old MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
old_mac[0], old_mac[1], old_mac[2],
old_mac[3], old_mac[4], old_mac[5]);
/* will die itself */
eeprom_backup(p);
if (new_mac)
eeprom_set_mac(p, new_mac);
rtlspi_fini(p);
/* we support only one device per execution (for now) */
if (new_mac)
exit(0);
}
}
int main(int argc, char **argv)
{
int i;
char *filter_id = NULL;
char *filter_slot = NULL;
uint8_t new_macbuf[6];
uint8_t *new_mac = NULL;
struct pci_access *pa = pci_alloc();
/* same syntax as lspci */
while ((i = getopt(argc, argv, "d:s:m:")) != -1) {
switch (i) {
case 'd':
filter_id = optarg;
break;
case 's':
filter_slot = optarg;
break;
case 'm':
if (macaddr_parse(new_macbuf, optarg))
die("Unable to parse `%s' as mac address\n", optarg);
new_mac = new_macbuf;
break;
default:
die("Syntax error");
}
}
printf("initializing pci access\n");
pa->error = die;
pa->writeable = 1;
pci_init(pa);
pci_scan_bus(pa);
iterate_devices(pa, filter_id, filter_slot, new_mac);
exit(0);
}