一个好用的linux平台MDIO读写程序

在进行网络驱动开发的过程中,我们经常需要通过命令行读取以太网物理层(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

通过这种方式,我们不仅提高了工作效率,还使得对网络硬件的操作变得更加直观和易于控制。无论是专业的网络驱动开发者还是对底层网络操作感兴趣的爱好者,都可以从这个小程序中受益。

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值