Subject: Re: [PATCH] CONFIG_PACKET_MMAP should depend on MMU

From: David Howells <[email-masked]>
Date: Wed, 18 Apr 2007 16:33:31 +0100

Aubrey Li <[email-masked]> wrote:

> Here, in the attachment I wrote a small test app. Please correct if
> there is anything wrong, and feel free to improve it.

Okay... I have that working... probably.  I don't know what output it's
supposed to produce, but I see this:

	# /packet-mmap/sample_packet_mmap
	00-00-00-01-00-00-00-8a-00-00-00-8a-00-42-00-50-
	38-43-13-a0-00-07-ff-3c-00-00-00-00-00-00-00-00-
	00-11-08-00-00-00-00-01-00-01-00-06-00-d0-b7-de-
	32-7b-00-00-00-00-00-00-00-00-00-00-00-00-00-00-
	00-00-00-90-cc-a2-75-6b-00-d0-b7-de-32-7b-08-00-
	45-00-00-7c-00-00-40-00-40-11-b4-13-c0-a8-02-80-
	c0-a8-02-8d-08-01-03-20-00-68-8e-65-7f-5b-7e-03-
	00-00-00-01-00-00-00-00-00-00-00-00-00-00-00-00-
	00-00-00-00-00-00-00-00-00-00-00-01-00-00-81-a4-
	00-00-00-01-00-00-00-00-00-00-00-00-00-1d-b8-86-
	00-00-10-00-ff-ff-ff-ff-00-00-0e-f0-00-00-09-02-
	01-cb-03-16-46-26-38-0d-00-00-00-00-46-26-38-1e-
	00-00-00-00-46-26-38-1e-00-00-00-00-00-00-00-00-
	00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00- [repeated]

Does that look reasonable?

I've attached the preliminary patch.  Note four things about it:

 (1) I've had to add the get_unmapped_area() op to the proto_ops struct, but
     I've only done it for CONFIG_MMU=n as making it available for CONFIG_MMU=y
     could cause problems.

 (2) There's a race between packet_get_unmapped_area() being called and
     packet_mmap() being called.

 (3) I've added an extra check into packet_set_ring() to make sure the caller
     isn't asking for a combination of buffer size and count that will exceed
     ULONG_MAX.  This protects a multiply done elsewhere.

 (4) The entire data buffer is allocated as one contiguous lump in NOMMU-mode.

David

---
[PATCH] NOMMU: Support mmap() on AF_PACKET sockets

From: David Howells <[email-masked]>

Support mmap() on AF_PACKET sockets in NOMMU-mode kernels.

Signed-Off-By: David Howells <[email-masked]>
---

 include/linux/net.h    |    7 +++
 include/net/sock.h     |    8 +++
 net/core/sock.c        |   10 ++++
 net/packet/af_packet.c |  118 ++++++++++++++++++++++++++++++++++++++++++++++++
 net/socket.c           |   77 +++++++++++++++++++++++++++++++
 5 files changed, 219 insertions(+), 1 deletions(-)

diff --git a/include/linux/net.h b/include/linux/net.h
index 4db21e6..9e77cf6 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -161,6 +161,11 @@ struct proto_ops {
 	int		(*recvmsg)   (struct kiocb *iocb, struct socket *sock,
 				      struct msghdr *m, size_t total_len,
 				      int flags);
+#ifndef CONFIG_MMU
+	unsigned long	(*get_unmapped_area)(struct file *file, struct socket *sock,
+					     unsigned long addr, unsigned long len,
+					     unsigned long pgoff, unsigned long flags);
+#endif
 	int		(*mmap)	     (struct file *file, struct socket *sock,
 				      struct vm_area_struct * vma);
 	ssize_t		(*sendpage)  (struct socket *sock, struct page *page,
@@ -191,6 +196,8 @@ extern int   	     sock_sendmsg(struct socket *sock, struct msghdr *msg,
 extern int	     sock_recvmsg(struct socket *sock, struct msghdr *msg,
 				  size_t size, int flags);
 extern int 	     sock_map_fd(struct socket *sock);
+extern void	     sock_make_mappable(struct socket *sock,
+					unsigned long prot);
 extern struct socket *sockfd_lookup(int fd, int *err);
 #define		     sockfd_put(sock) fput(sock->file)
 extern int	     net_ratelimit(void);
diff --git a/include/net/sock.h b/include/net/sock.h
index 2c7d60c..d91edea 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -841,6 +841,14 @@ extern int                      sock_no_sendmsg(struct kiocb *, struct socket *,
 						struct msghdr *, size_t);
 extern int                      sock_no_recvmsg(struct kiocb *, struct socket *,
 						struct msghdr *, size_t, int);
+#ifndef CONFIG_MMU
+extern unsigned long		sock_no_get_unmapped_area(struct file *,
+							  struct socket *,
+							  unsigned long,
+							  unsigned long,
+							  unsigned long,
+							  unsigned long);
+#endif
 extern int			sock_no_mmap(struct file *file,
 					     struct socket *sock,
 					     struct vm_area_struct *vma);
diff --git a/net/core/sock.c b/net/core/sock.c
index 27c4f62..b288799 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1364,6 +1364,15 @@ int sock_no_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m,
 	return -EOPNOTSUPP;
 }
 
+#ifndef CONFIG_MMU
+unsigned long sock_no_get_unmapped_area(struct file *file, struct socket *sock,
+					unsigned long addr, unsigned long len,
+					unsigned long pgoff, unsigned long flags)
+{
+	return (unsigned long) -ENODEV;
+}
+#endif
+
 int sock_no_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma)
 {
 	/* Mirror missing mmap method error code */
@@ -1943,6 +1952,7 @@ EXPORT_SYMBOL(sock_no_getname);
 EXPORT_SYMBOL(sock_no_getsockopt);
 EXPORT_SYMBOL(sock_no_ioctl);
 EXPORT_SYMBOL(sock_no_listen);
+EXPORT_SYMBOL(sock_no_get_unmapped_area);
 EXPORT_SYMBOL(sock_no_mmap);
 EXPORT_SYMBOL(sock_no_poll);
 EXPORT_SYMBOL(sock_no_recvmsg);
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 28d47e8..12d970a 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -78,6 +78,7 @@
 #include <linux/poll.h>
 #include <linux/module.h>
 #include <linux/init.h>
+#include <asm/mman.h>
 
 #ifdef CONFIG_INET
 #include <net/inet_common.h>
@@ -1023,6 +1024,7 @@ static int packet_create(struct socket *sock, int protocol)
 		sock->ops = &packet_ops_spkt;
 #endif
 	sock_init_data(sock, sk);
+	sock_make_mappable(sock, PROT_READ | PROT_WRITE);
 
 	po = pkt_sk(sk);
 	sk->sk_family = PF_PACKET;
@@ -1629,6 +1631,7 @@ static inline struct page *pg_vec_endpage(char *one_pg_vec, unsigned int order)
 	return virt_to_page(one_pg_vec + (PAGE_SIZE << order) - 1);
 }
 
+#ifdef CONFIG_MMU
 static void free_pg_vec(char **pg_vec, unsigned int order, unsigned int len)
 {
 	int i;
@@ -1671,6 +1674,45 @@ out_free_pgvec:
 	goto out;
 }
 
+#else
+/*
+ * free the buffer pointer block and all the buffers for NOMMU
+ */
+static void free_pg_vec(char **pg_vec, unsigned int order, unsigned int len)
+{
+	kfree(pg_vec[0]);
+	kfree(pg_vec);
+}
+
+/*
+ * allocate the buffer pointer block and all the buffer blocks for the mappable
+ * AF_PACKET buffers for NOMMU mode
+ * - in NOMMU mode the buffers must be contiguous with each other
+ */
+static char **alloc_pg_vec(struct tpacket_req *req, int order)
+{
+	char *buffers;
+	char **ptrblock;
+	int loop;
+
+	buffers = kcalloc(req->tp_block_size, req->tp_block_nr, GFP_KERNEL);
+	if (!buffers)
+		return ERR_PTR(-ENOMEM);
+	ptrblock = kmalloc(sizeof(char *) * req->tp_block_nr, GFP_KERNEL);
+	if (!ptrblock) {
+		kfree(buffers);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	for (loop = 0; loop < req->tp_block_nr; loop++) {
+		ptrblock[loop] = buffers;
+		buffers += req->tp_block_size;
+	}
+
+	return ptrblock;
+}
+#endif
+
 static int packet_set_ring(struct sock *sk, struct tpacket_req *req, int closing)
 {
 	char **pg_vec = NULL;
@@ -1695,6 +1737,8 @@ static int packet_set_ring(struct sock *sk, struct tpacket_req *req, int closing
 			return -EINVAL;
 		if (unlikely(req->tp_frame_size & (TPACKET_ALIGNMENT - 1)))
 			return -EINVAL;
+		if (unlikely(ULONG_MAX / req->tp_block_size < req->tp_block_nr))
+			return -ENOMEM;
 
 		po->frames_per_block = req->tp_block_size/req->tp_frame_size;
 		if (unlikely(po->frames_per_block <= 0))
@@ -1783,6 +1827,7 @@ out:
 	return err;
 }
 
+#ifdef CONFIG_MMU
 static int packet_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma)
 {
 	struct sock *sk = sock->sk;
@@ -1823,7 +1868,72 @@ out:
 	release_sock(sk);
 	return err;
 }
-#endif
+
+#else
+/*
+ * find out where the socket buffers are so that NOMMU mmap can return the
+ * address directly
+ */
+static unsigned long packet_get_unmapped_area(struct file *file,
+					      struct socket *sock,
+					      unsigned long addr,
+					      unsigned long len,
+					      unsigned long pgoff,
+					      unsigned long flags)
+{
+	struct sock *sk = sock->sk;
+	struct packet_sock *po = pkt_sk(sk);
+
+	/* check that they want to map the whole buffer */
+	if (pgoff)
+		return (unsigned long) -EINVAL;
+
+	lock_sock(sk);
+	if (!po->pg_vec ||
+	    len != po->pg_vec_len * po->pg_vec_pages * PAGE_SIZE)
+		addr = (unsigned long) -EINVAL;
+	else
+		addr = (unsigned long) po->pg_vec[0];
+	release_sock(sk);
+	return addr;
+}
+
+/*
+ * set the mapping of a packet socket buffer for NOMMU mmap
+ */
+static int packet_mmap(struct file *file, struct socket *sock,
+		       struct vm_area_struct *vma)
+{
+	struct sock *sk = sock->sk;
+	struct packet_sock *po = pkt_sk(sk);
+	unsigned long len;
+	int ret;
+
+	if (vma->vm_pgoff)
+		return -EINVAL;
+
+	lock_sock(sk);
+
+	/* check that they want to map the whole buffer */
+	len = vma->vm_end - vma->vm_start;
+	if (!po->pg_vec ||
+	    len != po->pg_vec_len * po->pg_vec_pages * PAGE_SIZE) {
+		ret = -EINVAL;
+	} else if (vma->vm_start != (unsigned long) po->pg_vec[0]) {
+		/* someone found the gap between get_unmapped_area and mmap */
+		ret = -ESTALE;
+	} else {
+		atomic_inc(&po->mapped);
+		vma->vm_ops = &packet_mmap_ops;
+		ret = 0;
+	}
+
+	release_sock(sk);
+	return ret;
+}
+
+#endif /* CONFIG_MMU */
+#endif /* CONFIG_PACKET_MMAP */

 #ifdef CONFIG_SOCK_PACKET
@@ -1844,6 +1954,9 @@ static const struct proto_ops packet_ops_spkt = {
 	.getsockopt =	sock_no_getsockopt,
 	.sendmsg =	packet_sendmsg_spkt,
 	.recvmsg =	packet_recvmsg,
+#ifndef CONFIG_MMU
+	.get_unmapped_area = sock_no_get_unmapped_area,
+#endif
 	.mmap =		sock_no_mmap,
 	.sendpage =	sock_no_sendpage,
 };
@@ -1866,6 +1979,9 @@ static const struct proto_ops packet_ops = {
 	.getsockopt =	packet_getsockopt,
 	.sendmsg =	packet_sendmsg,
 	.recvmsg =	packet_recvmsg,
+#ifndef CONFIG_MMU
+	.get_unmapped_area = packet_get_unmapped_area,
+#endif
 	.mmap =		packet_mmap,
 	.sendpage =	sock_no_sendpage,
 };
diff --git a/net/socket.c b/net/socket.c
index ea8f81a..4109db6 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -87,6 +87,7 @@
 
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
+#include <asm/mman.h>
 
 #include <net/compat.h>
 
@@ -98,6 +99,13 @@ static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov,
 			 unsigned long nr_segs, loff_t pos);
 static ssize_t sock_aio_write(struct kiocb *iocb, const struct iovec *iov,
 			  unsigned long nr_segs, loff_t pos);
+#ifndef CONFIG_MMU
+static unsigned long sock_get_unmapped_area(struct file *file,
+					    unsigned long addr,
+					    unsigned long len,
+					    unsigned long pgoff,
+					    unsigned long flags);
+#endif
 static int sock_mmap(struct file *file, struct vm_area_struct *vma);
 
 static int sock_close(struct inode *inode, struct file *file);
@@ -127,6 +135,9 @@ static const struct file_operations socket_file_ops = {
 #ifdef CONFIG_COMPAT
 	.compat_ioctl = compat_sock_ioctl,
 #endif
+#ifndef CONFIG_MMU
+	.get_unmapped_area = sock_get_unmapped_area,
+#endif
 	.mmap =		sock_mmap,
 	.open =		sock_no_open,	/* special open code to disallow open via /proc */
 	.release =	sock_close,
@@ -136,6 +147,26 @@ static const struct file_operations socket_file_ops = {
 };
 
 /*
+ * capabilities for non-mappable sockets
+ * - does not permit mmap at all
+ * - no readahead or I/O queue unplugging required
+ */
+struct backing_dev_info non_mappable_socket_bdi = {
+	.capabilities	= 0,
+};
+
+/*
+ * capabilities for read/write mappable sockets
+ * - permits shared-mmap for read and write only
+ * - does not permit private mmap
+ * - no readahead or I/O queue unplugging required
+ */
+struct backing_dev_info rw_mappable_socket_bdi = {
+	.capabilities	= (BDI_CAP_MAP_DIRECT | BDI_CAP_READ_MAP |
+			   BDI_CAP_WRITE_MAP),
+};
+
+/*
  *	The protocol list. Each protocol is registered in here.
  */
 
@@ -415,6 +446,34 @@ static struct socket *sock_from_file(struct file *file, int *err)
 }
 
 /**
+ *	sock_make_mappable -	Change the mmappability of a socket
+ *	@sock: The socket to alter
+ *	@prot: The mmap protection to permit
+ *
+ *	The nominated socket has its backing device information selection
+ *	changed to indicate to mmap() what sort of mappings are available on
+ *	this socket.
+ */
+void sock_make_mappable(struct socket *sock, unsigned long prot)
+{
+	struct address_space *mapping = &SOCK_INODE(sock)->i_data;
+
+	switch (prot) {
+	case 0:
+		mapping->backing_dev_info = &non_mappable_socket_bdi;
+		break;
+	case PROT_READ | PROT_WRITE:
+		mapping->backing_dev_info = &rw_mappable_socket_bdi;
+		break;
+	case PROT_READ:
+	case PROT_WRITE:
+	default:
+		BUG();
+		break;
+	}
+}
+
+/**
  *	sockfd_lookup	- 	Go from a file number to its socket slot
  *	@fd: file handle
  *	@err: pointer to an error code return
@@ -482,6 +541,7 @@ static struct socket *sock_alloc(void)
 	inode->i_mode = S_IFSOCK | S_IRWXUGO;
 	inode->i_uid = current->fsuid;
 	inode->i_gid = current->fsgid;
+	inode->i_data.backing_dev_info = &non_mappable_socket_bdi;
 
 	get_cpu_var(sockets_in_use)++;
 	put_cpu_var(sockets_in_use);
@@ -918,6 +978,22 @@ static unsigned int sock_poll(struct file *file, poll_table *wait)
 	return sock->ops->poll(file, sock, wait);
 }
 
+#ifndef CONFIG_MMU
+static unsigned long sock_get_unmapped_area(struct file *file,
+					    unsigned long addr,
+					    unsigned long len,
+					    unsigned long pgoff,
+					    unsigned long flags)
+{
+	struct socket *sock = file->private_data;
+
+	if (!sock->ops->get_unmapped_area)
+		return -ENOSYS;
+	return sock->ops->get_unmapped_area(file, sock,
+					    addr, len, pgoff, flags);
+}
+#endif
+
 static int sock_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	struct socket *sock = file->private_data;
@@ -2288,6 +2364,7 @@ EXPORT_SYMBOL(sock_create);
 EXPORT_SYMBOL(sock_create_kern);
 EXPORT_SYMBOL(sock_create_lite);
 EXPORT_SYMBOL(sock_map_fd);
+EXPORT_SYMBOL(sock_make_mappable);
 EXPORT_SYMBOL(sock_recvmsg);
 EXPORT_SYMBOL(sock_register);
 EXPORT_SYMBOL(sock_release);