Revision 35bf49e054214a0c7d3b0a0f8606f753f3f6ae96 authored by Lu Baolu on 23 August 2022, 06:15:57 UTC, committed by Joerg Roedel on 07 September 2022, 13:14:57 UTC
With CONFIG_INTEL_IOMMU_DEBUGFS enabled, below lockdep splat are seen
when an I/O fault occurs on a machine with an Intel IOMMU in it.

 DMAR: DRHD: handling fault status reg 3
 DMAR: [DMA Write NO_PASID] Request device [00:1a.0] fault addr 0x0
       [fault reason 0x05] PTE Write access is not set
 DMAR: Dump dmar0 table entries for IOVA 0x0
 DMAR: root entry: 0x0000000127f42001
 DMAR: context entry: hi 0x0000000000001502, low 0x000000012d8ab001
 ================================
 WARNING: inconsistent lock state
 5.20.0-0.rc0.20220812git7ebfc85e2cd7.10.fc38.x86_64 #1 Not tainted
 --------------------------------
 inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage.
 rngd/1006 [HC1[1]:SC0[0]:HE0:SE1] takes:
 ff177021416f2d78 (&k->k_lock){?.+.}-{2:2}, at: klist_next+0x1b/0x160
 {HARDIRQ-ON-W} state was registered at:
   lock_acquire+0xce/0x2d0
   _raw_spin_lock+0x33/0x80
   klist_add_tail+0x46/0x80
   bus_add_device+0xee/0x150
   device_add+0x39d/0x9a0
   add_memory_block+0x108/0x1d0
   memory_dev_init+0xe1/0x117
   driver_init+0x43/0x4d
   kernel_init_freeable+0x1c2/0x2cc
   kernel_init+0x16/0x140
   ret_from_fork+0x1f/0x30
 irq event stamp: 7812
 hardirqs last  enabled at (7811): [<ffffffff85000e86>] asm_sysvec_apic_timer_interrupt+0x16/0x20
 hardirqs last disabled at (7812): [<ffffffff84f16894>] irqentry_enter+0x54/0x60
 softirqs last  enabled at (7794): [<ffffffff840ff669>] __irq_exit_rcu+0xf9/0x170
 softirqs last disabled at (7787): [<ffffffff840ff669>] __irq_exit_rcu+0xf9/0x170

The klist iterator functions using spin_*lock_irq*() but the klist
insertion functions using spin_*lock(), combined with the Intel DMAR
IOMMU driver iterating over klists from atomic (hardirq) context, where
pci_get_domain_bus_and_slot() calls into bus_find_device() which iterates
over klists.

As currently there's no plan to fix the klist to make it safe to use in
atomic context, this fixes the lockdep splat by avoid calling
pci_get_domain_bus_and_slot() in the hardirq context.

Fixes: 8ac0b64b9735 ("iommu/vt-d: Use pci_get_domain_bus_and_slot() in pgtable_walk()")
Reported-by: Lennert Buytenhek <buytenh@wantstofly.org>
Link: https://lore.kernel.org/linux-iommu/Yvo2dfpEh%2FWC+Wrr@wantstofly.org/
Link: https://lore.kernel.org/linux-iommu/YvyBdPwrTuHHbn5X@wantstofly.org/
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Link: https://lore.kernel.org/r/20220819015949.4795-1-baolu.lu@linux.intel.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
1 parent a349ffc
Raw File
dir.c
// SPDX-License-Identifier: GPL-2.0
/*
 *
 * Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
 *
 *  Directory handling functions for NTFS-based filesystems.
 *
 */

#include <linux/fs.h>
#include <linux/nls.h>

#include "debug.h"
#include "ntfs.h"
#include "ntfs_fs.h"

/* Convert little endian UTF-16 to NLS string. */
int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len,
		      u8 *buf, int buf_len)
{
	int ret, warn;
	u8 *op;
	struct nls_table *nls = sbi->options->nls;

	static_assert(sizeof(wchar_t) == sizeof(__le16));

	if (!nls) {
		/* UTF-16 -> UTF-8 */
		ret = utf16s_to_utf8s(name, len, UTF16_LITTLE_ENDIAN, buf,
				      buf_len);
		buf[ret] = '\0';
		return ret;
	}

	op = buf;
	warn = 0;

	while (len--) {
		u16 ec;
		int charlen;
		char dump[5];

		if (buf_len < NLS_MAX_CHARSET_SIZE) {
			ntfs_warn(sbi->sb,
				  "filename was truncated while converting.");
			break;
		}

		ec = le16_to_cpu(*name++);
		charlen = nls->uni2char(ec, op, buf_len);

		if (charlen > 0) {
			op += charlen;
			buf_len -= charlen;
			continue;
		}

		*op++ = '_';
		buf_len -= 1;
		if (warn)
			continue;

		warn = 1;
		hex_byte_pack(&dump[0], ec >> 8);
		hex_byte_pack(&dump[2], ec);
		dump[4] = 0;

		ntfs_err(sbi->sb, "failed to convert \"%s\" to %s", dump,
			 nls->charset);
	}

	*op = '\0';
	return op - buf;
}

// clang-format off
#define PLANE_SIZE	0x00010000

#define SURROGATE_PAIR	0x0000d800
#define SURROGATE_LOW	0x00000400
#define SURROGATE_BITS	0x000003ff
// clang-format on

/*
 * put_utf16 - Modified version of put_utf16 from fs/nls/nls_base.c
 *
 * Function is sparse warnings free.
 */
static inline void put_utf16(wchar_t *s, unsigned int c,
			     enum utf16_endian endian)
{
	static_assert(sizeof(wchar_t) == sizeof(__le16));
	static_assert(sizeof(wchar_t) == sizeof(__be16));

	switch (endian) {
	default:
		*s = (wchar_t)c;
		break;
	case UTF16_LITTLE_ENDIAN:
		*(__le16 *)s = __cpu_to_le16(c);
		break;
	case UTF16_BIG_ENDIAN:
		*(__be16 *)s = __cpu_to_be16(c);
		break;
	}
}

/*
 * _utf8s_to_utf16s
 *
 * Modified version of 'utf8s_to_utf16s' allows to
 * detect -ENAMETOOLONG without writing out of expected maximum.
 */
static int _utf8s_to_utf16s(const u8 *s, int inlen, enum utf16_endian endian,
			    wchar_t *pwcs, int maxout)
{
	u16 *op;
	int size;
	unicode_t u;

	op = pwcs;
	while (inlen > 0 && *s) {
		if (*s & 0x80) {
			size = utf8_to_utf32(s, inlen, &u);
			if (size < 0)
				return -EINVAL;
			s += size;
			inlen -= size;

			if (u >= PLANE_SIZE) {
				if (maxout < 2)
					return -ENAMETOOLONG;

				u -= PLANE_SIZE;
				put_utf16(op++,
					  SURROGATE_PAIR |
						  ((u >> 10) & SURROGATE_BITS),
					  endian);
				put_utf16(op++,
					  SURROGATE_PAIR | SURROGATE_LOW |
						  (u & SURROGATE_BITS),
					  endian);
				maxout -= 2;
			} else {
				if (maxout < 1)
					return -ENAMETOOLONG;

				put_utf16(op++, u, endian);
				maxout--;
			}
		} else {
			if (maxout < 1)
				return -ENAMETOOLONG;

			put_utf16(op++, *s++, endian);
			inlen--;
			maxout--;
		}
	}
	return op - pwcs;
}

/*
 * ntfs_nls_to_utf16 - Convert input string to UTF-16.
 * @name:	Input name.
 * @name_len:	Input name length.
 * @uni:	Destination memory.
 * @max_ulen:	Destination memory.
 * @endian:	Endian of target UTF-16 string.
 *
 * This function is called:
 * - to create NTFS name
 * - to create symlink
 *
 * Return: UTF-16 string length or error (if negative).
 */
int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
		      struct cpu_str *uni, u32 max_ulen,
		      enum utf16_endian endian)
{
	int ret, slen;
	const u8 *end;
	struct nls_table *nls = sbi->options->nls;
	u16 *uname = uni->name;

	static_assert(sizeof(wchar_t) == sizeof(u16));

	if (!nls) {
		/* utf8 -> utf16 */
		ret = _utf8s_to_utf16s(name, name_len, endian, uname, max_ulen);
		uni->len = ret;
		return ret;
	}

	for (ret = 0, end = name + name_len; name < end; ret++, name += slen) {
		if (ret >= max_ulen)
			return -ENAMETOOLONG;

		slen = nls->char2uni(name, end - name, uname + ret);
		if (!slen)
			return -EINVAL;
		if (slen < 0)
			return slen;
	}

#ifdef __BIG_ENDIAN
	if (endian == UTF16_LITTLE_ENDIAN) {
		int i = ret;

		while (i--) {
			__cpu_to_le16s(uname);
			uname++;
		}
	}
#else
	if (endian == UTF16_BIG_ENDIAN) {
		int i = ret;

		while (i--) {
			__cpu_to_be16s(uname);
			uname++;
		}
	}
#endif

	uni->len = ret;
	return ret;
}

/*
 * dir_search_u - Helper function.
 */
struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
			   struct ntfs_fnd *fnd)
{
	int err = 0;
	struct super_block *sb = dir->i_sb;
	struct ntfs_sb_info *sbi = sb->s_fs_info;
	struct ntfs_inode *ni = ntfs_i(dir);
	struct NTFS_DE *e;
	int diff;
	struct inode *inode = NULL;
	struct ntfs_fnd *fnd_a = NULL;

	if (!fnd) {
		fnd_a = fnd_get();
		if (!fnd_a) {
			err = -ENOMEM;
			goto out;
		}
		fnd = fnd_a;
	}

	err = indx_find(&ni->dir, ni, NULL, uni, 0, sbi, &diff, &e, fnd);

	if (err)
		goto out;

	if (diff) {
		err = -ENOENT;
		goto out;
	}

	inode = ntfs_iget5(sb, &e->ref, uni);
	if (!IS_ERR(inode) && is_bad_inode(inode)) {
		iput(inode);
		err = -EINVAL;
	}
out:
	fnd_put(fnd_a);

	return err == -ENOENT ? NULL : err ? ERR_PTR(err) : inode;
}

static inline int ntfs_filldir(struct ntfs_sb_info *sbi, struct ntfs_inode *ni,
			       const struct NTFS_DE *e, u8 *name,
			       struct dir_context *ctx)
{
	const struct ATTR_FILE_NAME *fname;
	unsigned long ino;
	int name_len;
	u32 dt_type;

	fname = Add2Ptr(e, sizeof(struct NTFS_DE));

	if (fname->type == FILE_NAME_DOS)
		return 0;

	if (!mi_is_ref(&ni->mi, &fname->home))
		return 0;

	ino = ino_get(&e->ref);

	if (ino == MFT_REC_ROOT)
		return 0;

	/* Skip meta files. Unless option to show metafiles is set. */
	if (!sbi->options->showmeta && ntfs_is_meta_file(sbi, ino))
		return 0;

	if (sbi->options->nohidden && (fname->dup.fa & FILE_ATTRIBUTE_HIDDEN))
		return 0;

	name_len = ntfs_utf16_to_nls(sbi, fname->name, fname->name_len, name,
				     PATH_MAX);
	if (name_len <= 0) {
		ntfs_warn(sbi->sb, "failed to convert name for inode %lx.",
			  ino);
		return 0;
	}

	dt_type = (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG;

	return !dir_emit(ctx, (s8 *)name, name_len, ino, dt_type);
}

/*
 * ntfs_read_hdr - Helper function for ntfs_readdir().
 */
static int ntfs_read_hdr(struct ntfs_sb_info *sbi, struct ntfs_inode *ni,
			 const struct INDEX_HDR *hdr, u64 vbo, u64 pos,
			 u8 *name, struct dir_context *ctx)
{
	int err;
	const struct NTFS_DE *e;
	u32 e_size;
	u32 end = le32_to_cpu(hdr->used);
	u32 off = le32_to_cpu(hdr->de_off);

	for (;; off += e_size) {
		if (off + sizeof(struct NTFS_DE) > end)
			return -1;

		e = Add2Ptr(hdr, off);
		e_size = le16_to_cpu(e->size);
		if (e_size < sizeof(struct NTFS_DE) || off + e_size > end)
			return -1;

		if (de_is_last(e))
			return 0;

		/* Skip already enumerated. */
		if (vbo + off < pos)
			continue;

		if (le16_to_cpu(e->key_size) < SIZEOF_ATTRIBUTE_FILENAME)
			return -1;

		ctx->pos = vbo + off;

		/* Submit the name to the filldir callback. */
		err = ntfs_filldir(sbi, ni, e, name, ctx);
		if (err)
			return err;
	}
}

/*
 * ntfs_readdir - file_operations::iterate_shared
 *
 * Use non sorted enumeration.
 * We have an example of broken volume where sorted enumeration
 * counts each name twice.
 */
static int ntfs_readdir(struct file *file, struct dir_context *ctx)
{
	const struct INDEX_ROOT *root;
	u64 vbo;
	size_t bit;
	loff_t eod;
	int err = 0;
	struct inode *dir = file_inode(file);
	struct ntfs_inode *ni = ntfs_i(dir);
	struct super_block *sb = dir->i_sb;
	struct ntfs_sb_info *sbi = sb->s_fs_info;
	loff_t i_size = i_size_read(dir);
	u32 pos = ctx->pos;
	u8 *name = NULL;
	struct indx_node *node = NULL;
	u8 index_bits = ni->dir.index_bits;

	/* Name is a buffer of PATH_MAX length. */
	static_assert(NTFS_NAME_LEN * 4 < PATH_MAX);

	eod = i_size + sbi->record_size;

	if (pos >= eod)
		return 0;

	if (!dir_emit_dots(file, ctx))
		return 0;

	/* Allocate PATH_MAX bytes. */
	name = __getname();
	if (!name)
		return -ENOMEM;

	if (!ni->mi_loaded && ni->attr_list.size) {
		/*
		 * Directory inode is locked for read.
		 * Load all subrecords to avoid 'write' access to 'ni' during
		 * directory reading.
		 */
		ni_lock(ni);
		if (!ni->mi_loaded && ni->attr_list.size) {
			err = ni_load_all_mi(ni);
			if (!err)
				ni->mi_loaded = true;
		}
		ni_unlock(ni);
		if (err)
			goto out;
	}

	root = indx_get_root(&ni->dir, ni, NULL, NULL);
	if (!root) {
		err = -EINVAL;
		goto out;
	}

	if (pos >= sbi->record_size) {
		bit = (pos - sbi->record_size) >> index_bits;
	} else {
		err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx);
		if (err)
			goto out;
		bit = 0;
	}

	if (!i_size) {
		ctx->pos = eod;
		goto out;
	}

	for (;;) {
		vbo = (u64)bit << index_bits;
		if (vbo >= i_size) {
			ctx->pos = eod;
			goto out;
		}

		err = indx_used_bit(&ni->dir, ni, &bit);
		if (err)
			goto out;

		if (bit == MINUS_ONE_T) {
			ctx->pos = eod;
			goto out;
		}

		vbo = (u64)bit << index_bits;
		if (vbo >= i_size) {
			ntfs_inode_err(dir, "Looks like your dir is corrupt");
			err = -EINVAL;
			goto out;
		}

		err = indx_read(&ni->dir, ni, bit << ni->dir.idx2vbn_bits,
				&node);
		if (err)
			goto out;

		err = ntfs_read_hdr(sbi, ni, &node->index->ihdr,
				    vbo + sbi->record_size, pos, name, ctx);
		if (err)
			goto out;

		bit += 1;
	}

out:

	__putname(name);
	put_indx_node(node);

	if (err == -ENOENT) {
		err = 0;
		ctx->pos = pos;
	}

	return err;
}

static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs,
			  size_t *files)
{
	int err = 0;
	struct ntfs_inode *ni = ntfs_i(dir);
	struct NTFS_DE *e = NULL;
	struct INDEX_ROOT *root;
	struct INDEX_HDR *hdr;
	const struct ATTR_FILE_NAME *fname;
	u32 e_size, off, end;
	u64 vbo = 0;
	size_t drs = 0, fles = 0, bit = 0;
	loff_t i_size = ni->vfs_inode.i_size;
	struct indx_node *node = NULL;
	u8 index_bits = ni->dir.index_bits;

	if (is_empty)
		*is_empty = true;

	root = indx_get_root(&ni->dir, ni, NULL, NULL);
	if (!root)
		return -EINVAL;

	hdr = &root->ihdr;

	for (;;) {
		end = le32_to_cpu(hdr->used);
		off = le32_to_cpu(hdr->de_off);

		for (; off + sizeof(struct NTFS_DE) <= end; off += e_size) {
			e = Add2Ptr(hdr, off);
			e_size = le16_to_cpu(e->size);
			if (e_size < sizeof(struct NTFS_DE) ||
			    off + e_size > end)
				break;

			if (de_is_last(e))
				break;

			fname = de_get_fname(e);
			if (!fname)
				continue;

			if (fname->type == FILE_NAME_DOS)
				continue;

			if (is_empty) {
				*is_empty = false;
				if (!dirs && !files)
					goto out;
			}

			if (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY)
				drs += 1;
			else
				fles += 1;
		}

		if (vbo >= i_size)
			goto out;

		err = indx_used_bit(&ni->dir, ni, &bit);
		if (err)
			goto out;

		if (bit == MINUS_ONE_T)
			goto out;

		vbo = (u64)bit << index_bits;
		if (vbo >= i_size)
			goto out;

		err = indx_read(&ni->dir, ni, bit << ni->dir.idx2vbn_bits,
				&node);
		if (err)
			goto out;

		hdr = &node->index->ihdr;
		bit += 1;
		vbo = (u64)bit << ni->dir.idx2vbn_bits;
	}

out:
	put_indx_node(node);
	if (dirs)
		*dirs = drs;
	if (files)
		*files = fles;

	return err;
}

bool dir_is_empty(struct inode *dir)
{
	bool is_empty = false;

	ntfs_dir_count(dir, &is_empty, NULL, NULL);

	return is_empty;
}

// clang-format off
const struct file_operations ntfs_dir_operations = {
	.llseek		= generic_file_llseek,
	.read		= generic_read_dir,
	.iterate_shared	= ntfs_readdir,
	.fsync		= generic_file_fsync,
	.open		= ntfs_file_open,
};
// clang-format on
back to top