--- /dev/null
+From af73e4d9506d3b797509f3c030e7dcd554f7d9c4 Mon Sep 17 00:00:00 2001
+From: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
+Date: Tue, 7 May 2013 16:18:13 -0700
+Subject: hugetlbfs: fix mmap failure in unaligned size request
+
+From: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
+
+commit af73e4d9506d3b797509f3c030e7dcd554f7d9c4 upstream.
+
+The current kernel returns -EINVAL unless a given mmap length is
+"almost" hugepage aligned. This is because in sys_mmap_pgoff() the
+given length is passed to vm_mmap_pgoff() as it is without being aligned
+with hugepage boundary.
+
+This is a regression introduced in commit 40716e29243d ("hugetlbfs: fix
+alignment of huge page requests"), where alignment code is pushed into
+hugetlb_file_setup() and the variable len in caller side is not changed.
+
+To fix this, this patch partially reverts that commit, and adds
+alignment code in caller side. And it also introduces hstate_sizelog()
+in order to get proper hstate to specified hugepage size.
+
+Addresses https://bugzilla.kernel.org/show_bug.cgi?id=56881
+
+[akpm@linux-foundation.org: fix warning when CONFIG_HUGETLB_PAGE=n]
+Signed-off-by: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
+Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
+Reported-by: <iceman_dvd@yahoo.com>
+Cc: Steven Truelove <steven.truelove@utoronto.ca>
+Cc: Jianguo Wu <wujianguo@huawei.com>
+Cc: Hugh Dickins <hughd@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ fs/hugetlbfs/inode.c | 24 ++++++++++--------------
+ include/linux/hugetlb.h | 19 +++++++++++++------
+ ipc/shm.c | 6 +++++-
+ mm/mmap.c | 7 ++++++-
+ 4 files changed, 34 insertions(+), 22 deletions(-)
+
+--- a/fs/hugetlbfs/inode.c
++++ b/fs/hugetlbfs/inode.c
+@@ -908,19 +908,19 @@ static int can_do_hugetlb_shm(void)
+
+ static int get_hstate_idx(int page_size_log)
+ {
+- struct hstate *h;
++ struct hstate *h = hstate_sizelog(page_size_log);
+
+- if (!page_size_log)
+- return default_hstate_idx;
+- h = size_to_hstate(1 << page_size_log);
+ if (!h)
+ return -1;
+ return h - hstates;
+ }
+
+-struct file *hugetlb_file_setup(const char *name, unsigned long addr,
+- size_t size, vm_flags_t acctflag,
+- struct user_struct **user,
++/*
++ * Note that size should be aligned to proper hugepage size in caller side,
++ * otherwise hugetlb_reserve_pages reserves one less hugepages than intended.
++ */
++struct file *hugetlb_file_setup(const char *name, size_t size,
++ vm_flags_t acctflag, struct user_struct **user,
+ int creat_flags, int page_size_log)
+ {
+ int error = -ENOMEM;
+@@ -929,8 +929,6 @@ struct file *hugetlb_file_setup(const ch
+ struct path path;
+ struct dentry *root;
+ struct qstr quick_string;
+- struct hstate *hstate;
+- unsigned long num_pages;
+ int hstate_idx;
+
+ hstate_idx = get_hstate_idx(page_size_log);
+@@ -969,12 +967,10 @@ struct file *hugetlb_file_setup(const ch
+ if (!inode)
+ goto out_dentry;
+
+- hstate = hstate_inode(inode);
+- size += addr & ~huge_page_mask(hstate);
+- num_pages = ALIGN(size, huge_page_size(hstate)) >>
+- huge_page_shift(hstate);
+ error = -ENOMEM;
+- if (hugetlb_reserve_pages(inode, 0, num_pages, NULL, acctflag))
++ if (hugetlb_reserve_pages(inode, 0,
++ size >> huge_page_shift(hstate_inode(inode)), NULL,
++ acctflag))
+ goto out_inode;
+
+ d_instantiate(path.dentry, inode);
+--- a/include/linux/hugetlb.h
++++ b/include/linux/hugetlb.h
+@@ -185,8 +185,7 @@ static inline struct hugetlbfs_sb_info *
+
+ extern const struct file_operations hugetlbfs_file_operations;
+ extern const struct vm_operations_struct hugetlb_vm_ops;
+-struct file *hugetlb_file_setup(const char *name, unsigned long addr,
+- size_t size, vm_flags_t acct,
++struct file *hugetlb_file_setup(const char *name, size_t size, vm_flags_t acct,
+ struct user_struct **user, int creat_flags,
+ int page_size_log);
+
+@@ -205,8 +204,8 @@ static inline int is_file_hugepages(stru
+
+ #define is_file_hugepages(file) 0
+ static inline struct file *
+-hugetlb_file_setup(const char *name, unsigned long addr, size_t size,
+- vm_flags_t acctflag, struct user_struct **user, int creat_flags,
++hugetlb_file_setup(const char *name, size_t size, vm_flags_t acctflag,
++ struct user_struct **user, int creat_flags,
+ int page_size_log)
+ {
+ return ERR_PTR(-ENOSYS);
+@@ -284,6 +283,13 @@ static inline struct hstate *hstate_file
+ return hstate_inode(f->f_dentry->d_inode);
+ }
+
++static inline struct hstate *hstate_sizelog(int page_size_log)
++{
++ if (!page_size_log)
++ return &default_hstate;
++ return size_to_hstate(1 << page_size_log);
++}
++
+ static inline struct hstate *hstate_vma(struct vm_area_struct *vma)
+ {
+ return hstate_file(vma->vm_file);
+@@ -348,11 +354,12 @@ static inline int hstate_index(struct hs
+ return h - hstates;
+ }
+
+-#else
++#else /* CONFIG_HUGETLB_PAGE */
+ struct hstate {};
+ #define alloc_huge_page_node(h, nid) NULL
+ #define alloc_bootmem_huge_page(h) NULL
+ #define hstate_file(f) NULL
++#define hstate_sizelog(s) NULL
+ #define hstate_vma(v) NULL
+ #define hstate_inode(i) NULL
+ #define huge_page_size(h) PAGE_SIZE
+@@ -367,6 +374,6 @@ static inline unsigned int pages_per_hug
+ }
+ #define hstate_index_to_shift(index) 0
+ #define hstate_index(h) 0
+-#endif
++#endif /* CONFIG_HUGETLB_PAGE */
+
+ #endif /* _LINUX_HUGETLB_H */
+--- a/ipc/shm.c
++++ b/ipc/shm.c
+@@ -491,10 +491,14 @@ static int newseg(struct ipc_namespace *
+
+ sprintf (name, "SYSV%08x", key);
+ if (shmflg & SHM_HUGETLB) {
++ struct hstate *hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT)
++ & SHM_HUGE_MASK);
++ size_t hugesize = ALIGN(size, huge_page_size(hs));
++
+ /* hugetlb_file_setup applies strict accounting */
+ if (shmflg & SHM_NORESERVE)
+ acctflag = VM_NORESERVE;
+- file = hugetlb_file_setup(name, 0, size, acctflag,
++ file = hugetlb_file_setup(name, hugesize, acctflag,
+ &shp->mlock_user, HUGETLB_SHMFS_INODE,
+ (shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
+ } else {
+--- a/mm/mmap.c
++++ b/mm/mmap.c
+@@ -1296,15 +1296,20 @@ SYSCALL_DEFINE6(mmap_pgoff, unsigned lon
+ file = fget(fd);
+ if (!file)
+ goto out;
++ if (is_file_hugepages(file))
++ len = ALIGN(len, huge_page_size(hstate_file(file)));
+ } else if (flags & MAP_HUGETLB) {
+ struct user_struct *user = NULL;
++
++ len = ALIGN(len, huge_page_size(hstate_sizelog(
++ (flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK)));
+ /*
+ * VM_NORESERVE is used because the reservations will be
+ * taken when vm_ops->mmap() is called
+ * A dummy user value is used because we are not locking
+ * memory so no accounting is necessary
+ */
+- file = hugetlb_file_setup(HUGETLB_ANON_FILE, addr, len,
++ file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
+ VM_NORESERVE,
+ &user, HUGETLB_ANONHUGE_INODE,
+ (flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);