--- /dev/null
+From ce938231bd3b1d7af3cbd8836f084801090470e1 Mon Sep 17 00:00:00 2001
+From: "Daniel F. Dickinson" <cshored@thecshore.com>
+Date: Sat, 22 Dec 2018 01:09:13 -0500
+Subject: ath9k: Avoid OF no-EEPROM quirks without qca,no-eeprom
+
+From: Daniel F. Dickinson <cshored@thecshore.com>
+
+commit ce938231bd3b1d7af3cbd8836f084801090470e1 upstream.
+
+ath9k_of_init() function[0] was initially written on the assumption that
+if someone had an explicit ath9k OF node that "there must be something
+wrong, why would someone add an OF node if everything is fine"[1]
+(Quoting Martin Blumenstingl <martin.blumenstingl@googlemail.com>)
+
+"it turns out it's not that simple. with your requirements I'm now aware
+of two use-cases where the current code in ath9k_of_init() doesn't work
+without modifications"[1]
+
+The "your requirements" Martin speaks of is the result of the fact that I
+have a device (PowerCloud Systems CR5000) has some kind of default - not
+unique mac address - set and requires to set the correct MAC address via
+mac-address devicetree property, however:
+
+"some cards come with a physical EEPROM chip [or OTP] so "qca,no-eeprom"
+should not be set (your use-case). in this case AH_USE_EEPROM should be
+set (which is the default when there is no OF node)"[1]
+
+The other use case is:
+
+the firmware on some PowerMac G5 seems to add a OF node for the ath9k
+card automatically. depending on the EEPROM on the card AH_NO_EEP_SWAP
+should be unset (which is the default when there is no OF node). see [3]
+
+After this patch to ath9k_of_init() the new behavior will be:
+
+ if there's no OF node then everything is the same as before
+ if there's an empty OF node then ath9k will use the hardware EEPROM
+ (before ath9k would fail to initialize because no EEPROM data was
+ provided by userspace)
+ if there's an OF node with only a MAC address then ath9k will use
+ the MAC address and the hardware EEPROM (see the case above)
+ with "qca,no-eeprom" EEPROM data from userspace will be requested.
+ the behavior here will not change
+[1]
+
+Martin provides additional background on EEPROM swapping[1].
+
+Thanks to Christian Lamparter <chunkeey@gmail.com> for all his help on
+troubleshooting this issue and the basis for this patch.
+
+[0]https://elixir.bootlin.com/linux/v4.20-rc7/source/drivers/net/wireless/ath/ath9k/init.c#L615
+[1]https://github.com/openwrt/openwrt/pull/1645#issuecomment-448027058
+[2]https://github.com/openwrt/openwrt/pull/1613
+[3]https://patchwork.kernel.org/patch/10241731/
+
+Fixes: 138b41253d9c ("ath9k: parse the device configuration from an OF node")
+Reviewed-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+Tested-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+Signed-off-by: Daniel F. Dickinson <cshored@thecshore.com>
+Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
+Cc: Christian Lamparter <chunkeey@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/net/wireless/ath/ath9k/init.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/wireless/ath/ath9k/init.c
++++ b/drivers/net/wireless/ath/ath9k/init.c
+@@ -636,15 +636,15 @@ static int ath9k_of_init(struct ath_soft
+ ret = ath9k_eeprom_request(sc, eeprom_name);
+ if (ret)
+ return ret;
++
++ ah->ah_flags &= ~AH_USE_EEPROM;
++ ah->ah_flags |= AH_NO_EEP_SWAP;
+ }
+
+ mac = of_get_mac_address(np);
+ if (mac)
+ ether_addr_copy(common->macaddr, mac);
+
+- ah->ah_flags &= ~AH_USE_EEPROM;
+- ah->ah_flags |= AH_NO_EEP_SWAP;
+-
+ return 0;
+ }
+
--- /dev/null
+From 605b0487f0bc1ae9963bf52ece0f5c8055186f81 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruenba@redhat.com>
+Date: Wed, 6 Mar 2019 15:41:57 +0100
+Subject: gfs2: Fix missed wakeups in find_insert_glock
+
+From: Andreas Gruenbacher <agruenba@redhat.com>
+
+commit 605b0487f0bc1ae9963bf52ece0f5c8055186f81 upstream.
+
+Mark Syms has reported seeing tasks that are stuck waiting in
+find_insert_glock. It turns out that struct lm_lockname contains four padding
+bytes on 64-bit architectures that function glock_waitqueue doesn't skip when
+hashing the glock name. As a result, we can end up waking up the wrong
+waitqueue, and the waiting tasks may be stuck forever.
+
+Fix that by using ht_parms.key_len instead of sizeof(struct lm_lockname) for
+the key length.
+
+Reported-by: Mark Syms <mark.syms@citrix.com>
+Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
+Signed-off-by: Bob Peterson <rpeterso@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ fs/gfs2/glock.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/fs/gfs2/glock.c
++++ b/fs/gfs2/glock.c
+@@ -107,7 +107,7 @@ static int glock_wake_function(wait_queu
+
+ static wait_queue_head_t *glock_waitqueue(struct lm_lockname *name)
+ {
+- u32 hash = jhash2((u32 *)name, sizeof(*name) / 4, 0);
++ u32 hash = jhash2((u32 *)name, ht_parms.key_len / 4, 0);
+
+ return glock_wait_table + hash_32(hash, GLOCK_WAIT_TABLE_BITS);
+ }
--- /dev/null
+From 663cb6340c6e84fe29aa6d0fa63d85ea6bd6cd19 Mon Sep 17 00:00:00 2001
+From: Jackie Liu <liuyun01@kylinos.cn>
+Date: Thu, 7 Mar 2019 16:30:10 -0800
+Subject: scripts/gdb: replace flags (MS_xyz -> SB_xyz)
+
+From: Jackie Liu <liuyun01@kylinos.cn>
+
+commit 663cb6340c6e84fe29aa6d0fa63d85ea6bd6cd19 upstream.
+
+Since commit 1751e8a6cb93 ("Rename superblock flags (MS_xyz ->
+SB_xyz)"), scripts/gdb should be updated to replace MS_xyz with SB_xyz.
+
+This change didn't directly affect the running operation of scripts/gdb
+until commit e262e32d6bde "vfs: Suppress MS_* flag defs within the
+kernel unless explicitly enabled" removed the definitions used by
+constants.py.
+
+Update constants.py.in to utilise the new internal flags, matching the
+implementation at fs/proc_namespace.c::show_sb_opts.
+
+Note to stable, e262e32d6bde landed in v5.0-rc1 (which was just
+released), so we'll want this picked back to 5.0 stable once this patch
+hits mainline (akpm just picked it up). Without this, debugging a
+kernel a kernel via GDB+QEMU is broken in the 5.0 release.
+
+[kieran.bingham@ideasonboard.com: add fixes tag, reword commit message]
+Link: http://lkml.kernel.org/r/20190305103014.25847-1-kieran.bingham@ideasonboard.com
+Fixes: e262e32d6bde "vfs: Suppress MS_* flag defs within the kernel unless explicitly enabled"
+Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
+Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Tested-by: Nick Desaulniers <ndesaulniers@google.com>
+Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Cc: Felipe Balbi <felipe.balbi@linux.intel.com>
+Cc: Dan Robertson <danlrobertson89@gmail.com>
+Cc: Jan Kiszka <jan.kiszka@siemens.com>
+Cc: David Howells <dhowells@redhat.com>
+Cc: <stable@vger.kernel.org>
+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>
+
+---
+ scripts/gdb/linux/constants.py.in | 12 ++++++------
+ scripts/gdb/linux/proc.py | 12 ++++++------
+ 2 files changed, 12 insertions(+), 12 deletions(-)
+
+--- a/scripts/gdb/linux/constants.py.in
++++ b/scripts/gdb/linux/constants.py.in
+@@ -37,12 +37,12 @@
+ import gdb
+
+ /* linux/fs.h */
+-LX_VALUE(MS_RDONLY)
+-LX_VALUE(MS_SYNCHRONOUS)
+-LX_VALUE(MS_MANDLOCK)
+-LX_VALUE(MS_DIRSYNC)
+-LX_VALUE(MS_NOATIME)
+-LX_VALUE(MS_NODIRATIME)
++LX_VALUE(SB_RDONLY)
++LX_VALUE(SB_SYNCHRONOUS)
++LX_VALUE(SB_MANDLOCK)
++LX_VALUE(SB_DIRSYNC)
++LX_VALUE(SB_NOATIME)
++LX_VALUE(SB_NODIRATIME)
+
+ /* linux/mount.h */
+ LX_VALUE(MNT_NOSUID)
+--- a/scripts/gdb/linux/proc.py
++++ b/scripts/gdb/linux/proc.py
+@@ -114,11 +114,11 @@ def info_opts(lst, opt):
+ return opts
+
+
+-FS_INFO = {constants.LX_MS_SYNCHRONOUS: ",sync",
+- constants.LX_MS_MANDLOCK: ",mand",
+- constants.LX_MS_DIRSYNC: ",dirsync",
+- constants.LX_MS_NOATIME: ",noatime",
+- constants.LX_MS_NODIRATIME: ",nodiratime"}
++FS_INFO = {constants.LX_SB_SYNCHRONOUS: ",sync",
++ constants.LX_SB_MANDLOCK: ",mand",
++ constants.LX_SB_DIRSYNC: ",dirsync",
++ constants.LX_SB_NOATIME: ",noatime",
++ constants.LX_SB_NODIRATIME: ",nodiratime"}
+
+ MNT_INFO = {constants.LX_MNT_NOSUID: ",nosuid",
+ constants.LX_MNT_NODEV: ",nodev",
+@@ -184,7 +184,7 @@ values of that process namespace"""
+ fstype = superblock['s_type']['name'].string()
+ s_flags = int(superblock['s_flags'])
+ m_flags = int(vfs['mnt']['mnt_flags'])
+- rd = "ro" if (s_flags & constants.LX_MS_RDONLY) else "rw"
++ rd = "ro" if (s_flags & constants.LX_SB_RDONLY) else "rw"
+
+ gdb.write(
+ "{} {} {} {}{}{} 0 0\n"
media-revert-media-rc-some-events-are-dropped-by-userspace.patch
revert-pci-pme-implement-runtime-pm-callbacks.patch
bpf-stop-the-psock-parser-before-canceling-its-work.patch
+gfs2-fix-missed-wakeups-in-find_insert_glock.patch
+staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch
+staging-erofs-compressed_pages-should-not-be-accessed-again-after-freed.patch
+scripts-gdb-replace-flags-ms_xyz-sb_xyz.patch
+ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch
--- /dev/null
+From af692e117cb8cd9d3d844d413095775abc1217f9 Mon Sep 17 00:00:00 2001
+From: Gao Xiang <gaoxiang25@huawei.com>
+Date: Wed, 27 Feb 2019 13:33:30 +0800
+Subject: staging: erofs: compressed_pages should not be accessed again after freed
+
+From: Gao Xiang <gaoxiang25@huawei.com>
+
+commit af692e117cb8cd9d3d844d413095775abc1217f9 upstream.
+
+This patch resolves the following page use-after-free issue,
+z_erofs_vle_unzip:
+ ...
+ for (i = 0; i < nr_pages; ++i) {
+ ...
+ z_erofs_onlinepage_endio(page); (1)
+ }
+
+ for (i = 0; i < clusterpages; ++i) {
+ page = compressed_pages[i];
+
+ if (page->mapping == mngda) (2)
+ continue;
+ /* recycle all individual staging pages */
+ (void)z_erofs_gather_if_stagingpage(page_pool, page); (3)
+ WRITE_ONCE(compressed_pages[i], NULL);
+ }
+ ...
+
+After (1) is executed, page is freed and could be then reused, if
+compressed_pages is scanned after that, it could fall info (2) or
+(3) by mistake and that could finally be in a mess.
+
+This patch aims to solve the above issue only with little changes
+as much as possible in order to make the fix backport easier.
+
+Fixes: 3883a79abd02 ("staging: erofs: introduce VLE decompression support")
+Cc: <stable@vger.kernel.org> # 4.19+
+Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
+Reviewed-by: Chao Yu <yuchao0@huawei.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+
+---
+ drivers/staging/erofs/unzip_vle.c | 38 +++++++++++++++++-----------------
+ drivers/staging/erofs/unzip_vle.h | 3 --
+ drivers/staging/erofs/unzip_vle_lz4.c | 19 +++++++----------
+ 3 files changed, 29 insertions(+), 31 deletions(-)
+
+--- a/drivers/staging/erofs/unzip_vle.c
++++ b/drivers/staging/erofs/unzip_vle.c
+@@ -1017,11 +1017,10 @@ repeat:
+ if (llen > grp->llen)
+ llen = grp->llen;
+
+- err = z_erofs_vle_unzip_fast_percpu(compressed_pages,
+- clusterpages, pages, llen, work->pageofs,
+- z_erofs_onlinepage_endio);
++ err = z_erofs_vle_unzip_fast_percpu(compressed_pages, clusterpages,
++ pages, llen, work->pageofs);
+ if (err != -ENOTSUPP)
+- goto out_percpu;
++ goto out;
+
+ if (sparsemem_pages >= nr_pages)
+ goto skip_allocpage;
+@@ -1042,8 +1041,25 @@ skip_allocpage:
+ erofs_vunmap(vout, nr_pages);
+
+ out:
++ /* must handle all compressed pages before endding pages */
++ for (i = 0; i < clusterpages; ++i) {
++ page = compressed_pages[i];
++
++#ifdef EROFS_FS_HAS_MANAGED_CACHE
++ if (page->mapping == MNGD_MAPPING(sbi))
++ continue;
++#endif
++ /* recycle all individual staging pages */
++ (void)z_erofs_gather_if_stagingpage(page_pool, page);
++
++ WRITE_ONCE(compressed_pages[i], NULL);
++ }
++
+ for (i = 0; i < nr_pages; ++i) {
+ page = pages[i];
++ if (!page)
++ continue;
++
+ DBG_BUGON(!page->mapping);
+
+ /* recycle all individual staging pages */
+@@ -1056,20 +1072,6 @@ out:
+ z_erofs_onlinepage_endio(page);
+ }
+
+-out_percpu:
+- for (i = 0; i < clusterpages; ++i) {
+- page = compressed_pages[i];
+-
+-#ifdef EROFS_FS_HAS_MANAGED_CACHE
+- if (page->mapping == MNGD_MAPPING(sbi))
+- continue;
+-#endif
+- /* recycle all individual staging pages */
+- (void)z_erofs_gather_if_stagingpage(page_pool, page);
+-
+- WRITE_ONCE(compressed_pages[i], NULL);
+- }
+-
+ if (pages == z_pagemap_global)
+ mutex_unlock(&z_pagemap_global_lock);
+ else if (unlikely(pages != pages_onstack))
+--- a/drivers/staging/erofs/unzip_vle.h
++++ b/drivers/staging/erofs/unzip_vle.h
+@@ -218,8 +218,7 @@ extern int z_erofs_vle_plain_copy(struct
+
+ extern int z_erofs_vle_unzip_fast_percpu(struct page **compressed_pages,
+ unsigned clusterpages, struct page **pages,
+- unsigned outlen, unsigned short pageofs,
+- void (*endio)(struct page *));
++ unsigned int outlen, unsigned short pageofs);
+
+ extern int z_erofs_vle_unzip_vmap(struct page **compressed_pages,
+ unsigned clusterpages, void *vaddr, unsigned llen,
+--- a/drivers/staging/erofs/unzip_vle_lz4.c
++++ b/drivers/staging/erofs/unzip_vle_lz4.c
+@@ -125,8 +125,7 @@ int z_erofs_vle_unzip_fast_percpu(struct
+ unsigned int clusterpages,
+ struct page **pages,
+ unsigned int outlen,
+- unsigned short pageofs,
+- void (*endio)(struct page *))
++ unsigned short pageofs)
+ {
+ void *vin, *vout;
+ unsigned int nr_pages, i, j;
+@@ -148,19 +147,16 @@ int z_erofs_vle_unzip_fast_percpu(struct
+ ret = z_erofs_unzip_lz4(vin, vout + pageofs,
+ clusterpages * PAGE_SIZE, outlen);
+
+- if (ret >= 0) {
+- outlen = ret;
+- ret = 0;
+- }
++ if (ret < 0)
++ goto out;
++ ret = 0;
+
+ for (i = 0; i < nr_pages; ++i) {
+ j = min((unsigned int)PAGE_SIZE - pageofs, outlen);
+
+ if (pages[i]) {
+- if (ret < 0) {
+- SetPageError(pages[i]);
+- } else if (clusterpages == 1 &&
+- pages[i] == compressed_pages[0]) {
++ if (clusterpages == 1 &&
++ pages[i] == compressed_pages[0]) {
+ memcpy(vin + pageofs, vout + pageofs, j);
+ } else {
+ void *dst = kmap_atomic(pages[i]);
+@@ -168,12 +164,13 @@ int z_erofs_vle_unzip_fast_percpu(struct
+ memcpy(dst + pageofs, vout + pageofs, j);
+ kunmap_atomic(dst);
+ }
+- endio(pages[i]);
+ }
+ vout += PAGE_SIZE;
+ outlen -= j;
+ pageofs = 0;
+ }
++
++out:
+ preempt_enable();
+
+ if (clusterpages == 1)
--- /dev/null
+From 419d6efc50e94bcf5d6b35cd8c71f79edadec564 Mon Sep 17 00:00:00 2001
+From: Gao Xiang <gaoxiang25@huawei.com>
+Date: Fri, 1 Feb 2019 20:16:31 +0800
+Subject: staging: erofs: keep corrupted fs from crashing kernel in erofs_namei()
+
+From: Gao Xiang <gaoxiang25@huawei.com>
+
+commit 419d6efc50e94bcf5d6b35cd8c71f79edadec564 upstream.
+
+As Al pointed out, "
+... and while we are at it, what happens to
+ unsigned int nameoff = le16_to_cpu(de[mid].nameoff);
+ unsigned int matched = min(startprfx, endprfx);
+
+ struct qstr dname = QSTR_INIT(data + nameoff,
+ unlikely(mid >= ndirents - 1) ?
+ maxsize - nameoff :
+ le16_to_cpu(de[mid + 1].nameoff) - nameoff);
+
+ /* string comparison without already matched prefix */
+ int ret = dirnamecmp(name, &dname, &matched);
+if le16_to_cpu(de[...].nameoff) is not monotonically increasing? I.e.
+what's to prevent e.g. (unsigned)-1 ending up in dname.len?
+
+Corrupted fs image shouldn't oops the kernel.. "
+
+Revisit the related lookup flow to address the issue.
+
+Fixes: d72d1ce60174 ("staging: erofs: add namei functions")
+Cc: <stable@vger.kernel.org> # 4.19+
+Suggested-by: Al Viro <viro@ZenIV.linux.org.uk>
+Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+
+---
+ drivers/staging/erofs/namei.c | 183 ++++++++++++++++++++++--------------------
+ 1 file changed, 97 insertions(+), 86 deletions(-)
+
+--- a/drivers/staging/erofs/namei.c
++++ b/drivers/staging/erofs/namei.c
+@@ -15,74 +15,77 @@
+
+ #include <trace/events/erofs.h>
+
+-/* based on the value of qn->len is accurate */
+-static inline int dirnamecmp(struct qstr *qn,
+- struct qstr *qd, unsigned int *matched)
++struct erofs_qstr {
++ const unsigned char *name;
++ const unsigned char *end;
++};
++
++/* based on the end of qn is accurate and it must have the trailing '\0' */
++static inline int dirnamecmp(const struct erofs_qstr *qn,
++ const struct erofs_qstr *qd,
++ unsigned int *matched)
+ {
+- unsigned int i = *matched, len = min(qn->len, qd->len);
+-loop:
+- if (unlikely(i >= len)) {
+- *matched = i;
+- if (qn->len < qd->len) {
+- /*
+- * actually (qn->len == qd->len)
+- * when qd->name[i] == '\0'
+- */
+- return qd->name[i] == '\0' ? 0 : -1;
+- }
+- return (qn->len > qd->len);
+- }
++ unsigned int i = *matched;
+
+- if (qn->name[i] != qd->name[i]) {
+- *matched = i;
+- return qn->name[i] > qd->name[i] ? 1 : -1;
++ /*
++ * on-disk error, let's only BUG_ON in the debugging mode.
++ * otherwise, it will return 1 to just skip the invalid name
++ * and go on (in consideration of the lookup performance).
++ */
++ DBG_BUGON(qd->name > qd->end);
++
++ /* qd could not have trailing '\0' */
++ /* However it is absolutely safe if < qd->end */
++ while (qd->name + i < qd->end && qd->name[i] != '\0') {
++ if (qn->name[i] != qd->name[i]) {
++ *matched = i;
++ return qn->name[i] > qd->name[i] ? 1 : -1;
++ }
++ ++i;
+ }
+-
+- ++i;
+- goto loop;
++ *matched = i;
++ /* See comments in __d_alloc on the terminating NUL character */
++ return qn->name[i] == '\0' ? 0 : 1;
+ }
+
+-static struct erofs_dirent *find_target_dirent(
+- struct qstr *name,
+- u8 *data, int maxsize)
++#define nameoff_from_disk(off, sz) (le16_to_cpu(off) & ((sz) - 1))
++
++static struct erofs_dirent *find_target_dirent(struct erofs_qstr *name,
++ u8 *data,
++ unsigned int dirblksize,
++ const int ndirents)
+ {
+- unsigned int ndirents, head, back;
++ int head, back;
+ unsigned int startprfx, endprfx;
+ struct erofs_dirent *const de = (struct erofs_dirent *)data;
+
+- /* make sure that maxsize is valid */
+- BUG_ON(maxsize < sizeof(struct erofs_dirent));
+-
+- ndirents = le16_to_cpu(de->nameoff) / sizeof(*de);
+-
+- /* corrupted dir (may be unnecessary...) */
+- BUG_ON(!ndirents);
+-
+- head = 0;
++ /* since the 1st dirent has been evaluated previously */
++ head = 1;
+ back = ndirents - 1;
+ startprfx = endprfx = 0;
+
+ while (head <= back) {
+- unsigned int mid = head + (back - head) / 2;
+- unsigned int nameoff = le16_to_cpu(de[mid].nameoff);
++ const int mid = head + (back - head) / 2;
++ const int nameoff = nameoff_from_disk(de[mid].nameoff,
++ dirblksize);
+ unsigned int matched = min(startprfx, endprfx);
+-
+- struct qstr dname = QSTR_INIT(data + nameoff,
+- unlikely(mid >= ndirents - 1) ?
+- maxsize - nameoff :
+- le16_to_cpu(de[mid + 1].nameoff) - nameoff);
++ struct erofs_qstr dname = {
++ .name = data + nameoff,
++ .end = unlikely(mid >= ndirents - 1) ?
++ data + dirblksize :
++ data + nameoff_from_disk(de[mid + 1].nameoff,
++ dirblksize)
++ };
+
+ /* string comparison without already matched prefix */
+ int ret = dirnamecmp(name, &dname, &matched);
+
+- if (unlikely(!ret))
++ if (unlikely(!ret)) {
+ return de + mid;
+- else if (ret > 0) {
++ } else if (ret > 0) {
+ head = mid + 1;
+ startprfx = matched;
+- } else if (unlikely(mid < 1)) /* fix "mid" overflow */
+- break;
+- else {
++ } else {
+ back = mid - 1;
+ endprfx = matched;
+ }
+@@ -91,12 +94,12 @@ static struct erofs_dirent *find_target_
+ return ERR_PTR(-ENOENT);
+ }
+
+-static struct page *find_target_block_classic(
+- struct inode *dir,
+- struct qstr *name, int *_diff)
++static struct page *find_target_block_classic(struct inode *dir,
++ struct erofs_qstr *name,
++ int *_ndirents)
+ {
+ unsigned int startprfx, endprfx;
+- unsigned int head, back;
++ int head, back;
+ struct address_space *const mapping = dir->i_mapping;
+ struct page *candidate = ERR_PTR(-ENOENT);
+
+@@ -105,41 +108,43 @@ static struct page *find_target_block_cl
+ back = inode_datablocks(dir) - 1;
+
+ while (head <= back) {
+- unsigned int mid = head + (back - head) / 2;
++ const int mid = head + (back - head) / 2;
+ struct page *page = read_mapping_page(mapping, mid, NULL);
+
+- if (IS_ERR(page)) {
+-exact_out:
+- if (!IS_ERR(candidate)) /* valid candidate */
+- put_page(candidate);
+- return page;
+- } else {
+- int diff;
+- unsigned int ndirents, matched;
+- struct qstr dname;
++ if (!IS_ERR(page)) {
+ struct erofs_dirent *de = kmap_atomic(page);
+- unsigned int nameoff = le16_to_cpu(de->nameoff);
+-
+- ndirents = nameoff / sizeof(*de);
++ const int nameoff = nameoff_from_disk(de->nameoff,
++ EROFS_BLKSIZ);
++ const int ndirents = nameoff / sizeof(*de);
++ int diff;
++ unsigned int matched;
++ struct erofs_qstr dname;
+
+- /* corrupted dir (should have one entry at least) */
+- BUG_ON(!ndirents || nameoff > PAGE_SIZE);
++ if (unlikely(!ndirents)) {
++ DBG_BUGON(1);
++ kunmap_atomic(de);
++ put_page(page);
++ page = ERR_PTR(-EIO);
++ goto out;
++ }
+
+ matched = min(startprfx, endprfx);
+
+ dname.name = (u8 *)de + nameoff;
+- dname.len = ndirents == 1 ?
+- /* since the rest of the last page is 0 */
+- EROFS_BLKSIZ - nameoff
+- : le16_to_cpu(de[1].nameoff) - nameoff;
++ if (ndirents == 1)
++ dname.end = (u8 *)de + EROFS_BLKSIZ;
++ else
++ dname.end = (u8 *)de +
++ nameoff_from_disk(de[1].nameoff,
++ EROFS_BLKSIZ);
+
+ /* string comparison without already matched prefix */
+ diff = dirnamecmp(name, &dname, &matched);
+ kunmap_atomic(de);
+
+ if (unlikely(!diff)) {
+- *_diff = 0;
+- goto exact_out;
++ *_ndirents = 0;
++ goto out;
+ } else if (diff > 0) {
+ head = mid + 1;
+ startprfx = matched;
+@@ -147,45 +152,51 @@ exact_out:
+ if (likely(!IS_ERR(candidate)))
+ put_page(candidate);
+ candidate = page;
++ *_ndirents = ndirents;
+ } else {
+ put_page(page);
+
+- if (unlikely(mid < 1)) /* fix "mid" overflow */
+- break;
+-
+ back = mid - 1;
+ endprfx = matched;
+ }
++ continue;
+ }
++out: /* free if the candidate is valid */
++ if (!IS_ERR(candidate))
++ put_page(candidate);
++ return page;
+ }
+- *_diff = 1;
+ return candidate;
+ }
+
+ int erofs_namei(struct inode *dir,
+- struct qstr *name,
+- erofs_nid_t *nid, unsigned int *d_type)
++ struct qstr *name,
++ erofs_nid_t *nid, unsigned int *d_type)
+ {
+- int diff;
++ int ndirents;
+ struct page *page;
+- u8 *data;
++ void *data;
+ struct erofs_dirent *de;
++ struct erofs_qstr qn;
+
+ if (unlikely(!dir->i_size))
+ return -ENOENT;
+
+- diff = 1;
+- page = find_target_block_classic(dir, name, &diff);
++ qn.name = name->name;
++ qn.end = name->name + name->len;
++
++ ndirents = 0;
++ page = find_target_block_classic(dir, &qn, &ndirents);
+
+ if (unlikely(IS_ERR(page)))
+ return PTR_ERR(page);
+
+ data = kmap_atomic(page);
+ /* the target page has been mapped */
+- de = likely(diff) ?
+- /* since the rest of the last page is 0 */
+- find_target_dirent(name, data, EROFS_BLKSIZ) :
+- (struct erofs_dirent *)data;
++ if (ndirents)
++ de = find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents);
++ else
++ de = (struct erofs_dirent *)data;
+
+ if (likely(!IS_ERR(de))) {
+ *nid = le64_to_cpu(de->nid);