蜗窝科技

以太网驱动的流程浅析(二)-Ifconfig的详细代码流程

heaven · · Linux内核分析 · 阅读 12285 · 2019年12月存档

我们继续上一节的内容往下分析代码

【硬件环境】 Imx6ul

【Linux kernel版本】 Linux4.1.15

【以太网phy】 Realtek8201f

1. 以太网流程分析跟踪

1.1 Ifconfig的详细代码流程

ret_fast_syscall ===》这是返回系统调用的syscall,大家可以看注释,saving r0,back into the SVC stack

arch/arm/kernel/entry-common.S

/*

 * This is the fast syscall return path.  We do as little as

 * possible here, and this includes saving r0 back into the SVC

 * stack.

 */

ret_fast_syscall:

 UNWIND(.fnstart	)

 UNWIND(.cantunwind	)

	disable_irq				@ disable interrupts

	ldr	r1, [tsk, #TI_FLAGS]		@ re-check for syscall tracing

	tst	r1, #_TIF_SYSCALL_WORK

	bne	__sys_trace_return

	tst	r1, #_TIF_WORK_MASK

	bne	fast_work_pending

	asm_trace_hardirqs_on



	/* perform architecture specific actions before user return */

	arch_ret_to_user r1, lr

	ct_user_enter



	restore_user_regs fast = 1, offset = S_OFF

 UNWIND(.fnend		)

fs/ioctl.c

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)

{

	int error;

	struct fd f = fdget(fd);



	if (!f.file)

		return -EBADF;

	error = security_file_ioctl(f.file, cmd, arg);

	if (!error)

		error = do_vfs_ioctl(f.file, fd, cmd, arg);

	fdput(f);

	return error;

}
/*

 * When you add any new common ioctls to the switches above and below

 * please update compat_sys_ioctl() too.

 *

 * do_vfs_ioctl() is not for drivers and not intended to be EXPORT_SYMBOL()'d.

 * It's just a simple helper for sys_ioctl and compat_sys_ioctl.

 */

int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,

	     unsigned long arg)

{

	int error = 0;

	int __user *argp = (int __user *)arg;

	struct inode *inode = file_inode(filp);



	switch (cmd) {

	case FIOCLEX:

		set_close_on_exec(fd, 1);

		break;



	case FIONCLEX:

		set_close_on_exec(fd, 0);

		break;



	case FIONBIO:

		error = ioctl_fionbio(filp, argp);

		break;



	case FIOASYNC:

		error = ioctl_fioasync(fd, filp, argp);

		break;



	case FIOQSIZE:

		if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode) ||

		    S_ISLNK(inode->i_mode)) {

			loff_t res = inode_get_bytes(inode);

			error = copy_to_user(argp, &res, sizeof(res)) ?

					-EFAULT : 0;

		} else

			error = -ENOTTY;

		break;



	case FIFREEZE:

		error = ioctl_fsfreeze(filp);

		break;



	case FITHAW:

		error = ioctl_fsthaw(filp);

		break;



	case FS_IOC_FIEMAP:

		return ioctl_fiemap(filp, arg);



	case FIGETBSZ:

		return put_user(inode->i_sb->s_blocksize, argp);



	default:

		if (S_ISREG(inode->i_mode))

			error = file_ioctl(filp, cmd, arg);

		else

			error = vfs_ioctl(filp, cmd, arg);

		break;

	}

	return error;

}

通过dump信息,我们知道是调用了vfs_ioctl,

继续看vfs_ioctl:

/**

 * vfs_ioctl - call filesystem specific ioctl methods

 * @filp:	open file to invoke ioctl method on

 * @cmd:	ioctl command to execute

 * @arg:	command-specific argument for ioctl

 *

 * Invokes filesystem specific ->unlocked_ioctl, if one exists; otherwise

 * returns -ENOTTY.

 *

 * Returns 0 on success, -errno on error.

 */

static long vfs_ioctl(struct file *filp, unsigned int cmd,

		      unsigned long arg)

{

	int error = -ENOTTY;

    
    printk("zbh %s:%s(%d) file system name:%s \r\n", 
        __FILE__, __func__, __LINE__, filp->f_path.dentry->d_sb->s_type->name);
	if (!filp->f_op->unlocked_ioctl)

		goto out;



	error = filp->f_op->unlocked_ioctl(filp, cmd, arg);

	if (error == -ENOIOCTLCMD)

		error = -ENOTTY;

 out:

	return error;

}

打印的目的是告诉大家一个查看文件系统类型的方法。

这个是属于vfs下的sockfs文件系统

到了这里我们要找到unlocked_ioctl的回调函数是哪个

方法一:

图像 97.jpg

方法二:

因为我们在kernel的dump信息里面知道是调用了sock_ioctl,所以我们直接去找这个函数就好了,net/socket.c

/*

 *	Socket files have a set of 'special' operations as well as the generic file ones. These don't appear

 *	in the operation structures but are done directly via the socketcall() multiplexor.

 */



static const struct file_operations socket_file_ops = {

	.owner =	THIS_MODULE,

	.llseek =	no_llseek,

	.read_iter =	sock_read_iter,

	.write_iter =	sock_write_iter,

	.poll =		sock_poll,

	.unlocked_ioctl = sock_ioctl,

#ifdef CONFIG_COMPAT

	.compat_ioctl = compat_sock_ioctl,

#endif

	.mmap =		sock_mmap,

	.release =	sock_close,

	.fasync =	sock_fasync,

	.sendpage =	sock_sendpage,

	.splice_write = generic_splice_sendpage,

	.splice_read =	sock_splice_read,

};

注册是在如下地方注册的:

使用函数

sock_alloc_file
/*

 *	Obtains the first available file descriptor and sets it up for use.

 *

 *	These functions create file structures and maps them to fd space

 *	of the current process. On success it returns file descriptor

 *	and file struct implicitly stored in sock->file.

 *	Note that another thread may close file descriptor before we return

 *	from this function. We use the fact that now we do not refer

 *	to socket after mapping. If one day we will need it, this

 *	function will increment ref. count on file by 1.

 *

 *	In any case returned fd MAY BE not valid!

 *	This race condition is unavoidable

 *	with shared fd spaces, we cannot solve it inside kernel,

 *	but we take care of internal coherence yet.

 */



struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)

{

	struct qstr name = { .name = "" };

	struct path path;

	struct file *file;



	if (dname) {

		name.name = dname;

		name.len = strlen(name.name);

	} else if (sock->sk) {

		name.name = sock->sk->sk_prot_creator->name;

		name.len = strlen(name.name);

	}

	path.dentry = d_alloc_pseudo(sock_mnt->mnt_sb, &name);

	if (unlikely(!path.dentry))

		return ERR_PTR(-ENOMEM);

	path.mnt = mntget(sock_mnt);



	d_instantiate(path.dentry, SOCK_INODE(sock));



	file = alloc_file(&path, FMODE_READ | FMODE_WRITE,

		  &socket_file_ops);

	if (unlikely(IS_ERR(file))) {

		/* drop dentry, keep inode */

		ihold(d_inode(path.dentry));

		path_put(&path);

		return file;

	}



	sock->file = file;

	file->f_flags = O_RDWR | (flags & O_NONBLOCK);

	file->private_data = sock;

	return file;

}

EXPORT_SYMBOL(sock_alloc_file);

从socket到下的流程是这样

系统调用socket

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

{

	int retval;

	struct socket *sock;

	int flags;



	/* Check the SOCK_* constants for consistency.  */

	BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);

	BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);

	BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);

	BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);



	flags = type & ~SOCK_TYPE_MASK;

	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))

		return -EINVAL;

	type &= SOCK_TYPE_MASK;



	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))

		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;



	retval = sock_create(family, type, protocol, &sock);

	if (retval < 0)

		goto out;



	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));

	if (retval < 0)

		goto out_release;



out:

	/* It may be already another descriptor 8) Not kernel problem. */

	return retval;



out_release:

	sock_release(sock);

	return retval;

}
在sock_map_fd中调用了sock_alloc_file








static int sock_map_fd(struct socket *sock, int flags)

{

	struct file *newfile;

	int fd = get_unused_fd_flags(flags);

	if (unlikely(fd < 0))

		return fd;



	newfile = sock_alloc_file(sock, flags, NULL);

	if (likely(!IS_ERR(newfile))) {

		fd_install(fd, newfile);

		return fd;

	}



	put_unused_fd(fd);

	return PTR_ERR(newfile);

}

看下sock_ioctl代码:

/*

 *	With an ioctl, arg may well be a user mode pointer, but we don't know

 *	what to do with it - that's up to the protocol still.

 */



static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)

{

	struct socket *sock;

	struct sock *sk;

	void __user *argp = (void __user *)arg;

	int pid, err;

	struct net *net;

	

	sock = file->private_data;

	sk = sock->sk;

	net = sock_net(sk);

	if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {

		err = dev_ioctl(net, cmd, argp);

	} else

#ifdef CONFIG_WEXT_CORE

	if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {

		err = dev_ioctl(net, cmd, argp);

	} else

#endif

		switch (cmd) {

		case FIOSETOWN:

		case SIOCSPGRP:

			err = -EFAULT;

			if (get_user(pid, (int __user *)argp))

				break;

			f_setown(sock->file, pid, 1);

			err = 0;

			break;

		case FIOGETOWN:

		case SIOCGPGRP:

			err = put_user(f_getown(sock->file),

				       (int __user *)argp);

			break;

		case SIOCGIFBR:

		case SIOCSIFBR:

		case SIOCBRADDBR:

		case SIOCBRDELBR:

			err = -ENOPKG;

			if (!br_ioctl_hook)

				request_module("bridge");



			mutex_lock(&br_ioctl_mutex);

			if (br_ioctl_hook)

				err = br_ioctl_hook(net, cmd, argp);

			mutex_unlock(&br_ioctl_mutex);

			break;

		case SIOCGIFVLAN:

		case SIOCSIFVLAN:

			err = -ENOPKG;

			if (!vlan_ioctl_hook)

				request_module("8021q");



			mutex_lock(&vlan_ioctl_mutex);

			if (vlan_ioctl_hook)

				err = vlan_ioctl_hook(net, argp);

			mutex_unlock(&vlan_ioctl_mutex);

			break;

		case SIOCADDDLCI:

		case SIOCDELDLCI:

			err = -ENOPKG;

			if (!dlci_ioctl_hook)

				request_module("dlci");



			mutex_lock(&dlci_ioctl_mutex);

			if (dlci_ioctl_hook)

				err = dlci_ioctl_hook(cmd, argp);

			mutex_unlock(&dlci_ioctl_mutex);

			break;

		default:

			err = sock_do_ioctl(net, sock, cmd, arg);

			break;

		}

	return err;

}

最后执行sock_do_ioctl

static long sock_do_ioctl(struct net *net, struct socket *sock,

				 unsigned int cmd, unsigned long arg)

{

	int err;

	void __user *argp = (void __user *)arg;



	err = sock->ops->ioctl(sock, cmd, arg);



	/*

	 * If this ioctl is unknown try to hand it down

	 * to the NIC driver.

	 */

	if (err == -ENOIOCTLCMD)

		err = dev_ioctl(net, cmd, argp);



	return err;

}

我们可以得出err是-19,

这里的sock->ops->ioctl回调的是inet_ioctl, 路径:net/ipv4/af_inet.c

/*

 *	ioctl() calls you can issue on an INET socket. Most of these are

 *	device configuration and stuff and very rarely used. Some ioctls

 *	pass on to the socket itself.

 *

 *	NOTE: I like the idea of a module for the config stuff. ie ifconfig

 *	loads the devconfigure module does its configuring and unloads it.

 *	There's a good 20K of config code hanging around the kernel.

 */



int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)

{

	struct sock *sk = sock->sk;

	int err = 0;

	struct net *net = sock_net(sk);



	switch (cmd) {

	case SIOCGSTAMP:

		err = sock_get_timestamp(sk, (struct timeval __user *)arg);

		break;

	case SIOCGSTAMPNS:

		err = sock_get_timestampns(sk, (struct timespec __user *)arg);

		break;

	case SIOCADDRT:

	case SIOCDELRT:

	case SIOCRTMSG:

		err = ip_rt_ioctl(net, cmd, (void __user *)arg);

		break;

	case SIOCDARP:

	case SIOCGARP:

	case SIOCSARP:

		err = arp_ioctl(net, cmd, (void __user *)arg);

		break;

	case SIOCGIFADDR:

	case SIOCSIFADDR:

	case SIOCGIFBRDADDR:

	case SIOCSIFBRDADDR:

	case SIOCGIFNETMASK:

	case SIOCSIFNETMASK:

	case SIOCGIFDSTADDR:

	case SIOCSIFDSTADDR:

	case SIOCSIFPFLAGS:

	case SIOCGIFPFLAGS:

	case SIOCSIFFLAGS:

		err = devinet_ioctl(net, cmd, (void __user *)arg);

		break;

	default:

		if (sk->sk_prot->ioctl)

			err = sk->sk_prot->ioctl(sk, cmd, arg);

		else

			err = -ENOIOCTLCMD;

		break;

	}

	return err;

}

EXPORT_SYMBOL(inet_ioctl);

我们看到这个代码,和ifconfig出问题的那个宏SIOCSIFFLAGS一样

case SIOCSIFFLAGS:
err = devinet_ioctl(net, cmd, (void __user *)arg);
break;

调到了devinet_ioctl,路径:net/ipv4/devinet.c

这个函数太长,我就黏贴部分代码

int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)

{

	struct ifreq ifr;

	struct sockaddr_in sin_orig;

	struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;

	struct in_device *in_dev;

	struct in_ifaddr **ifap = NULL;

	struct in_ifaddr *ifa = NULL;

	struct net_device *dev;

...



	case SIOCSIFFLAGS:

		ret = -EPERM;

		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))

			goto out;

		break;



...



	case SIOCSIFFLAGS:

		if (colon) {

			ret = -EADDRNOTAVAIL;

			if (!ifa)

				break;

			ret = 0;

			if (!(ifr.ifr_flags & IFF_UP))

				inet_del_ifa(in_dev, ifap, 1);

			break;

		}

		ret = dev_change_flags(dev, ifr.ifr_flags);

		break;

继续跟踪dev_change_flags,路径:net/core/dev.c

/**

 *	dev_change_flags - change device settings

 *	@dev: device

 *	@flags: device state flags

 *

 *	Change settings on device based state flags. The flags are

 *	in the userspace exported format.

 */

int dev_change_flags(struct net_device *dev, unsigned int flags)

{

	int ret;

	unsigned int changes, old_flags = dev->flags, old_gflags = dev->gflags;



	ret = __dev_change_flags(dev, flags);

	if (ret < 0)

		return ret;



	changes = (old_flags ^ dev->flags) | (old_gflags ^ dev->gflags);

	__dev_notify_flags(dev, old_flags, changes);

	return ret;

}

EXPORT_SYMBOL(dev_change_flags);
int __dev_change_flags(struct net_device *dev, unsigned int flags)

{

	unsigned int old_flags = dev->flags;

	int ret;



	ASSERT_RTNL();



	/*

	 *	Set the flags on our device.

	 */



	dev->flags = (flags & (IFF_DEBUG | IFF_NOTRAILERS | IFF_NOARP |

			       IFF_DYNAMIC | IFF_MULTICAST | IFF_PORTSEL |

			       IFF_AUTOMEDIA)) |

		     (dev->flags & (IFF_UP | IFF_VOLATILE | IFF_PROMISC |

				    IFF_ALLMULTI));



	/*

	 *	Load in the correct multicast list now the flags have changed.

	 */



	if ((old_flags ^ flags) & IFF_MULTICAST)

		dev_change_rx_flags(dev, IFF_MULTICAST);



	dev_set_rx_mode(dev);



	/*

	 *	Have we downed the interface. We handle IFF_UP ourselves

	 *	according to user attempts to set it, rather than blindly

	 *	setting it.

	 */



	ret = 0;

	if ((old_flags ^ flags) & IFF_UP)

		ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);



	if ((flags ^ dev->gflags) & IFF_PROMISC) {

		int inc = (flags & IFF_PROMISC) ? 1 : -1;

		unsigned int old_flags = dev->flags;



		dev->gflags ^= IFF_PROMISC;



		if (__dev_set_promiscuity(dev, inc, false) >= 0)

			if (dev->flags != old_flags)

				dev_set_rx_mode(dev);

	}



	/* NOTE: order of synchronization of IFF_PROMISC and IFF_ALLMULTI

	   is important. Some (broken) drivers set IFF_PROMISC, when

	   IFF_ALLMULTI is requested not asking us and not reporting.

	 */

	if ((flags ^ dev->gflags) & IFF_ALLMULTI) {

		int inc = (flags & IFF_ALLMULTI) ? 1 : -1;



		dev->gflags ^= IFF_ALLMULTI;

		__dev_set_allmulti(dev, inc, false);

	}



	return ret;

}

我们看这里

	/*

	 *	Have we downed the interface. We handle IFF_UP ourselves

	 *	according to user attempts to set it, rather than blindly

	 *	setting it.

	 */



	ret = 0;

	if ((old_flags ^ flags) & IFF_UP)

		ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);

到这里大家有印象了吧?__dev_open最终回调的是控制器驱动fec_main.c中的那个

fec_enet_open,大家还记得我们要分析什么吧?那个-19的错误就是这个open里面返回的

我们继续看这个最底层实现。

2. 网址分享

http://stackoverflow.com/questions/5308090/set-ip-address-using-siocsifaddr-ioctl

http://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.commtrf2/ioctl_socket_control_operations.htm

https://lkml.org/lkml/2017/2/3/396

linux PHY驱动

http://www.latelee.org/programming-under-linux/linux-phy-driver.html

Linux PHY几个状态的跟踪

http://www.latelee.org/programming-under-linux/linux-phy-state.html

第十六章PHY -基于Linux3.10

https://blog.csdn.net/shichaog/article/details/44682931

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(2 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • kangkang

    这分析的也太浅了。。根本没有发文章的必要,流水账可以记录到自己的博客里面
    • btrace

      @kangkang:你来个深入的,否则就shutup