在进行网络驱动开发的过程中,我们经常需要通过命令行读取以太网物理层(PHY)寄存器的信息。为了简化这一过程,我为大家提供了一个实用的小程序。这个程序允许我们通过命令行直接与最底层的MDIO接口进行交互。
这一操作的核心在于我们在操作系统内核中加入了一个特殊的模块(module)。这个模块的作用是建立一个旁路,允许我们直接访问和控制网络设备。具体来说,我们首先需要找到名为mdio_bus的类,这个类代表着我们要操作的MDIO总线。一旦找到了对应的MDIO总线,我们就可以使用bus->read(bus, addr, regnum)这样的函数来直接从硬件读取数据。这种方法直接作用于硬件层面,因此效率极高。下面是具体的代码实现:(源代码以及编译makefile)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <net/net_namespace.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/of_gpio.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/reset.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/phy.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#define NETLINK_USERSOCK 2
#define MAX_PAYLOAD 1024
enum mdio_operation {
MDIO_OP_INVALID = 0,
MDIO_OP_READ,
MDIO_OP_WRITE,
};
enum mdio_result {
MDIO_RESULT_INVALID = 0,
MDIO_RESULT_FAILURE,
MDIO_RESULT_SUCCESS,
};
struct mdio_message {
char bus_name[100];
enum mdio_operation op;
enum mdio_result result;
uint16_t addr;
uint32_t reg;
uint16_t data;
} __PACKED__;
struct sock *socket;
static void netlink_recvmsg(struct sk_buff *skb)
{
struct mii_bus *mdio_bus;
struct nlmsghdr *nlh = (struct nlmsghdr *) skb->data;
pid_t pid = nlh->nlmsg_pid;
struct mdio_message mdio_msg;
size_t message_size = sizeof(struct mdio_message);
struct sk_buff *skb_out;
int tmp;
memcpy(&mdio_msg, nlmsg_data(nlh), sizeof(mdio_msg));
mdio_bus = mdio_find_bus((const char *)mdio_msg.bus_name);
if (!mdio_bus) {
mdio_msg.result = MDIO_RESULT_FAILURE;
goto bailout;
}
if (mdio_msg.op == MDIO_OP_READ) {
tmp = mdiobus_read(mdio_bus, mdio_msg.addr, mdio_msg.reg);
if (tmp < 0) {
mdio_msg.result = MDIO_RESULT_FAILURE;
goto bailout;
}
mdio_msg.result = MDIO_RESULT_SUCCESS;
mdio_msg.data = (uint16_t) tmp;
} else if (mdio_msg.op == MDIO_OP_WRITE) {
tmp = mdiobus_write(mdio_bus, mdio_msg.addr, mdio_msg.reg, mdio_msg.data);
return;
}
bailout:
skb_out = nlmsg_new(message_size, GFP_KERNEL);
if (!skb_out) {
printk(KERN_ERR "Failed to allocate a new skb\n");
return;
}
nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, message_size, 0);
NETLINK_CB(skb_out).dst_group = 0;
memcpy(nlmsg_data(nlh), &mdio_msg, message_size);
nlmsg_unicast(socket, skb_out, pid);
}
static int __init mdio_proxy_init(void)
{
struct netlink_kernel_cfg nl_cfg = {
.input = netlink_recvmsg,
};
socket = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &nl_cfg);
if (socket == NULL) {
return -1;
}
printk(KERN_INFO "MDIO proxy module loaded.\n");
return 0;
}
static void __exit mdio_proxy_exit(void) {
if (socket) {
netlink_kernel_release(socket);
}
}
module_init(mdio_proxy_init);
module_exit(mdio_proxy_exit);
MODULE_LICENSE("Dual BSD/GPL");
#include <linux/types.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <dirent.h>
#define NETLINK_USERSOCK 2
#define MAX_PAYLOAD 1024
enum mdio_operation {
MDIO_OP_INVALID = 0,
MDIO_OP_READ,
MDIO_OP_WRITE,
};
enum mdio_result {
MDIO_RESULT_INVALID = 0,
MDIO_RESULT_FAILURE,
MDIO_RESULT_SUCCESS,
};
struct mdio_message {
char bus_name[100];
enum mdio_operation op;
enum mdio_result result;
uint16_t addr;
uint32_t reg;
uint16_t data;
} __PACKED__;
#define SYSFS_NET_PATH "/sys/class/net"
int findMdioBusForInterface(const char *interfaceName, char *mdioBusName) {
char sysfsPath[256];
char *mdioBus = NULL;
DIR *dir;
struct dirent *entry;
snprintf(sysfsPath, sizeof(sysfsPath), "%s/%s/device/mdio_bus", SYSFS_NET_PATH, interfaceName);
dir = opendir(sysfsPath);
if (dir == NULL) {
perror("opendir");
return -1;
}
while ((entry = readdir(dir)) != NULL) {
strcpy(mdioBusName,entry->d_name);
}
closedir(dir);
if(entry->d_name == "..") {
printf("%s not found mdio\n", interfaceName);
return -1;
}
return 0;
}
/* A very early proof-of-concept for accessing mdio busses from userspace */
int main(int argc, char *argv[])
{
int fd;
struct sockaddr_nl local;
struct sockaddr_nl kernel;
struct nlmsghdr *nlh;
struct iovec iov;
struct msghdr msg;
struct mdio_message mdio_msg;
int is_c45 = 0;
int retries = 10;
if ((argc < 5) || (argc > 7)) {
fprintf(stderr, "Wrong number of parameters provided.\n");
fprintf(stderr, "Usage:\n");
fprintf(stderr, "\t%s <bus> <addr> c22 <reg> [data]\n", argv[0]);
fprintf(stderr, "\t%s <bus> <addr> c45 <devad> <reg> [data]\n", argv[0]);
return -1;
}
memset(&mdio_msg, 0, sizeof(mdio_msg));
/* default operation is read */
mdio_msg.op = MDIO_OP_READ;
/* get bus name & desired address */
int ret = findMdioBusForInterface(argv[1], mdio_msg.bus_name);
if(ret < 0) {
printf("%s no mdio_bus.\n", argv[1]);
return 0;
}
printf("%s bus is %s\n",argv[1], mdio_msg.bus_name);
mdio_msg.addr = strtol(argv[2], NULL, 16);
mdio_msg.op = MDIO_OP_READ;
/* clause-45 access ? */
if (!strncmp("c45", argv[3], 3)) {
is_c45 = 1;
if(argc < 6) {
printf("args fail\n");
return 0;
}
mdio_msg.reg = (1<<30);
mdio_msg.reg |= strtol(argv[4], NULL, 16) << 16;
}
/* from this point everything gets offseted by is_c45 */
mdio_msg.reg |= strtol(argv[4 + is_c45], NULL, 16);
if (argc == (6 + is_c45)) {
mdio_msg.data = strtol(argv[5 + is_c45], NULL, 16);
mdio_msg.op = MDIO_OP_WRITE;
}
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK);
/* setup our receive side first */
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = 0;
if (bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0) {
perror("bind");
return -1;
}
/* send out message */
memset(&kernel, 0, sizeof(kernel));
kernel.nl_family = AF_NETLINK;
kernel.nl_pid = 0; /* kernel expects 0 */
kernel.nl_groups = 0;
nlh = malloc(NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), &mdio_msg, sizeof(mdio_msg));
memset(&iov, 0, sizeof(iov));
iov.iov_base = (void *) nlh;
iov.iov_len = nlh->nlmsg_len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *) &kernel;
msg.msg_namelen = sizeof(kernel);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
sendmsg(fd, &msg, 0);
if (mdio_msg.op == MDIO_OP_WRITE)
goto finish;
/* receive reply using the same iov and msg
* clear the nlh before recv
*/
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
while (--retries && !recvmsg(fd, &msg, 0)) {
usleep(10);
}
if (!retries) {
printf("Operation timeout\n");
goto finish;
}
memcpy(&mdio_msg, NLMSG_DATA(nlh), sizeof(mdio_msg));
if(mdio_msg.result != MDIO_RESULT_SUCCESS) {
printf("read %s fail,result=%d\n",mdio_msg.bus_name, mdio_msg.result);
}
else {
printf("Got back data: 0x%04x\n", mdio_msg.data);
}
finish:
free(nlh);
return 0;
}
obj-m += mdio-proxy.o
all: userapp module
module:
make -C $(KERNELDIR) M=$(PWD) modules
userapp:
$(CROSS_COMPILE)gcc mdio-app.c -o mdio-proxy
clean:
make -C $(KERNELDIR) M=$(PWD) clean
rm -f mdio-proxy
通过这种方式,我们不仅提高了工作效率,还使得对网络硬件的操作变得更加直观和易于控制。无论是专业的网络驱动开发者还是对底层网络操作感兴趣的爱好者,都可以从这个小程序中受益。