2012-11-08 13:03:38

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 00/26] pstore, mmc: add mmc as backend for pstore

From: Dragos Tatulea <[email protected]>

These patches enable using the mmc card to store panic information.

They include changes for pstore and mmc:
- add block device backend for pstore
- add logic in mmc for writing in panic mode

A mmc host driver must implement mmc_panic_ops. This patchset contains
an implementation for sdhci.

v2:
- Added some detailed description to some commits. Not all of them
though because they are very small and self explanatory.
- Added clarification on what mmc host controller drivers have to
implement to support panic dumping.

Adrian Hunter (26):
pstore: allow for big files
pstore: add flags
pstore: add flush
blkoops: add a block device oops / panic logger
block: add panic write
mmc: block: add panic write support
mmc: panic write: bypass host claiming
mmc: panic write: bypass request completion
mmc: panic write: suppress host not claimed warnings
mmc: panic write: do not msleep
mmc: panic write: bypass clock gating
mmc: panic write: bypass regulators
mmc: panic write: trap non panic tasks
mmc: panic write: bypass bus ref locking
mmc: sdhci: panic write: bypass spin lock
mmc: sdhci: panic write: no sleeping
mmc: sdhci: panic write: call tasklets inline
mmc: sdhci: panic write: no timeout timer
mmc: sdhci: panic write: no runtime pm
mmc: sdhci: panic write: no tuning
mmc: sdhci: panic write: poll interrupts
mmc: sdhci: panic write: no dma mapping
mmc: sdhci: panic write: resume suspended host
mmc: sdhci: panic write: abort request in progress
mmc: sdhci: panic write: trap nonpanic tasks
mmc: sdhci-pci: add panic write support

Documentation/blockdev/00-INDEX | 2 +
Documentation/blockdev/blkoops.txt | 104 +++
drivers/acpi/apei/erst.c | 16 +-
drivers/block/Kconfig | 13 +
drivers/block/Makefile | 1 +
drivers/block/blkoops.c | 1569 ++++++++++++++++++++++++++++++++++++
drivers/mmc/card/Kconfig | 11 +
drivers/mmc/card/block.c | 257 +++++-
drivers/mmc/core/core.c | 61 +-
drivers/mmc/core/core.h | 6 +-
drivers/mmc/core/host.c | 11 +
drivers/mmc/core/mmc.c | 4 +-
drivers/mmc/core/mmc_ops.c | 10 +-
drivers/mmc/core/sd.c | 4 +-
drivers/mmc/core/sd_ops.c | 2 +-
drivers/mmc/core/sdio.c | 4 +-
drivers/mmc/core/sdio_irq.c | 4 +-
drivers/mmc/core/sdio_ops.c | 2 +-
drivers/mmc/host/sdhci-pci.c | 5 +
drivers/mmc/host/sdhci.c | 441 ++++++++--
drivers/mmc/host/sdhci.h | 24 +
fs/pstore/inode.c | 26 +-
fs/pstore/internal.h | 5 +-
fs/pstore/platform.c | 23 +-
fs/pstore/ram.c | 15 +-
include/linux/blkdev.h | 77 ++
include/linux/genhd.h | 3 +
include/linux/mmc/host.h | 92 +++
include/linux/mmc/sdhci.h | 9 +
include/linux/pstore.h | 12 +-
30 files changed, 2690 insertions(+), 123 deletions(-)
create mode 100644 Documentation/blockdev/blkoops.txt
create mode 100644 drivers/block/blkoops.c

--
1.7.9.5


2012-11-08 13:03:47

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 02/26] pstore: add flags

From: Adrian Hunter <[email protected]>

Let the back end tweak pstore behaviour. Flags added are:

PSTORE_NO_HEADINGS

Omit pstore heading lines from dumped data

PSTORE_MAX_KMSG_BYTES

Default kmsg_bytes to ULONG_MAX

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
fs/pstore/platform.c | 9 ++++++++-
include/linux/pstore.h | 4 ++++
2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c
index 108bd69..b9ab942 100644
--- a/fs/pstore/platform.c
+++ b/fs/pstore/platform.c
@@ -128,7 +128,11 @@ static void pstore_dump(struct kmsg_dumper *dumper,
size_t len;

dst = psinfo->buf;
- hsize = sprintf(dst, "%s#%d Part%d\n", why, oopscount, part);
+ if (psinfo->flags & PSTORE_NO_HEADINGS)
+ hsize = 0;
+ else
+ hsize = sprintf(dst, "%s#%d Part%d\n", why, oopscount,
+ part);
size = psinfo->bufsize - hsize;
dst += hsize;

@@ -237,6 +241,9 @@ int pstore_register(struct pstore_info *psi)
return -EINVAL;
}

+ if (psinfo->flags & PSTORE_MAX_KMSG_BYTES)
+ kmsg_bytes = ULONG_MAX;
+
if (pstore_is_mounted())
pstore_get_records(0);

diff --git a/include/linux/pstore.h b/include/linux/pstore.h
index 3a293ff..27f1995 100644
--- a/include/linux/pstore.h
+++ b/include/linux/pstore.h
@@ -40,9 +40,13 @@ enum pstore_type_id {

struct module;

+#define PSTORE_NO_HEADINGS BIT(0)
+#define PSTORE_MAX_KMSG_BYTES BIT(1)
+
struct pstore_info {
struct module *owner;
char *name;
+ unsigned int flags;
spinlock_t buf_lock; /* serialize access to 'buf' */
char *buf;
size_t bufsize;
--
1.7.9.5

2012-11-08 13:03:50

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 03/26] pstore: add flush

From: Adrian Hunter <[email protected]>

Let the back end know when writing has finished by adding a flush method.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
fs/pstore/platform.c | 3 +++
include/linux/pstore.h | 1 +
2 files changed, 4 insertions(+)

diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c
index b9ab942..97ae8a9 100644
--- a/fs/pstore/platform.c
+++ b/fs/pstore/platform.c
@@ -114,6 +114,9 @@ static void pstore_dump(struct kmsg_dumper *dumper,

why = get_reason_str(reason);

+ if (psinfo->flush)
+ psinfo->flush(psinfo);
+
if (in_nmi()) {
is_locked = spin_trylock(&psinfo->buf_lock);
if (!is_locked)
diff --git a/include/linux/pstore.h b/include/linux/pstore.h
index 27f1995..3f93b4a 100644
--- a/include/linux/pstore.h
+++ b/include/linux/pstore.h
@@ -68,6 +68,7 @@ struct pstore_info {
struct pstore_info *psi);
int (*erase)(enum pstore_type_id type, u64 id,
struct pstore_info *psi);
+ int (*flush)(struct pstore_info *psi);
void *data;
};

--
1.7.9.5

2012-11-08 13:03:58

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 04/26] blkoops: add a block device oops / panic logger

From: Adrian Hunter <[email protected]>

blkoops is a pstore back end to write panic / oops logs to a block
device. It is initially intended for use with eMMC as an alternative to
using a crash kernel.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
Documentation/blockdev/00-INDEX | 2 +
Documentation/blockdev/blkoops.txt | 104 +++
drivers/block/Kconfig | 10 +
drivers/block/Makefile | 1 +
drivers/block/blkoops.c | 1569 ++++++++++++++++++++++++++++++++++++
5 files changed, 1686 insertions(+)
create mode 100644 Documentation/blockdev/blkoops.txt
create mode 100644 drivers/block/blkoops.c

diff --git a/Documentation/blockdev/00-INDEX b/Documentation/blockdev/00-INDEX
index c08df56..c45cef8 100644
--- a/Documentation/blockdev/00-INDEX
+++ b/Documentation/blockdev/00-INDEX
@@ -2,6 +2,8 @@
- this file
README.DAC960
- info on Mylex DAC960/DAC1100 PCI RAID Controller Driver for Linux.
+blkoops.txt
+ - info on block device oops / panic logger
cciss.txt
- info, major/minor #'s for Compaq's SMART Array Controllers.
cpqarray.txt
diff --git a/Documentation/blockdev/blkoops.txt b/Documentation/blockdev/blkoops.txt
new file mode 100644
index 0000000..fb08664
--- /dev/null
+++ b/Documentation/blockdev/blkoops.txt
@@ -0,0 +1,104 @@
+Block device oops / panic logger
+--------------------------------
+
+Contents:
+
+ 1) Overview
+ 2) Format
+ 3) Parameters
+ 4) blkoops and pstore
+ 5) debugfs
+
+1) Overview
+-----------
+
+ blkoops is a pstore back end to write panic / oops logs to a block
+ device. It is initially intended for use with eMMC as an alternative to
+ using a crash kernel.
+
+2) Format
+---------
+
+ Data is written in chunks called nodes which are preceded by a
+ header. The header is always aligned to a block boundary. Nodes are
+ written sequentially starting at the second block. The first block
+ contains a special node that fulfils 2 purposes: 1) the blkoops magic
+ number must be present or blkoops will not attach to the block device,
+ and 2) erase information is recorded there. Nodes can be arbitrarily
+ long.
+
+ Nodes are identified by session number, file number and part number.
+ A session may have up to 2^32 - 1 files each with up to 2^32 - 1 parts.
+
+ A new session begins when blkoops attaches to a block device and ends
+ when it detaches or there is a reboot. A new session overwrites the
+ previous session. Once the media is full no more nodes are written.
+
+3) Parameters
+-------------
+
+devname
+
+ Canonical block device name or number
+
+ devname may be set on the kernel command line e.g.
+
+ blkoops.devname=/dev/mmcblk0p7
+
+ or by writing to sysfs e.g.
+
+ echo /dev/mmcblk0p1 > /sys/module/blkoops/parameters/devname
+
+ devname is NOT the name of a file system object. e.g. /dev/mmcblk0p7
+ does NOT mean the block device special file mmcblk0p7 in the /dev
+ directory. Instead it means partition 7 of the device named mmcblk0.
+ For more information see name_to_dev_t comment in init/do_mounts.c
+
+ When devname is changed, the old devname (if any) is detached from
+ blkoops and the new devname (if any) is attached.
+
+ blkoops will reject a block device that does not have the blkoops magic
+ number written on the 1st sector. For example, to prepare
+ /dev/mmcblk0p7 for blkoops:
+
+ sudo bash -c "echo -e -n '\0034\0327\0130\0350' \
+ | dd count=1 conv=sync \
+ > /dev/mmcblk0p7"
+
+dump_oops
+
+ set to 1 to dump oopses, 0 to dump only panics (default 1)
+
+4) blkoops and pstore
+---------------------
+
+ pstore creates file names from pstore type code, back end name and
+ pstore 64-bit id. blkoops records the pstore type code, uses back end
+ name "blkoops", and creates the pstore 64-bit id from session number and
+ file number (session << 32 | file). blkoops concatenates all parts
+ together and presents them as one file.
+
+ pstore noramally reads back end data entirely into memory when mounting.
+ However if a blkoops file is too big it will be read from media as
+ needed instead.
+
+ blkoops suppreses pstore heading lines from dumped data.
+
+ blkoops increases pstore default kmsg_bytes to ULONG_MAX.
+
+5) debugfs
+----------
+
+blkoops/type
+
+ pstore type code to use when dumping data via blkoops/data
+
+blkoops/reason
+
+ kmsg dump reason code to use when dumping data via blkoops/data
+
+blkoops/data
+
+ Data written to blkoops/data is dumped to the block device
+ using blkoops/type. blkoops/reason must be the numberical value of
+ KMSG_DUMP_PANIC or (if dump_oops is 1) KMSG_DUMP_OOPS.
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 824e09c..af5b325 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -544,4 +544,14 @@ config BLK_DEV_RBD

If unsure, say N.

+config BLK_DEV_OOPS
+ bool "Block Oops / Panic Logger"
+ select BLK_DEV_PANIC_WRITE
+ select PSTORE
+ default n
+ help
+ This enables panic and oops messages to be logged to a block device.
+
+ See <file:Documentation/blockdev/blkoops.txt> for more information.
+
endif # BLK_DEV
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 17e82df..db44850 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_MG_DISK) += mg_disk.o
obj-$(CONFIG_SUNVDC) += sunvdc.o
obj-$(CONFIG_BLK_DEV_NVME) += nvme.o
obj-$(CONFIG_BLK_DEV_OSD) += osdblk.o
+obj-$(CONFIG_BLK_DEV_OOPS) += blkoops.o

obj-$(CONFIG_BLK_DEV_UMEM) += umem.o
obj-$(CONFIG_BLK_DEV_NBD) += nbd.o
diff --git a/drivers/block/blkoops.c b/drivers/block/blkoops.c
new file mode 100644
index 0000000..bafe17e
--- /dev/null
+++ b/drivers/block/blkoops.c
@@ -0,0 +1,1569 @@
+/*
+ * Block Oops / Panic Logger
+ *
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/swap.h> /* For nr_free_buffer_pages() */
+#include <linux/debugfs.h>
+#include <linux/completion.h>
+#include <linux/blkdev.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/pstore.h>
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG_PRINTK printk
+#else
+#define DBG_PRINTK no_printk
+#endif
+
+#define DBG(fmt, ...) \
+ DBG_PRINTK(KERN_DEBUG "%s: " fmt "\n", __func__, ##__VA_ARGS__)
+
+#define BLKOOPS_BUFSIZE (32 * 1024)
+
+#define BLKOOPS_MAGIC 0xe858d71c
+
+#define BLKOOPS_VERSION 1
+
+#define BLKOOPS_HDR_SZ (sizeof(struct blkoops_header))
+
+#define BLKOOPS_MODE (FMODE_READ | FMODE_WRITE | FMODE_EXCL)
+
+#define BLKOOPS_DEVNAME_SZ 256
+
+static char devname[BLKOOPS_DEVNAME_SZ];
+static int dump_oops;
+static int init_done;
+
+static DEFINE_MUTEX(blkoops_mutex);
+static DEFINE_SPINLOCK(blkoops_lock);
+
+/**
+ * struct blkoops - blkoops private data.
+ * @psi: pstore information
+ * @present: non-zero if blkoops is attached to a block device
+ * @bdev: block device to which that blkoops is attached
+ * @devid: block device (major, minor) number
+ * @blksize: block device block size
+ * @sects_per_blk: block device sectors per block
+ * @nr_sects: block device size in sectors
+ * @size: block device size in bytes
+ * @bdev_name: block device name
+ * @buf: panic write buffer
+ * @bufsize: panic write buffer size
+ * @last_session: last session number on media
+ * @next_session: next session number to write
+ * @next_file: next file number to write
+ * @next_sect: next sector to write
+ * @cache_invalid: page cache is invalid because
+ * @root: rb-tree of all blkoops nodes sorted by session / file / part numbers
+ * @file_cnt: number of (non-erased) files on media
+ * @max_file: maximum file number on media
+ * @used_sects: number of sectors used on media by the last session
+ * @erased_session: session number of a completely or partly erased session
+ * @erased_file: maximum erased file number of @erased_session
+ * @erased_sects: number of sectors used on media by @erased_session
+ * @read_anew: start reading from the first file
+ * @read_file: last file number read
+ * @dbg_root: blkoops debugfs root directory
+ * @dbg_type: pstore type code to use when writing
+ * @dbg_reason: kmsg dump reason code to use when writing
+ * @dbg_part: next part number to use when writing
+ * @dbg_lock: with @dbg_open, prevent debugfs 'data' file from having more than
+ 1 user, and also cause the call to 'blkoops_write()' to be in an
+ atomic context
+ * @dbg_open: non-zero if debugfs 'data' file is open
+ * @dbg_buf: debug write buffer
+ * @dbg_bufsize: debug write buffer size
+ * @dbg_used: number of bytes used in @dbg_buf
+ */
+struct blkoops {
+ struct pstore_info psi;
+
+ int present;
+
+ struct block_device *bdev;
+ dev_t devid;
+ unsigned int blksize;
+ unsigned int sects_per_blk;
+ sector_t nr_sects;
+ loff_t size;
+ char bdev_name[BDEVNAME_SIZE];
+
+ char *buf;
+ size_t bufsize;
+
+ unsigned int last_session;
+
+ unsigned int next_session;
+ unsigned int next_file;
+ sector_t next_sect;
+
+ int flush_needed;
+ int cache_invalid;
+
+ struct rb_root root;
+ unsigned int file_cnt;
+ unsigned int max_file;
+ sector_t used_sects;
+
+ unsigned int erased_session;
+ unsigned int erased_file;
+ sector_t erased_sects;
+
+ int read_anew;
+ unsigned int read_file;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dbg_root;
+ u32 dbg_type;
+ u32 dbg_reason;
+ u32 dbg_part;
+ spinlock_t dbg_lock; /* debug serialization */
+ int dbg_open;
+ void *dbg_buf;
+ size_t dbg_bufsize;
+ size_t dbg_used;
+#endif
+};
+
+/**
+ * struct blkoops_node - blkoops node.
+ * @node: rb-tree of all blkoops nodes
+ * @session: session number
+ * @file: file number
+ * @part: part number
+ * @len: length in bytes (excluding header)
+ * @nr_sects: length in sectors including header
+ * @type: pstore type code
+ * @timestamp: time the node was written to media
+ * @offs: offset on block device
+ * @file_offs: offset within file
+ * @tot_len: total length of file (only recorded on the first part)
+ */
+struct blkoops_node {
+ struct rb_node node;
+ unsigned int session;
+ unsigned int file;
+ unsigned int part;
+ loff_t len;
+ sector_t nr_sects;
+ enum pstore_type_id type;
+ struct timespec timestamp;
+ loff_t offs;
+ loff_t file_offs;
+ loff_t tot_len;
+};
+
+/**
+ * struct blkoops_header - blkoops on-media node header.
+ * @magic: blkoops magic number
+ * @version: blkoops media format version
+ * @session: session number
+ * @file: file number
+ * @part: part number
+ * @type: pstore type code
+ * @timestamp: time the node was written to media
+ * @len: length in bytes (excluding header)
+ * @nr_sects: length in sectors including header
+ * @padding: reserved for future, zeroes
+ *
+ * Data is written in chunks called nodes which are preceded by this header.
+ * The header is always aligned to a block boundary. Nodes are written
+ * sequentially starting at the second block. The first block contains a
+ * special node that fulfils 2 purposes: 1) the blkoops magic number must be
+ * present or blkoops will not attach to the block device, and 2) erase
+ * information is recorded there. Nodes can be arbitrarily long.
+ *
+ * Nodes are identified by session number, file number and part number. A
+ * session may have up to 2^32 - 1 files each with up to 2^32 - 1 parts.
+ *
+ * A new session begins when blkoops attaches to a block device and ends when it
+ * detaches or there is a reboot. A new session overwrites the previous session.
+ * Once the media is full no more nodes are written.
+ */
+struct blkoops_header {
+ __le32 magic;
+ __le32 version;
+ __le32 session;
+ __le32 file;
+ __le32 part;
+ __le32 type;
+ __le64 timestamp;
+ __le64 len;
+ __le64 nr_sects;
+ __u8 padding[16];
+} __packed;
+
+static int blkoops_lt(struct blkoops_node *bn1, struct blkoops_node *bn2)
+{
+ if (bn1->session == bn2->session) {
+ if (bn1->file == bn2->file)
+ return bn1->part < bn2->part;
+ return bn1->file < bn2->file;
+ }
+
+ return bn1->session < bn2->session;
+}
+
+static void blkoops_add_node(struct blkoops *c, struct blkoops_node *bn)
+{
+ struct rb_node **p = &c->root.rb_node;
+ struct rb_node *parent = NULL;
+ struct blkoops_node *t;
+
+ while (*p) {
+ parent = *p;
+ t = rb_entry(parent, struct blkoops_node, node);
+
+ if (blkoops_lt(bn, t))
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&bn->node, parent, p);
+ rb_insert_color(&bn->node, &c->root);
+}
+
+static int blkoops_lt_file(unsigned int session, unsigned int file,
+ struct blkoops_node *bn)
+{
+ if (session == bn->session)
+ return file < bn->file;
+ return session < bn->session;
+}
+
+static struct blkoops_node *blkoops_lookup_next(struct blkoops *c,
+ unsigned int session,
+ unsigned int file)
+{
+ struct rb_node *node = c->root.rb_node;
+ struct blkoops_node *bn, *next = NULL;
+
+ while (node) {
+ bn = rb_entry(node, struct blkoops_node, node);
+
+ if (blkoops_lt_file(session, file, bn)) {
+ node = node->rb_left;
+ next = bn;
+ } else {
+ node = node->rb_right;
+ }
+ }
+ return next;
+}
+
+static int blkoops_cmp_offs(unsigned int session, unsigned int file, loff_t pos,
+ struct blkoops_node *bn)
+{
+ if (session == bn->session) {
+ if (file == bn->file) {
+ if (pos < bn->file_offs)
+ return -1;
+ else if (pos >= bn->file_offs + bn->len)
+ return 1;
+ return 0;
+ }
+ return file < bn->file ? -1 : 1;
+ }
+ return session < bn->session ? -1 : 1;
+}
+
+static struct blkoops_node *blkoops_lookup_pos(struct blkoops *c,
+ unsigned int session,
+ unsigned int file, loff_t pos)
+{
+ struct rb_node *node = c->root.rb_node;
+ struct blkoops_node *bn;
+ int cmp;
+
+ while (node) {
+ bn = rb_entry(node, struct blkoops_node, node);
+ cmp = blkoops_cmp_offs(session, file, pos, bn);
+ if (cmp == -1)
+ node = node->rb_left;
+ else if (cmp == 1)
+ node = node->rb_right;
+ else
+ return bn;
+ }
+ return NULL;
+}
+
+static struct page *blkoops_read_page(struct blkoops *c, loff_t pos)
+{
+ pgoff_t index = pos >> PAGE_CACHE_SHIFT;
+
+ return read_mapping_page(c->bdev->bd_inode->i_mapping, index, NULL);
+}
+
+static int blkoops_validate_node(struct blkoops *c, struct blkoops_node *bn)
+{
+ sector_t nr_sects;
+ loff_t len;
+
+ if (bn->offs + bn->len > c->size) {
+ DBG("bad node size (offs %lld + len %lld > size %lld)",
+ bn->offs, bn->len, c->size);
+ return -EINVAL;
+ }
+ len = bn->len + BLKOOPS_HDR_SZ;
+ nr_sects = (len >> 9) + (len & 511 ? 1 : 0);
+ if (bn->nr_sects < nr_sects) {
+ DBG("bad node nr sects %llu vs len %lld",
+ (u64)bn->nr_sects, bn->len);
+ return -EINVAL;
+ }
+ if (bn->offs - BLKOOPS_HDR_SZ + ((loff_t)bn->nr_sects << 9) > c->size) {
+ DBG("bad node nr sects %llu vs size %lld",
+ (u64)bn->nr_sects, c->size);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct blkoops_node *blkoops_alloc_node(struct blkoops *c,
+ struct blkoops_header *hdr,
+ loff_t pos)
+{
+ struct blkoops_node *bn;
+
+ if (__le32_to_cpu(hdr->magic) != BLKOOPS_MAGIC) {
+ DBG("bad magic at pos %lld", pos);
+ return ERR_PTR(-EINVAL);
+ }
+
+ bn = kzalloc(sizeof(struct blkoops_node), GFP_KERNEL);
+ if (!bn)
+ return ERR_PTR(-ENOMEM);
+
+ RB_CLEAR_NODE(&bn->node);
+
+ bn->session = __le32_to_cpu(hdr->session);
+ bn->file = __le32_to_cpu(hdr->file);
+ bn->part = __le32_to_cpu(hdr->part);
+ bn->type = __le32_to_cpu(hdr->type);
+ bn->timestamp.tv_sec = __le64_to_cpu(hdr->timestamp);
+ bn->len = __le64_to_cpu(hdr->len);
+ bn->nr_sects = __le64_to_cpu(hdr->nr_sects);
+ bn->offs = pos + BLKOOPS_HDR_SZ;
+
+ return bn;
+}
+
+static struct blkoops_node *blkoops_read_node(struct blkoops *c, loff_t pos)
+{
+ struct blkoops_header *hdr;
+ struct blkoops_node *bn;
+ struct page *page;
+
+ if (pos >= c->size || pos & 511) {
+ DBG("bad pos %lld", pos);
+ return ERR_PTR(-EINVAL);
+ }
+
+ page = blkoops_read_page(c, pos);
+ if (IS_ERR(page)) {
+ pr_err("blkoops: bad page at pos %lld", pos);
+ return ERR_CAST(page);
+ }
+ /*
+ * Whole header must be within page because the header is sector aligned
+ * and smaller than 1 sector.
+ */
+ hdr = page_address(page) + (pos & (PAGE_CACHE_SIZE - 1));
+
+ bn = blkoops_alloc_node(c, hdr, pos);
+
+ page_cache_release(page);
+ return bn;
+}
+
+static inline loff_t blkoops_scan_start(struct blkoops *c)
+{
+ return c->blksize;
+}
+
+static loff_t blkoops_do_scan(struct blkoops *c, loff_t pos)
+{
+ struct blkoops_node *bn;
+ int err;
+
+ c->used_sects = pos >> 9;
+
+ if (pos >= c->size) {
+ DBG("end of scan at pos %lld (size %lld)", pos, c->size);
+ return 0;
+ }
+
+ bn = blkoops_read_node(c, pos);
+ if (IS_ERR(bn))
+ return PTR_ERR(bn);
+
+ err = blkoops_validate_node(c, bn);
+ if (err) {
+ DBG("bad node at pos %lld", pos);
+ goto out_free;
+ }
+
+ DBG("node at %lld sess %u file %u part %u len %lld nr sects %llu",
+ pos, bn->session, bn->file, bn->part, bn->len, (u64)bn->nr_sects);
+
+ /* A new oops may have written an updated session number */
+ if (pos == blkoops_scan_start(c))
+ c->last_session = bn->session;
+
+ /* Only scan the last session */
+ if (bn->session != c->last_session) {
+ DBG("wrong session %u (expected %u) at pos %lld",
+ bn->session, c->last_session, pos);
+ err = 0;
+ goto out_free;
+ }
+
+ /* Skip erased files */
+ if (c->erased_file && bn->session == c->erased_session &&
+ bn->file <= c->erased_file) {
+ loff_t next_pos, new_pos;
+
+ /*
+ * Try to skip to next non-erased node, but only if it makes
+ * sense.
+ */
+ next_pos = pos + ((loff_t)bn->nr_sects << 9);
+ new_pos = (loff_t)c->erased_sects << 9;
+ if (new_pos < next_pos || new_pos > c->size)
+ new_pos = next_pos;
+ DBG("skipping erased node at pos %lld continuing from pos %lld",
+ pos, new_pos);
+ pos = new_pos;
+ } else {
+ blkoops_add_node(c, bn);
+ pos += (loff_t)bn->nr_sects << 9;
+ }
+
+ return pos;
+
+out_free:
+ kfree(bn);
+ return err;
+}
+
+static void blkoops_free_tree(struct blkoops *c)
+{
+ struct rb_node *node, *next;
+ struct blkoops_node *bn;
+
+ node = rb_first(&c->root);
+ while (node) {
+ bn = rb_entry(node, struct blkoops_node, node);
+ next = rb_next(node);
+ rb_erase(node, &c->root);
+ kfree(bn);
+ node = next;
+ }
+
+ c->file_cnt = 0;
+}
+
+static void blkoops_count_files(struct blkoops *c)
+{
+ struct rb_node *node;
+ struct blkoops_node *bn, *last = NULL;
+
+ c->file_cnt = 0;
+ c->max_file = 0;
+
+ for (node = rb_first(&c->root); node; node = rb_next(node)) {
+ bn = rb_entry(node, struct blkoops_node, node);
+ if (last && bn->session == last->session &&
+ bn->file == last->file) {
+ bn->file_offs = last->tot_len;
+ last->tot_len += bn->len;
+ continue;
+ }
+ last = bn;
+ last->file_offs = 0;
+ last->tot_len = bn->len;
+ c->file_cnt += 1;
+ if (bn->file > c->max_file)
+ c->max_file = bn->file;
+ }
+ DBG("file cnt %u max file %u used sects %llu",
+ c->file_cnt, c->max_file, (u64)c->used_sects);
+}
+
+static int blkoops_scan(struct blkoops *c)
+{
+ loff_t pos = blkoops_scan_start(c);
+
+ blkoops_free_tree(c);
+
+ if (c->cache_invalid) {
+ DBG("cache_invalid");
+ c->cache_invalid = 0;
+ invalidate_mapping_pages(c->bdev->bd_inode->i_mapping, 0, -1);
+ }
+
+ do {
+ pos = blkoops_do_scan(c, pos);
+ } while (pos > 0);
+
+ blkoops_count_files(c);
+
+ return pos;
+}
+
+static int blkoops_open(struct pstore_info *psi)
+{
+ struct blkoops *c = psi->data;
+
+ mutex_lock(&blkoops_mutex);
+ if (!c->present)
+ goto out;
+ blkoops_scan(c);
+ c->read_anew = 1;
+out:
+ mutex_unlock(&blkoops_mutex);
+ return 0;
+}
+
+static struct blkoops_node *blkoops_next_node(struct blkoops_node *bn)
+{
+ struct rb_node *node;
+ struct blkoops_node *t;
+
+ node = rb_next(&bn->node);
+ if (!node)
+ return NULL;
+ t = rb_entry(node, struct blkoops_node, node);
+ if (t->session == bn->session && t->file == bn->file)
+ return t;
+ return NULL;
+}
+
+static int blkoops_read_to_buf(struct blkoops *c, char *buf, loff_t pos,
+ loff_t len)
+{
+ struct page *page;
+ loff_t offs;
+ size_t n;
+ int err = 0;
+
+ while (len) {
+ page = blkoops_read_page(c, pos);
+
+ offs = pos & (PAGE_CACHE_SIZE - 1);
+ n = PAGE_CACHE_SIZE - offs;
+ if (n > len)
+ n = len;
+
+ if (IS_ERR(page)) {
+ if (!err)
+ err = PTR_ERR(page);
+ memset(buf, 0, n);
+ } else {
+ memcpy(buf, page_address(page) + offs, n);
+ }
+
+ pos += n;
+ buf += n;
+ len -= n;
+
+ page_cache_release(page);
+ }
+
+ return err;
+}
+
+static int blkoops_fill_buf(struct blkoops *c, char *buf,
+ struct blkoops_node *bn)
+{
+ loff_t tot_len = bn->tot_len;
+ int err = 0, err1;
+
+ while (bn && tot_len) {
+ err1 = blkoops_read_to_buf(c, buf, bn->offs, bn->len);
+ if (!err)
+ err = err1;
+ tot_len -= bn->len;
+ buf += bn->len;
+ bn = blkoops_next_node(bn);
+ }
+ return err;
+}
+
+static struct blkoops_node *blkoops_read_next(struct blkoops *c)
+{
+ struct blkoops_node *bn = NULL;
+ struct rb_node *node;
+
+ if (c->read_anew) {
+ c->read_anew = 0;
+ node = rb_first(&c->root);
+ if (node)
+ bn = rb_entry(node, struct blkoops_node, node);
+ } else {
+ bn = blkoops_lookup_next(c, c->last_session, c->read_file);
+ }
+ if (bn)
+ c->read_file = bn->file;
+ return bn;
+}
+
+static ssize_t blkoops_read_to_userbuf(struct blkoops *c, char __user *userbuf,
+ loff_t pos, size_t len)
+{
+ struct page *page;
+ ssize_t ret = 0;
+ loff_t offs;
+ size_t n, rn;
+ int err;
+
+ while (len) {
+ page = blkoops_read_page(c, pos);
+
+ offs = pos & (PAGE_CACHE_SIZE - 1);
+ n = PAGE_CACHE_SIZE - offs;
+ if (n > len)
+ n = len;
+
+ if (IS_ERR(page)) {
+ err = PTR_ERR(page);
+ goto out_err;
+ }
+
+ rn = copy_to_user(userbuf, page_address(page) + offs, n);
+ if (rn) {
+ page_cache_release(page);
+ ret += n - rn;
+ err = -EFAULT;
+ goto out_err;
+ }
+
+ pos += n;
+ userbuf += n;
+ len -= n;
+ ret += n;
+
+ page_cache_release(page);
+ }
+
+ return ret;
+
+out_err:
+ if (!ret)
+ ret = err;
+ return ret;
+}
+
+static inline unsigned int blkoops_session(u64 id)
+{
+ return id >> 32;
+}
+
+static inline unsigned int blkoops_file(u64 id)
+{
+ return id;
+}
+
+static ssize_t blkoops_file_read(u64 id, enum pstore_type_id type,
+ char __user *userbuf, size_t count,
+ loff_t *ppos, struct pstore_info *psi)
+{
+ struct blkoops *c = psi->data;
+ struct blkoops_node *bn;
+ unsigned int session, file;
+ ssize_t done, res = 0;
+ loff_t noffs, dpos;
+ size_t len;
+ int err;
+
+ if (*ppos < 0)
+ return -EINVAL;
+
+ session = blkoops_session(id);
+ file = blkoops_file(id);
+
+ mutex_lock(&blkoops_mutex);
+ if (!c->present) {
+ err = -ENODEV;
+ goto out_err;
+ }
+
+ DBG("sess %u file %u pos %lld count %zu", session, file, *ppos, count);
+
+ while (count) {
+ bn = blkoops_lookup_pos(c, session, file, *ppos);
+ if (!bn)
+ break;
+ noffs = *ppos - bn->file_offs;
+ len = min_t(loff_t, count, bn->len - noffs);
+ dpos = bn->offs + noffs;
+ done = blkoops_read_to_userbuf(c, userbuf, dpos, len);
+ if (done < 0) {
+ err = done;
+ goto out_err;
+ }
+ res += done;
+ if (done < len)
+ break;
+ *ppos += len;
+ userbuf += len;
+ count -= len;
+ }
+out:
+ DBG("sess %u file %u pos %lld res %zd", session, file, *ppos, res);
+ mutex_unlock(&blkoops_mutex);
+ return res;
+
+out_err:
+ if (!res)
+ res = err;
+ goto out;
+}
+
+static inline u64 blkoops_id(u32 session, u32 file)
+{
+ return (u64)session << 32 | file;
+}
+
+static int blkoops_read(u64 *id, enum pstore_type_id *type,
+ struct timespec *time, char **buf, loff_t *size,
+ struct pstore_info *psi)
+{
+ struct blkoops *c = psi->data;
+ struct blkoops_node *bn;
+ unsigned long limit;
+ u64 bn_id;
+ int err;
+
+ mutex_lock(&blkoops_mutex);
+ if (!c->present) {
+ err = -ENODEV;
+ goto out;
+ }
+
+ bn = blkoops_read_next(c);
+ if (!bn) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ bn_id = blkoops_id(bn->session, bn->file);
+
+ DBG("node at pos %lld sess %u file %u part %u len %lld nr sects %llu",
+ bn->offs, bn->session, bn->file, bn->part, bn->len,
+ (u64)bn->nr_sects);
+
+ *type = bn->type;
+ *id = bn_id;
+ *time = bn->timestamp;
+ *size = bn->tot_len;
+
+ limit = nr_free_buffer_pages() << (PAGE_SHIFT - 3);
+ limit /= c->file_cnt;
+
+ if (bn->tot_len > limit) {
+ DBG("file size %lld over limit %lu", bn->tot_len, limit);
+ err = -EFBIG;
+ goto out;
+ }
+
+ *buf = kmalloc(bn->tot_len, GFP_KERNEL | __GFP_NOWARN);
+ if (!*buf) {
+ DBG("failed to allocate %lld bytes", bn->tot_len);
+ err = -EFBIG;
+ goto out;
+ }
+
+ err = blkoops_fill_buf(c, *buf, bn);
+ if (err) {
+ pr_err("blkoops: read failed, file id %lld, error %d\n",
+ bn_id, err);
+ err = 0;
+ }
+out:
+ mutex_unlock(&blkoops_mutex);
+ return err;
+}
+
+static int blkoops_write(enum pstore_type_id type, enum kmsg_dump_reason reason,
+ u64 *id, unsigned int part, size_t size,
+ struct pstore_info *psi)
+{
+ struct blkoops *c = psi->data;
+ struct blkoops_header *hdr;
+ size_t nr, sz, rsz, len;
+ u32 partno;
+ int err;
+
+ /* If blkoops_lock is locked then there is no back end, so give up */
+ if (!spin_trylock(&blkoops_lock))
+ return -ENODEV;
+
+ if (!c->present) {
+ err = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (reason != KMSG_DUMP_OOPS &&
+ reason != KMSG_DUMP_PANIC) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (reason == KMSG_DUMP_OOPS && !dump_oops) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (c->next_sect + c->sects_per_blk > c->nr_sects) {
+ err = -ENOSPC;
+ goto out_unlock;
+ }
+
+ if (part == 1)
+ c->next_file += 1;
+
+ /*
+ * Special case: dmesg is written backwards so reverse the order of the
+ * part numbers.
+ */
+ if (type == PSTORE_TYPE_DMESG)
+ partno = -part;
+ else
+ partno = part;
+
+ *id = blkoops_id(c->next_session, c->next_file);
+
+ /* Round up the size to block size and pad with zeroes */
+ sz = size + BLKOOPS_HDR_SZ;
+ rsz = roundup(sz, c->blksize);
+ nr = rsz >> 9;
+ memset(c->psi.buf + size, 0, rsz - sz);
+
+ /*
+ * Truncate the node to fit the remaining space. Note, we have
+ * already checked that there is enough space for at least 1 block.
+ */
+ len = size;
+ while (c->next_sect + nr > c->nr_sects) {
+ nr -= c->sects_per_blk;
+ len = (nr << 9) - BLKOOPS_HDR_SZ;
+ }
+
+ hdr = (void *)c->buf;
+ memset(hdr, 0, BLKOOPS_HDR_SZ);
+ hdr->magic = __cpu_to_le32(BLKOOPS_MAGIC);
+ hdr->version = __cpu_to_le32(BLKOOPS_VERSION);
+ hdr->session = __cpu_to_le32(c->next_session);
+ hdr->file = __cpu_to_le32(c->next_file);
+ hdr->part = __cpu_to_le32(partno);
+ hdr->type = __cpu_to_le32(type);
+ hdr->timestamp = __cpu_to_le64(get_seconds());
+ hdr->len = __cpu_to_le64(len);
+ hdr->nr_sects = __cpu_to_le64(nr);
+
+ c->cache_invalid = 1;
+ c->flush_needed = 1;
+
+ err = blk_panic_write(c->bdev, c->next_sect, c->buf, nr << 9);
+ if (err)
+ goto out_unlock;
+
+ c->next_sect += nr;
+
+out_unlock:
+ spin_unlock(&blkoops_lock);
+ return err;
+}
+
+struct blkoops_bio_batch {
+ atomic_t done;
+ unsigned long flags;
+ struct completion *wait;
+};
+
+static void blkoops_end_io(struct bio *bio, int err)
+{
+ struct blkoops_bio_batch *bb = bio->bi_private;
+
+ if (err)
+ clear_bit(BIO_UPTODATE, &bb->flags);
+ if (atomic_dec_and_test(&bb->done))
+ complete(bb->wait);
+ bio_put(bio);
+}
+
+static int blkoops_direct_io(int type, struct block_device *bdev, sector_t sect,
+ unsigned long nr, void *buf)
+{
+ DECLARE_COMPLETION_ONSTACK(wait);
+ struct blkoops_bio_batch bb;
+ struct bio *bio;
+ int ret = 0;
+ unsigned long len = nr << 9;
+
+ DBG("type %#x sect %llu nr %lu", type, (u64)sect, nr);
+
+ atomic_set(&bb.done, 1);
+ bb.flags = 1 << BIO_UPTODATE;
+ bb.wait = &wait;
+
+ while (len) {
+ bio = bio_alloc(GFP_KERNEL, 1);
+ if (!bio) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ bio->bi_sector = sect;
+ bio->bi_end_io = blkoops_end_io;
+ bio->bi_bdev = bdev;
+ bio->bi_private = &bb;
+
+ while (len) {
+ unsigned int offs = offset_in_page(buf);
+ unsigned int n = PAGE_SIZE - offs;
+ int bytes;
+
+ if (n > len)
+ n = len;
+ bytes = bio_add_page(bio, virt_to_page(buf), n, offs);
+ if (bytes <= 0)
+ break;
+ len -= bytes;
+ buf += bytes;
+ }
+ sect += bio->bi_size >> 9;
+
+ atomic_inc(&bb.done);
+ submit_bio(type, bio);
+ }
+
+ if (!atomic_dec_and_test(&bb.done))
+ wait_for_completion(&wait);
+
+ if (!test_bit(BIO_UPTODATE, &bb.flags))
+ ret = -EIO;
+
+ if (ret)
+ DBG("I/O error %d", ret);
+ return ret;
+}
+
+static void blkoops_invalidate_range(struct block_device *bdev, sector_t sect,
+ unsigned int nr)
+{
+ pgoff_t start, end;
+
+ start = sect >> (PAGE_CACHE_SHIFT - 9);
+ end = (sect + nr - 1) >> (PAGE_CACHE_SHIFT - 9);
+
+ invalidate_mapping_pages(bdev->bd_inode->i_mapping, start, end);
+}
+
+static int blkoops_write_meta(struct block_device *bdev, sector_t sect,
+ unsigned long nr, void *buf)
+{
+ blkoops_invalidate_range(bdev, sect, nr);
+
+ return blkoops_direct_io(REQ_WRITE | REQ_META, bdev, sect, nr, buf);
+}
+
+static int blkoops_mark_erased(struct blkoops *c)
+{
+ struct blkoops_header *hdr;
+ int err;
+
+ DBG("session %u file %u sects %llu",
+ c->erased_session, c->erased_file, (u64)c->erased_sects);
+
+ hdr = kzalloc(c->blksize, GFP_KERNEL);
+ if (!hdr)
+ return -ENOMEM;
+
+ hdr->magic = __cpu_to_le32(BLKOOPS_MAGIC);
+ hdr->version = __cpu_to_le32(BLKOOPS_VERSION);
+ hdr->session = __cpu_to_le32(c->erased_session);
+ hdr->file = __cpu_to_le32(c->erased_file);
+ hdr->timestamp = __cpu_to_le64(get_seconds());
+ hdr->nr_sects = __cpu_to_le64(c->erased_sects);
+
+ err = blkoops_write_meta(c->bdev, 0, c->sects_per_blk, hdr);
+
+ kfree(hdr);
+
+ return err;
+}
+
+static int blkoops_erase(enum pstore_type_id type, u64 id,
+ struct pstore_info *psi)
+{
+ struct blkoops *c = psi->data;
+ int err = 0;
+
+ mutex_lock(&blkoops_mutex);
+ if (!c->present) {
+ err = -ENODEV;
+ goto out;
+ }
+ DBG("type %d id %lld", type, id);
+ /*
+ * Do nothing until all files are erased and then mark the range as
+ * erased.
+ */
+ if (c->file_cnt && !--c->file_cnt && !c->cache_invalid) {
+ c->erased_session = c->last_session;
+ c->erased_file = c->max_file;
+ c->erased_sects = c->used_sects;
+ err = blkoops_mark_erased(c);
+ }
+out:
+ mutex_unlock(&blkoops_mutex);
+ return err;
+}
+
+static int blkoops_flush(struct pstore_info *psi)
+{
+ struct blkoops *c = psi->data;
+ int err;
+
+ /* If blkoops_lock is locked then there is no back end, so give up */
+ if (!spin_trylock(&blkoops_lock))
+ return -ENODEV;
+
+ if (!c->present) {
+ err = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (!c->flush_needed) {
+ err = 0;
+ goto out_unlock;
+ }
+
+ err = blk_panic_flush(c->bdev);
+
+out_unlock:
+ spin_unlock(&blkoops_lock);
+ return err;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int blkoops_dbg_write_buf(struct blkoops *c, void *buf, size_t len)
+{
+ unsigned long flags;
+ size_t n;
+ int ret;
+ u64 id;
+
+ while (len) {
+ n = min(len, c->psi.bufsize);
+ memcpy(c->psi.buf, buf, n);
+ spin_lock_irqsave(&c->dbg_lock, flags);
+ ret = blkoops_write(c->dbg_type, c->dbg_reason, &id,
+ c->dbg_part++, n, &c->psi);
+ spin_unlock_irqrestore(&c->dbg_lock, flags);
+ if (ret) {
+ pr_err("blkoops: debug write failed, error %d\n", ret);
+ return ret;
+ }
+ buf += n;
+ len -= n;
+ }
+ return 0;
+}
+
+static int blkoops_dbg_flush_buf(struct blkoops *c)
+{
+ int err;
+
+ err = blkoops_dbg_write_buf(c, c->dbg_buf, c->dbg_used);
+ c->dbg_used = 0;
+ return err;
+}
+
+static int blkoops_dbg_drain_buf(struct blkoops *c)
+{
+ size_t written = 0;
+ int err = 0;
+
+ while (c->dbg_used >= c->psi.bufsize) {
+ err = blkoops_dbg_write_buf(c, c->dbg_buf + written,
+ c->psi.bufsize);
+ c->dbg_used -= c->psi.bufsize;
+ written += c->psi.bufsize;
+ }
+ memmove(c->dbg_buf, c->dbg_buf + written, c->dbg_used);
+ return err;
+}
+
+static ssize_t blkoops_dbg_fill_buf(struct blkoops *c, const char __user *buf,
+ size_t len)
+{
+ size_t remains = c->dbg_bufsize - c->dbg_used, n = min(len, remains);
+ void *addr = c->dbg_buf + c->dbg_used;
+
+ if (copy_from_user(addr, buf, n))
+ return -EFAULT;
+ c->dbg_used += n;
+ return n;
+}
+
+static ssize_t blkoops_dbg_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ struct blkoops *c = file->private_data;
+ ssize_t n, res = len;
+
+ while (len) {
+ n = blkoops_dbg_fill_buf(c, buf, len);
+ if (n < 0)
+ return n;
+ buf += n;
+ len -= n;
+ if (c->dbg_used == c->dbg_bufsize)
+ blkoops_dbg_drain_buf(c);
+ }
+ return res;
+}
+
+static int blkoops_dbg_file_open(struct inode *inode, struct file *file)
+{
+ struct blkoops *c = inode->i_private;
+ unsigned long flags;
+ int err = 0;
+
+ spin_lock_irqsave(&c->dbg_lock, flags);
+ if (c->dbg_open)
+ err = -EBUSY;
+ else
+ c->dbg_open = 1;
+ spin_unlock_irqrestore(&c->dbg_lock, flags);
+ if (err)
+ return err;
+
+ c->dbg_bufsize = BLKOOPS_BUFSIZE;
+ c->dbg_buf = kmalloc(c->dbg_bufsize, GFP_KERNEL);
+ if (!c->dbg_buf) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+
+ err = nonseekable_open(inode, file);
+ if (err)
+ goto out_err;
+
+ file->private_data = c;
+
+ c->dbg_part = 1;
+ c->dbg_used = 0;
+
+ return 0;
+
+out_err:
+ spin_lock_irqsave(&c->dbg_lock, flags);
+ c->dbg_open = 0;
+ spin_unlock_irqrestore(&c->dbg_lock, flags);
+ return err;
+}
+
+static int blkoops_dbg_file_release(struct inode *inode, struct file *file)
+{
+ struct blkoops *c = inode->i_private;
+ unsigned long flags;
+ int err, err2;
+
+ err = blkoops_dbg_flush_buf(c);
+
+ spin_lock_irqsave(&c->dbg_lock, flags);
+ err2 = blkoops_flush(&c->psi);
+ spin_unlock_irqrestore(&c->dbg_lock, flags);
+ if (err2 && !err)
+ err = err2;
+
+ kfree(c->dbg_buf);
+
+ spin_lock_irqsave(&c->dbg_lock, flags);
+ c->dbg_open = 0;
+ spin_unlock_irqrestore(&c->dbg_lock, flags);
+
+ return err;
+}
+
+static const struct file_operations dbg_data_fops = {
+ .owner = THIS_MODULE,
+ .open = blkoops_dbg_file_open,
+ .release = blkoops_dbg_file_release,
+ .write = blkoops_dbg_write,
+ .llseek = no_llseek,
+};
+
+static void blkoops_init_debugfs(struct blkoops *c)
+{
+ umode_t rw = S_IRUSR | S_IWUSR, wo = S_IWUSR;
+
+ c->dbg_root = debugfs_create_dir("blkoops", NULL);
+
+ debugfs_create_u32("type", rw, c->dbg_root, &c->dbg_type);
+ debugfs_create_u32("reason", rw, c->dbg_root, &c->dbg_reason);
+ debugfs_create_file("data", wo, c->dbg_root, c, &dbg_data_fops);
+}
+
+static void blkoops_remove_debugfs(struct blkoops *c)
+{
+ debugfs_remove_recursive(c->dbg_root);
+}
+
+#else
+
+static inline void blkoops_init_debugfs(struct blkoops *c)
+{
+}
+
+static void blkoops_remove_debugfs(struct blkoops *c)
+{
+}
+
+#endif
+
+static void *blkoops_alloc_buf(unsigned int blksize, loff_t max_size,
+ unsigned int *rsz)
+{
+ unsigned int size;
+ void *addr;
+ gfp_t flgs;
+
+ size = BLKOOPS_BUFSIZE;
+ if (size > max_size)
+ size = max_size;
+
+ while (1) {
+ if (size < blksize)
+ *rsz = blksize;
+ else
+ *rsz = roundup(size, blksize);
+ flgs = *rsz == blksize ? GFP_KERNEL : GFP_KERNEL | __GFP_NOWARN;
+ addr = kzalloc(*rsz, flgs);
+ if (addr || *rsz == blksize)
+ break;
+ size >>= 1;
+ }
+ return addr;
+}
+
+static int blkoops_read_session(struct blkoops *c)
+{
+ struct blkoops_node *bn;
+
+ bn = blkoops_read_node(c, 0);
+ if (IS_ERR(bn)) {
+ if (PTR_ERR(bn) == -EINVAL)
+ pr_err("blkoops: %s: bad magic\n", c->bdev_name);
+ return PTR_ERR(bn);
+ }
+
+ c->last_session = bn->session;
+ c->next_session = c->last_session + 1;
+ c->next_file = 0;
+
+ c->erased_session = bn->session;
+ c->erased_file = bn->file;
+ c->erased_sects = bn->nr_sects;
+
+ kfree(bn);
+
+ bn = blkoops_read_node(c, blkoops_scan_start(c));
+ if (IS_ERR(bn))
+ goto out;
+
+ if (!blkoops_validate_node(c, bn)) {
+ c->last_session = bn->session;
+ c->next_session = c->last_session + 1;
+ }
+
+ kfree(bn);
+out:
+ DBG("sess %u size %lld blksz %u", c->last_session, c->size, c->blksize);
+ if (c->erased_file) {
+ DBG("erased sess %u file %u sects %llu",
+ c->erased_session, c->erased_file, (u64)c->erased_sects);
+ }
+
+ return 0;
+}
+
+static int blkoops_get_bdev_size(struct blkoops *c, struct block_device *bdev)
+{
+ unsigned int blksize;
+ loff_t size;
+
+ blksize = bdev_logical_block_size(bdev);
+ if (blksize < 512 || blksize & 511)
+ return -EINVAL;
+
+ size = i_size_read(bdev->bd_inode);
+ if (size < 2 * blksize)
+ return -EINVAL;
+
+ c->blksize = blksize;
+ c->sects_per_blk = blksize >> 9;
+ c->size = size;
+ c->nr_sects = size >> 9;
+
+ return 0;
+}
+
+static struct blkoops *blkoops_alloc(void)
+{
+ struct blkoops *c;
+
+ c = kzalloc(sizeof(struct blkoops), GFP_KERNEL);
+ if (!c)
+ return NULL;
+
+ c->psi.owner = THIS_MODULE,
+ c->psi.name = "blkoops",
+ c->psi.flags = PSTORE_NO_HEADINGS | PSTORE_MAX_KMSG_BYTES,
+ c->psi.open = blkoops_open,
+ c->psi.read = blkoops_read,
+ c->psi.file_read = blkoops_file_read,
+ c->psi.write = blkoops_write,
+ c->psi.erase = blkoops_erase,
+ c->psi.flush = blkoops_flush,
+ c->psi.data = c;
+
+ spin_lock_init(&c->psi.buf_lock);
+#ifdef CONFIG_DEBUG_FS
+ spin_lock_init(&c->dbg_lock);
+#endif
+ return c;
+}
+
+static struct blkoops *blkoops;
+
+static int blkoops_do_add(struct blkoops *c)
+{
+ int err;
+
+ if (c->present)
+ return -EINVAL;
+
+ if (!*devname)
+ return -EINVAL;
+
+ *c->bdev_name = '\0';
+
+ c->devid = name_to_dev_t(devname);
+ if (!c->devid) {
+ err = -ENODEV;
+ goto out_err;
+ }
+
+ c->bdev = blkdev_get_by_dev(c->devid, BLKOOPS_MODE, blkoops_do_add);
+ if (IS_ERR(c->bdev)) {
+ err = PTR_ERR(c->bdev);
+ goto out_err;
+ }
+
+ bdevname(c->bdev, c->bdev_name);
+
+ err = blk_panic_init(c->bdev);
+ if (err)
+ goto out_put;
+
+ err = blkoops_get_bdev_size(c, c->bdev);
+ if (err)
+ goto out_cleanup;
+
+ err = blkoops_read_session(c);
+ if (err)
+ goto out_cleanup;
+
+ if (!c->buf) {
+ unsigned int rsz;
+ void *addr;
+
+ addr = blkoops_alloc_buf(c->blksize, c->size, &rsz);
+ if (!addr) {
+ err = -ENOMEM;
+ goto out_cleanup;
+ }
+ c->buf = addr;
+ c->bufsize = rsz;
+ c->root = RB_ROOT;
+ }
+
+ c->next_sect = blkoops_scan_start(c) >> 9;
+
+ spin_lock(&blkoops_lock);
+ c->present = 1;
+ spin_unlock(&blkoops_lock);
+
+ if (!c->psi.buf) {
+ c->psi.buf = c->buf + BLKOOPS_HDR_SZ;
+ c->psi.bufsize = c->bufsize - BLKOOPS_HDR_SZ;
+ err = pstore_register(&c->psi);
+ if (err)
+ goto out_no_pstore;
+ }
+
+ blkoops_init_debugfs(c);
+
+ pr_info("blkoops initialized on %s\n", c->bdev_name);
+
+ return 0;
+
+out_no_pstore:
+ c->psi.buf = NULL;
+ c->present = 0;
+ kfree(c->buf);
+ c->buf = NULL;
+out_cleanup:
+ blk_panic_cleanup(c->bdev);
+out_put:
+ blkdev_put(c->bdev, BLKOOPS_MODE);
+out_err:
+ pr_err("blkoops initialization failed on %s, error %d\n",
+ *c->bdev_name ? c->bdev_name : devname, err);
+ return err;
+}
+
+static void blkoops_do_remove(struct blkoops *c)
+{
+ if (!c->present)
+ return;
+
+ blkoops_remove_debugfs(c);
+
+ spin_lock(&blkoops_lock);
+ c->present = 0;
+ spin_unlock(&blkoops_lock);
+
+ blkoops_free_tree(c);
+
+ blk_panic_cleanup(c->bdev);
+
+ blkdev_put(c->bdev, BLKOOPS_MODE);
+
+ pr_info("blkoops detached from %s\n", c->bdev_name);
+}
+
+static int blkoops_add(void)
+{
+ int err;
+
+ /*
+ * Headers are sector aligned and less than 1 sector in size so that
+ * only whole headers are read.
+ */
+ BUILD_BUG_ON(BLKOOPS_HDR_SZ > 512);
+
+ if (!blkoops)
+ blkoops = blkoops_alloc();
+ if (!blkoops)
+ return -ENOMEM;
+
+ err = blkoops_do_add(blkoops);
+
+ if (!blkoops->psi.buf) {
+ /*
+ * 'blkoops' can be freed if the registration with pstore
+ * failed, otherwise 'blkoops' is never freed.
+ */
+ kfree(blkoops);
+ blkoops = NULL;
+ }
+
+ return err;
+}
+
+static void blkoops_remove(void)
+{
+ if (blkoops)
+ blkoops_do_remove(blkoops);
+}
+
+static int __init blkoops_init(void)
+{
+ int err = 0;
+ dev_t devid;
+
+ mutex_lock(&blkoops_mutex);
+ init_done = 1;
+ if (!*devname)
+ goto out;
+ devid = name_to_dev_t(devname);
+ if (!devid)
+ wait_for_device_probe();
+ err = blkoops_add();
+out:
+ mutex_unlock(&blkoops_mutex);
+ return err;
+}
+
+late_initcall(blkoops_init);
+
+static int param_set_devname(const char *val, const struct kernel_param *kp)
+{
+ int err = 0;
+
+ if (strlen(val) >= BLKOOPS_DEVNAME_SZ) {
+ pr_err("blkoops: devname parameter too long\n");
+ return -ENOSPC;
+ }
+
+ mutex_lock(&blkoops_mutex);
+ if (init_done)
+ blkoops_remove();
+ if (sscanf(val, "%s", devname) != 1)
+ *devname = '\0';
+ if (init_done && *devname)
+ err = blkoops_add();
+ mutex_unlock(&blkoops_mutex);
+
+ return err;
+}
+
+static int param_get_devname(char *buffer, const struct kernel_param *kp)
+{
+ return snprintf(buffer, BLKOOPS_DEVNAME_SZ, "%s", devname);
+}
+
+static struct kernel_param_ops param_ops_devname = {
+ .set = param_set_devname,
+ .get = param_get_devname,
+};
+
+module_param_cb(devname, &param_ops_devname, &devname, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(devname, "Canonical block device name or number");
+
+module_param(dump_oops, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(dump_oops,
+ "set to 1 to dump oopses, 0 to dump only panics (default 0)");
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Adrian Hunter");
+MODULE_DESCRIPTION("Block Oops / Panic Logger");
--
1.7.9.5

2012-11-08 13:04:04

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 11/26] mmc: panic write: bypass clock gating

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 5 +++++
drivers/mmc/core/host.c | 9 +++++++++
2 files changed, 14 insertions(+)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 204a9bd..f7552af 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -956,6 +956,11 @@ void mmc_set_ungated(struct mmc_host *host)
{
unsigned long flags;

+ if (mmc_am_panic_task(host)) {
+ host->clk_gated = false;
+ return;
+ }
+
/*
* We've been given a new frequency while the clock is gated,
* so make sure we regard this as ungating it.
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index ee2e16b..6eda5a1 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -153,6 +153,12 @@ void mmc_host_clk_hold(struct mmc_host *host)
{
unsigned long flags;

+ if (mmc_am_panic_task(host)) {
+ if (host->clk_gated)
+ mmc_ungate_clock(host);
+ return;
+ }
+
/* cancel any clock gating work scheduled by mmc_host_clk_release() */
cancel_delayed_work_sync(&host->clk_gate_work);
mutex_lock(&host->clk_gate_mutex);
@@ -200,6 +206,9 @@ void mmc_host_clk_release(struct mmc_host *host)
{
unsigned long flags;

+ if (mmc_am_panic_task(host))
+ return;
+
spin_lock_irqsave(&host->clk_lock, flags);
host->clk_requests--;
if (mmc_host_may_gate_card(host->card) &&
--
1.7.9.5

2012-11-08 13:04:12

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 13/26] mmc: panic write: trap non panic tasks

From: Adrian Hunter <[email protected]>

If in panic, wait for task to come into context.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 10 +++++++++-
drivers/mmc/core/host.c | 2 ++
2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index d3caa7e..4fd7061 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -252,6 +252,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
}
mmc_host_clk_hold(host);
led_trigger_event(host->led, LED_FULL);
+ mmc_trap_nonpanic_tasks(host);
host->ops->request(host, mrq);
}

@@ -344,8 +345,10 @@ static void mmc_wait_for_req_done(struct mmc_host *host,

while (1) {
/* Panic task requests must be completed in ->request() */
- if (!mmc_am_panic_task(host))
+ if (!mmc_am_panic_task(host)) {
wait_for_completion(&mrq->completion);
+ mmc_trap_nonpanic_tasks(host);
+ }

cmd = mrq->cmd;
if (!cmd->error || !cmd->retries ||
@@ -774,6 +777,8 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)

if (mmc_am_panic_task(host))
return 0;
+ else
+ mmc_trap_nonpanic_tasks(host);

might_sleep();

@@ -817,6 +822,8 @@ int mmc_try_claim_host(struct mmc_host *host)

if (mmc_am_panic_task(host))
return 1;
+ else
+ mmc_trap_nonpanic_tasks(host);

spin_lock_irqsave(&host->lock, flags);
if (!host->claimed || host->claimer == current) {
@@ -880,6 +887,7 @@ static inline void mmc_set_ios(struct mmc_host *host)

if (ios->clock > 0)
mmc_set_ungated(host);
+ mmc_trap_nonpanic_tasks(host);
host->ops->set_ios(host, ios);
}

diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 6eda5a1..b76bfc8 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -90,6 +90,8 @@ static void mmc_host_clk_gate_delayed(struct mmc_host *host)
unsigned long freq = host->ios.clock;
unsigned long flags;

+ mmc_trap_nonpanic_tasks(host);
+
if (!freq) {
pr_debug("%s: frequency set to 0 in disable function, "
"this means the clock is already disabled.\n",
--
1.7.9.5

2012-11-08 13:04:20

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 17/26] mmc: sdhci: panic write: call tasklets inline

From: Adrian Hunter <[email protected]>

When in panic task, we need to schedule other tasklets than normally.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 827e34f..ff72e98 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -914,6 +914,17 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
}

+static void sdhci_tasklet_card(unsigned long param);
+static void sdhci_tasklet_finish(unsigned long param);
+
+#define sdhci_sched_tasklet(host, name) \
+{ \
+ if (mmc_am_panic_task(host->mmc)) \
+ sdhci_tasklet_##name((unsigned long)host); \
+ else \
+ tasklet_schedule(&host->name##_tasklet); \
+}
+
static void sdhci_finish_data(struct sdhci_host *host)
{
struct mmc_data *data;
@@ -965,7 +976,7 @@ static void sdhci_finish_data(struct sdhci_host *host)

sdhci_send_command(host, data->stop);
} else
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
}

static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
@@ -994,7 +1005,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
"inhibit bit(s).\n", mmc_hostname(host->mmc));
sdhci_dumpregs(host);
cmd->error = -EIO;
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
return;
}
timeout--;
@@ -1015,7 +1026,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
pr_err("%s: Unsupported response type!\n",
mmc_hostname(host->mmc));
cmd->error = -EINVAL;
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
return;
}

@@ -1076,7 +1087,7 @@ static void sdhci_finish_command(struct sdhci_host *host)
sdhci_finish_data(host);

if (!host->cmd->data)
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);

host->cmd = NULL;
}
@@ -1303,7 +1314,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)

if (!present || host->flags & SDHCI_DEVICE_DEAD) {
host->mrq->cmd->error = -ENOMEDIUM;
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
} else {
u32 present_state;

@@ -2079,7 +2090,7 @@ static void sdhci_tasklet_card(unsigned long param)
sdhci_reset(host, SDHCI_RESET_DATA);

host->mrq->cmd->error = -ENOMEDIUM;
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
}

sdhci_unlock_irqrestore(host, flags);
@@ -2174,7 +2185,7 @@ static void sdhci_timeout_timer(unsigned long data)
else
host->mrq->cmd->error = -ETIMEDOUT;

- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
}
}

@@ -2221,7 +2232,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)
host->cmd->error = -EILSEQ;

if (host->cmd->error) {
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
return;
}

@@ -2429,7 +2440,7 @@ again:
sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT |
SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS);
intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);
- tasklet_schedule(&host->card_tasklet);
+ sdhci_sched_tasklet(host, card);
}

if (intmask & SDHCI_INT_CMD_MASK) {
@@ -3192,7 +3203,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
" transfer!\n", mmc_hostname(host->mmc));

host->mrq->cmd->error = -ENOMEDIUM;
- tasklet_schedule(&host->finish_tasklet);
+ sdhci_sched_tasklet(host, finish);
}

sdhci_unlock_irqrestore(host, flags);
--
1.7.9.5

2012-11-08 13:04:16

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 16/26] mmc: sdhci: panic write: no sleeping

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 1ed78f0..827e34f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1536,6 +1536,14 @@ static int sdhci_check_ro(struct sdhci_host *host)

#define SAMPLE_COUNT 5

+static void sdhci_msleep(struct sdhci_host *host, unsigned int ms)
+{
+ if (mmc_am_panic_task(host->mmc))
+ mdelay(ms);
+ else
+ msleep(ms);
+}
+
static int sdhci_do_get_ro(struct sdhci_host *host)
{
int i, ro_count;
@@ -1549,7 +1557,7 @@ static int sdhci_do_get_ro(struct sdhci_host *host)
if (++ro_count > SAMPLE_COUNT / 2)
return 1;
}
- msleep(30);
+ sdhci_msleep(host, 30);
}
return 0;
}
@@ -1605,6 +1613,14 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
sdhci_unlock_irqrestore(host, flags);
}

+#define sdhci_usleep_range(host, min, max) \
+do { \
+ if (mmc_am_panic_task((host)->mmc)) \
+ mdelay(DIV_ROUND_UP(min, 1000)); \
+ else \
+ usleep_range(min, max); \
+} while (0)
+
static int sdhci_do_3_3v_signal_voltage_switch(struct sdhci_host *host,
u16 ctrl)
{
@@ -1623,7 +1639,7 @@ static int sdhci_do_3_3v_signal_voltage_switch(struct sdhci_host *host,
}
}
/* Wait for 5ms */
- usleep_range(5000, 5500);
+ sdhci_usleep_range(host, 5000, 5500);

/* 3.3V regulator output should be stable within 5 ms */
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
@@ -1668,7 +1684,7 @@ static int sdhci_do_1_8v_signal_voltage_switch(struct sdhci_host *host,
sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);

/* Wait for 5ms */
- usleep_range(5000, 5500);
+ sdhci_usleep_range(host, 5000, 5500);

ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
if (ctrl & SDHCI_CTRL_VDD_180) {
@@ -1676,7 +1692,7 @@ static int sdhci_do_1_8v_signal_voltage_switch(struct sdhci_host *host,
clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
clk |= SDHCI_CLOCK_CARD_EN;
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
- usleep_range(1000, 1500);
+ sdhci_usleep_range(host, 1000, 1500);

/*
* If DAT[3:0] level is 1111b, then the card
@@ -1703,7 +1719,7 @@ static int sdhci_do_1_8v_signal_voltage_switch(struct sdhci_host *host,
regulator_disable(host->vmmc);

/* Wait for 1ms as per the spec */
- usleep_range(1000, 1500);
+ sdhci_usleep_range(host, 1000, 1500);
pwr |= SDHCI_POWER_ON;
sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
if (host->vmmc)
--
1.7.9.5

2012-11-08 13:04:27

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 19/26] mmc: sdhci: panic write: no runtime pm

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 3653494..fcedcec 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -2601,11 +2601,17 @@ EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups);

static int sdhci_runtime_pm_get(struct sdhci_host *host)
{
+ if (mmc_am_panic_task(host->mmc))
+ return 0;
+
return pm_runtime_get_sync(host->mmc->parent);
}

static int sdhci_runtime_pm_put(struct sdhci_host *host)
{
+ if (mmc_am_panic_task(host->mmc))
+ return 0;
+
pm_runtime_mark_last_busy(host->mmc->parent);
return pm_runtime_put_autosuspend(host->mmc->parent);
}
--
1.7.9.5

2012-11-08 13:04:33

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 22/26] mmc: sdhci: panic write: no dma mapping

From: Adrian Hunter <[email protected]>

Implement simple dma ops to avoid usage dma mapping in panic mode.
Init & cleanup pave the way for the dma ops implemented through
pre and post functions.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 158 ++++++++++++++++++++++++++++++++++++++++++++-
include/linux/mmc/sdhci.h | 8 +++
2 files changed, 164 insertions(+), 2 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index d85e9d5..ab3f5ce 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -649,6 +649,69 @@ static void sdhci_adma_table_post(struct sdhci_host *host,
data->sg_len, direction);
}

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+static void sdhci_panic_dma_pre(struct sdhci_host *host, struct mmc_data *data)
+{
+ struct scatterlist *sg;
+ int i, len;
+ dma_addr_t addr;
+ u8 *desc;
+
+ if (host->flags & SDHCI_USE_ADMA) {
+ if (data->sg_len != 1) {
+ WARN_ON(1);
+ host->flags &= ~SDHCI_REQ_USE_DMA;
+ return;
+ }
+ desc = host->panic_adma_desc;
+ for_each_sg(data->sg, sg, 1, i) {
+ addr = host->panic_dma_addr;
+ len = sg->length;
+ sdhci_set_adma_desc(desc, addr, len, 0x21);
+ desc += 8;
+ }
+ if (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC) {
+ if (desc != host->panic_adma_desc) {
+ desc -= 8;
+ desc[0] |= 0x2;
+ }
+ } else {
+ sdhci_set_adma_desc(desc, 0, 0, 0x3);
+ }
+ sdhci_writel(host, host->panic_adma_addr, SDHCI_ADMA_ADDRESS);
+ } else {
+ sdhci_writel(host, host->panic_dma_addr, SDHCI_DMA_ADDRESS);
+ }
+
+ if (data->flags & MMC_DATA_WRITE) {
+ sg_copy_to_buffer(data->sg, data->sg_len, host->panic_buf,
+ host->panic_bufsize);
+ }
+}
+
+static void sdhci_panic_dma_post(struct sdhci_host *host, struct mmc_data *data)
+{
+ if (data->flags & MMC_DATA_READ) {
+ sg_copy_from_buffer(data->sg, data->sg_len, host->panic_buf,
+ host->panic_bufsize);
+ }
+}
+
+#else
+
+static inline void sdhci_panic_dma_pre(struct sdhci_host *host,
+ struct mmc_data *data)
+{
+}
+
+static inline void sdhci_panic_dma_post(struct sdhci_host *host,
+ struct mmc_data *data)
+{
+}
+
+#endif
+
static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
{
u8 count;
@@ -810,7 +873,9 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
}

if (host->flags & SDHCI_REQ_USE_DMA) {
- if (host->flags & SDHCI_USE_ADMA) {
+ if (mmc_am_panic_task(host->mmc)) {
+ sdhci_panic_dma_pre(host, data);
+ } else if (host->flags & SDHCI_USE_ADMA) {
ret = sdhci_adma_table_pre(host, data);
if (ret) {
/*
@@ -937,7 +1002,9 @@ static void sdhci_finish_data(struct sdhci_host *host)
host->data = NULL;

if (host->flags & SDHCI_REQ_USE_DMA) {
- if (host->flags & SDHCI_USE_ADMA)
+ if (mmc_am_panic_task(host->mmc))
+ sdhci_panic_dma_post(host, data);
+ else if (host->flags & SDHCI_USE_ADMA)
sdhci_adma_table_post(host, data);
else {
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
@@ -2045,6 +2112,91 @@ static const struct mmc_host_ops sdhci_ops = {

#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE

+/*
+ * Arbitrary maximum DMA buffer size and hence maximum request size when using
+ * DMA.
+ */
+#define SDHCI_PANIC_DMA_BUFSIZE (32 * 1024)
+
+/*
+ * We DMA at most one segment which is aligned. Plus we need an end descriptor.
+ * We allow for a 8 byte (32-bit address) descriptor size.
+ */
+#define SDHCI_PANIC_ADMA_DESC_SZ ((1 + 1) * 12)
+
+static void sdhci_panic_dma_cleanup(struct sdhci_host *host)
+{
+ if (host->panic_adma_desc) {
+ dma_free_coherent(mmc_dev(host->mmc), SDHCI_PANIC_ADMA_DESC_SZ,
+ host->panic_adma_desc, host->panic_adma_addr);
+ host->panic_adma_desc = NULL;
+ }
+
+ if (host->panic_buf) {
+ dma_free_coherent(mmc_dev(host->mmc), host->panic_bufsize,
+ host->panic_buf, host->panic_dma_addr);
+ host->panic_buf = NULL;
+ }
+}
+
+static int sdhci_panic_dma_init(struct sdhci_host *host)
+{
+ size_t size;
+
+ size = min_t(size_t, SDHCI_PANIC_DMA_BUFSIZE, host->mmc->max_seg_size);
+ while (size > PAGE_SIZE) {
+ host->panic_buf = dma_alloc_coherent(mmc_dev(host->mmc), size,
+ &host->panic_dma_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (host->panic_buf)
+ break;
+ size >>= 1;
+ }
+ if (!host->panic_buf) {
+ host->panic_buf = dma_alloc_coherent(mmc_dev(host->mmc), size,
+ &host->panic_dma_addr,
+ GFP_KERNEL);
+ }
+ if (!host->panic_buf)
+ return -ENOMEM;
+
+ host->panic_bufsize = size;
+ host->mmc->panic_max_size = size;
+
+ if (host->flags & SDHCI_USE_ADMA) {
+ size = SDHCI_PANIC_ADMA_DESC_SZ;
+ host->panic_adma_desc = dma_alloc_coherent(mmc_dev(host->mmc),
+ size,
+ &host->panic_adma_addr,
+ GFP_KERNEL);
+ if (!host->panic_adma_desc) {
+ sdhci_panic_dma_cleanup(host);
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static int sdhci_panic_init(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ mmc->panic_max_size = mmc->max_seg_size;
+
+ if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))
+ return sdhci_panic_dma_init(host);
+
+ return 0;
+}
+
+static void sdhci_panic_cleanup(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ sdhci_panic_dma_cleanup(host);
+}
+
void sdhci_lock(struct sdhci_host *host)
{
if (mmc_am_panic_task(host->mmc))
@@ -2092,6 +2244,8 @@ static void sdhci_panic_end(struct mmc_host *mmc)
}

static const struct mmc_panic_ops sdhci_pops = {
+ .init = sdhci_panic_init,
+ .cleanup = sdhci_panic_cleanup,
.begin = sdhci_panic_begin,
.end = sdhci_panic_end,
};
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index fa8529a..873e41d 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -172,6 +172,14 @@ struct sdhci_host {
#define SDHCI_TUNING_MODE_1 0
struct timer_list tuning_timer; /* Timer for tuning */

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ void *panic_buf; /* buffer for panic requests */
+ size_t panic_bufsize; /* panic buffer size */
+ dma_addr_t panic_dma_addr; /* panic buffer dma address */
+ u8 *panic_adma_desc; /* adma desc buffer */
+ dma_addr_t panic_adma_addr; /* adma desc address */
+#endif
+
unsigned long private[0] ____cacheline_aligned;
};
#endif /* LINUX_MMC_SDHCI_H */
--
1.7.9.5

2012-11-08 13:04:40

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 24/26] mmc: sdhci: panic write: abort request in progress

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 496adc8..9a24417 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1351,6 +1351,11 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)

sdhci_lock_irqsave(host, flags);

+ if (mmc_am_panic_task(mmc) && host->mrq) {
+ host->mrq->cmd->error = -EILSEQ;
+ sdhci_sched_tasklet(host, finish);
+ }
+
WARN_ON(host->mrq != NULL);

#ifndef SDHCI_USE_LEDS_CLASS
--
1.7.9.5

2012-11-08 13:04:30

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 21/26] mmc: sdhci: panic write: poll interrupts

From: Adrian Hunter <[email protected]>

We really don't want to use interrupts while doing a panic dump, so
implement polling.

The mmc panic begin/end ops have also been added for sdhci.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index e096526..d85e9d5 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -131,6 +131,8 @@ static void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
ier &= ~clear;
ier |= set;
sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+ if (mmc_panic_task_active(host->mmc))
+ ier = 0;
sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
}

@@ -1267,6 +1269,8 @@ static int sdhci_set_power(struct sdhci_host *host, unsigned short power)
* *
\*****************************************************************************/

+static irqreturn_t sdhci_irq(int irq, void *dev_id);
+
static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host;
@@ -1347,6 +1351,11 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)

mmiowb();
sdhci_unlock_irqrestore(host, flags);
+
+ if (mmc_am_panic_task(host->mmc)) {
+ while (host->mrq)
+ sdhci_irq(0, host);
+ }
}

static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
@@ -2066,6 +2075,27 @@ void sdhci_unlock_irqrestore(struct sdhci_host *host, unsigned long flags)
spin_unlock_irqrestore(&host->lock, flags);
}

+static void sdhci_panic_begin(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
+}
+
+static void sdhci_panic_end(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u32 ier;
+
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+}
+
+static const struct mmc_panic_ops sdhci_pops = {
+ .begin = sdhci_panic_begin,
+ .end = sdhci_panic_end,
+};
+
#endif

/*****************************************************************************\
@@ -2854,6 +2884,9 @@ int sdhci_add_host(struct sdhci_host *host)
* Set host parameters.
*/
mmc->ops = &sdhci_ops;
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ mmc->pops = &sdhci_pops;
+#endif
mmc->f_max = host->max_clk;
if (host->ops->get_min_clock)
mmc->f_min = host->ops->get_min_clock(host);
--
1.7.9.5

2012-11-08 13:04:36

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 23/26] mmc: sdhci: panic write: resume suspended host

From: Adrian Hunter <[email protected]>

Resume host when entering panic mode.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index ab3f5ce..496adc8 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -2231,6 +2231,10 @@ static void sdhci_panic_begin(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);

+ if (host->runtime_suspended)
+ sdhci_runtime_resume_host(host);
+ else if (mmc->ios.vdd == 0)
+ sdhci_resume_host(host);
sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
}

@@ -2741,10 +2745,12 @@ int sdhci_resume_host(struct sdhci_host *host)
host->ops->enable_dma(host);
}

- ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
- mmc_hostname(host->mmc), host);
- if (ret)
- return ret;
+ if (!mmc_am_panic_task(host->mmc)) {
+ ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
+ mmc_hostname(host->mmc), host);
+ if (ret)
+ return ret;
+ }

if ((host->mmc->pm_flags & MMC_PM_KEEP_POWER) &&
(host->quirks2 & SDHCI_QUIRK2_HOST_OFF_CARD_ON)) {
--
1.7.9.5

2012-11-08 13:04:46

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 26/26] mmc: sdhci-pci: add panic write support

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci-pci.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
index 4bb74b0..bee9a45 100644
--- a/drivers/mmc/host/sdhci-pci.c
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -966,6 +966,8 @@ static void sdhci_pci_hw_reset(struct sdhci_host *host)

if (!gpio_is_valid(rst_n_gpio))
return;
+ if (mmc_panic_task_active(host->mmc))
+ return;
gpio_set_value_cansleep(rst_n_gpio, 0);
/* For eMMC, minimum is 1us but give it 10us for good measure */
udelay(10);
@@ -1278,6 +1280,9 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
}

host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ slot->host->mmc->caps2 = MMC_CAP_PANIC_WRITE;
+#endif

ret = sdhci_add_host(host);
if (ret)
--
1.7.9.5

2012-11-08 13:05:01

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 25/26] mmc: sdhci: panic write: trap nonpanic tasks

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 47 +++++++++++++++++++++++++++++++++++++++++----
include/linux/mmc/sdhci.h | 1 +
2 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 9a24417..db12fad 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -2204,8 +2204,12 @@ static void sdhci_panic_cleanup(struct mmc_host *mmc)

void sdhci_lock(struct sdhci_host *host)
{
- if (mmc_am_panic_task(host->mmc))
- return;
+ if (mmc_panic_task_active(host->mmc)) {
+ if (mmc_am_panic_task(host->mmc))
+ return;
+ while (mmc_am_nonpanic_task(host->mmc))
+ cpu_relax();
+ }
spin_lock(&host->lock);
}

@@ -2219,8 +2223,12 @@ void _sdhci_lock_irqsave(struct sdhci_host *host, unsigned long *flags_ptr)
{
unsigned long flags;

- if (mmc_am_panic_task(host->mmc))
- return;
+ if (mmc_panic_task_active(host->mmc)) {
+ if (mmc_am_panic_task(host->mmc))
+ return;
+ while (mmc_am_nonpanic_task(host->mmc))
+ cpu_relax();
+ }

spin_lock_irqsave(&host->lock, flags);
*flags_ptr = flags;
@@ -2235,6 +2243,16 @@ void sdhci_unlock_irqrestore(struct sdhci_host *host, unsigned long flags)
static void sdhci_panic_begin(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);
+ int i;
+
+ /* Wait a bit for other users to go away */
+ for (i = 0; i < 10000; i++) {
+ if (spin_trylock(&host->lock)) {
+ host->locked = 1;
+ break;
+ }
+ udelay(1);
+ }

if (host->runtime_suspended)
sdhci_runtime_resume_host(host);
@@ -2250,6 +2268,10 @@ static void sdhci_panic_end(struct mmc_host *mmc)

ier = sdhci_readl(host, SDHCI_INT_ENABLE);
sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+ if (host->locked) {
+ host->locked = 0;
+ spin_unlock(&host->lock);
+ }
}

static const struct mmc_panic_ops sdhci_pops = {
@@ -2368,6 +2390,9 @@ static void sdhci_timeout_timer(unsigned long data)

host = (struct sdhci_host*)data;

+ if (mmc_am_nonpanic_task(host->mmc))
+ return;
+
sdhci_lock_irqsave(host, flags);

if (host->mrq) {
@@ -2399,6 +2424,9 @@ static void sdhci_tuning_timer(unsigned long data)

host = (struct sdhci_host *)data;

+ if (mmc_am_nonpanic_task(host->mmc))
+ return;
+
sdhci_lock_irqsave(host, flags);

host->flags |= SDHCI_NEEDS_RETUNING;
@@ -2596,6 +2624,9 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
u32 intmask, unexpected = 0;
int cardint = 0, max_loops = 16;

+ if (mmc_am_nonpanic_task(host->mmc))
+ return IRQ_HANDLED;
+
sdhci_lock(host);


@@ -2713,6 +2744,8 @@ int sdhci_suspend_host(struct sdhci_host *host)
if (host->ops->platform_suspend)
host->ops->platform_suspend(host);

+ mmc_trap_nonpanic_tasks(host->mmc);
+
sdhci_disable_card_detection(host);

/* Disable tuning since we are suspending */
@@ -2745,6 +2778,8 @@ int sdhci_resume_host(struct sdhci_host *host)
{
int ret;

+ mmc_trap_nonpanic_tasks(host->mmc);
+
if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
if (host->ops->enable_dma)
host->ops->enable_dma(host);
@@ -2820,6 +2855,8 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host)
unsigned long flags;
int ret = 0;

+ mmc_trap_nonpanic_tasks(host->mmc);
+
/* Disable tuning since we are suspending */
if (host->flags & SDHCI_USING_RETUNING_TIMER) {
del_timer_sync(&host->tuning_timer);
@@ -2845,6 +2882,8 @@ int sdhci_runtime_resume_host(struct sdhci_host *host)
unsigned long flags;
int ret = 0, host_flags = host->flags;

+ mmc_trap_nonpanic_tasks(host->mmc);
+
if (host_flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
if (host->ops->enable_dma)
host->ops->enable_dma(host);
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index 873e41d..13cc598 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -173,6 +173,7 @@ struct sdhci_host {
struct timer_list tuning_timer; /* Timer for tuning */

#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ int locked; /* panic task has the lock */
void *panic_buf; /* buffer for panic requests */
size_t panic_bufsize; /* panic buffer size */
dma_addr_t panic_dma_addr; /* panic buffer dma address */
--
1.7.9.5

2012-11-08 13:06:21

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 20/26] mmc: sdhci: panic write: no tuning

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index fcedcec..e096526 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1794,6 +1794,10 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
int err = 0;
bool requires_tuning_nonuhs = false;

+ /* Tuning may need a timer so it is not supported by the panic task */
+ if (mmc_am_panic_task(mmc))
+ return -EINVAL;
+
host = mmc_priv(mmc);

sdhci_runtime_pm_get(host);
--
1.7.9.5

2012-11-08 13:06:57

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 18/26] mmc: sdhci: panic write: no timeout timer

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index ff72e98..3653494 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1012,7 +1012,8 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
mdelay(1);
}

- mod_timer(&host->timer, jiffies + 10 * HZ);
+ if (!mmc_am_panic_task(host->mmc))
+ mod_timer(&host->timer, jiffies + 10 * HZ);

host->cmd = cmd;

@@ -2117,7 +2118,8 @@ static void sdhci_tasklet_finish(unsigned long param)
return;
}

- del_timer(&host->timer);
+ if (!mmc_am_panic_task(host->mmc))
+ del_timer(&host->timer);

mrq = host->mrq;

--
1.7.9.5

2012-11-08 13:07:42

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 15/26] mmc: sdhci: panic write: bypass spin lock

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/host/sdhci.c | 119 ++++++++++++++++++++++++++++++----------------
drivers/mmc/host/sdhci.h | 24 ++++++++++
2 files changed, 101 insertions(+), 42 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 7922adb..1ed78f0 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -287,7 +287,7 @@ static void sdhci_led_control(struct led_classdev *led,
struct sdhci_host *host = container_of(led, struct sdhci_host, led);
unsigned long flags;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

if (host->runtime_suspended)
goto out;
@@ -297,7 +297,7 @@ static void sdhci_led_control(struct led_classdev *led,
else
sdhci_activate_led(host);
out:
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}
#endif

@@ -1266,7 +1266,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)

sdhci_runtime_pm_get(host);

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

WARN_ON(host->mrq != NULL);

@@ -1319,9 +1319,9 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
tuning_opcode = mmc->card->type == MMC_TYPE_MMC ?
MMC_SEND_TUNING_BLOCK_HS200 :
MMC_SEND_TUNING_BLOCK;
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
sdhci_execute_tuning(mmc, tuning_opcode);
- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

/* Restore original mmc_request structure */
host->mrq = mrq;
@@ -1334,7 +1334,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
}

mmiowb();
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
@@ -1343,10 +1343,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
int vdd_bit = -1;
u8 ctrl;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

if (host->flags & SDHCI_DEVICE_DEAD) {
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
if (host->vmmc && ios->power_mode == MMC_POWER_OFF)
mmc_regulator_set_ocr(host->mmc, host->vmmc, 0);
return;
@@ -1369,9 +1369,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
vdd_bit = sdhci_set_power(host, ios->vdd);

if (host->vmmc && vdd_bit != -1) {
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
mmc_regulator_set_ocr(host->mmc, host->vmmc, vdd_bit);
- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);
}

if (host->ops->platform_send_init_74_clocks)
@@ -1500,7 +1500,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);

mmiowb();
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -1517,7 +1517,7 @@ static int sdhci_check_ro(struct sdhci_host *host)
unsigned long flags;
int is_readonly;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

if (host->flags & SDHCI_DEVICE_DEAD)
is_readonly = 0;
@@ -1527,7 +1527,7 @@ static int sdhci_check_ro(struct sdhci_host *host)
is_readonly = !(sdhci_readl(host, SDHCI_PRESENT_STATE)
& SDHCI_WRITE_PROTECT);

- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);

/* This quirk needs to be replaced by a callback-function later */
return host->quirks & SDHCI_QUIRK_INVERTED_WRITE_PROTECT ?
@@ -1600,9 +1600,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
struct sdhci_host *host = mmc_priv(mmc);
unsigned long flags;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);
sdhci_enable_sdio_irq_nolock(host, enable);
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

static int sdhci_do_3_3v_signal_voltage_switch(struct sdhci_host *host,
@@ -1770,7 +1770,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)

sdhci_runtime_pm_get(host);
disable_irq(host->irq);
- spin_lock(&host->lock);
+ sdhci_lock(host);

ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);

@@ -1790,7 +1790,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
requires_tuning_nonuhs)
ctrl |= SDHCI_CTRL_EXEC_TUNING;
else {
- spin_unlock(&host->lock);
+ sdhci_unlock(host);
enable_irq(host->irq);
sdhci_runtime_pm_put(host);
return 0;
@@ -1863,7 +1863,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
host->cmd = NULL;
host->mrq = NULL;

- spin_unlock(&host->lock);
+ sdhci_unlock(host);
enable_irq(host->irq);

/* Wait for Buffer Read Ready interrupt */
@@ -1871,7 +1871,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
(host->tuning_done == 1),
msecs_to_jiffies(50));
disable_irq(host->irq);
- spin_lock(&host->lock);
+ sdhci_lock(host);

if (!host->tuning_done) {
pr_info(DRIVER_NAME ": Timeout waiting for "
@@ -1945,7 +1945,7 @@ out:
err = 0;

sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);
- spin_unlock(&host->lock);
+ sdhci_unlock(host);
enable_irq(host->irq);
sdhci_runtime_pm_put(host);

@@ -1961,7 +1961,7 @@ static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
if (host->version < SDHCI_SPEC_300)
return;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);

@@ -1979,7 +1979,7 @@ static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
host->flags &= ~SDHCI_PV_ENABLED;
}

- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
@@ -2002,6 +2002,40 @@ static const struct mmc_host_ops sdhci_ops = {
.enable_preset_value = sdhci_enable_preset_value,
};

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+void sdhci_lock(struct sdhci_host *host)
+{
+ if (mmc_am_panic_task(host->mmc))
+ return;
+ spin_lock(&host->lock);
+}
+
+void sdhci_unlock(struct sdhci_host *host)
+{
+ if (!mmc_am_panic_task(host->mmc))
+ spin_unlock(&host->lock);
+}
+
+void _sdhci_lock_irqsave(struct sdhci_host *host, unsigned long *flags_ptr)
+{
+ unsigned long flags;
+
+ if (mmc_am_panic_task(host->mmc))
+ return;
+
+ spin_lock_irqsave(&host->lock, flags);
+ *flags_ptr = flags;
+}
+
+void sdhci_unlock_irqrestore(struct sdhci_host *host, unsigned long flags)
+{
+ if (!mmc_am_panic_task(host->mmc))
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+#endif
+
/*****************************************************************************\
* *
* Tasklets *
@@ -2015,7 +2049,7 @@ static void sdhci_tasklet_card(unsigned long param)

host = (struct sdhci_host*)param;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

/* Check host->mrq first in case we are runtime suspended */
if (host->mrq &&
@@ -2032,7 +2066,7 @@ static void sdhci_tasklet_card(unsigned long param)
tasklet_schedule(&host->finish_tasklet);
}

- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);

mmc_detect_change(host->mmc, msecs_to_jiffies(200));
}
@@ -2045,14 +2079,14 @@ static void sdhci_tasklet_finish(unsigned long param)

host = (struct sdhci_host*)param;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

/*
* If this tasklet gets rescheduled while running, it will
* be run again afterwards but without any active request.
*/
if (!host->mrq) {
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
return;
}

@@ -2095,7 +2129,7 @@ static void sdhci_tasklet_finish(unsigned long param)
#endif

mmiowb();
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);

mmc_request_done(host->mmc, mrq);
sdhci_runtime_pm_put(host);
@@ -2108,7 +2142,7 @@ static void sdhci_timeout_timer(unsigned long data)

host = (struct sdhci_host*)data;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

if (host->mrq) {
pr_err("%s: Timeout waiting for hardware "
@@ -2129,7 +2163,7 @@ static void sdhci_timeout_timer(unsigned long data)
}

mmiowb();
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

static void sdhci_tuning_timer(unsigned long data)
@@ -2139,11 +2173,11 @@ static void sdhci_tuning_timer(unsigned long data)

host = (struct sdhci_host *)data;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

host->flags |= SDHCI_NEEDS_RETUNING;

- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

/*****************************************************************************\
@@ -2336,10 +2370,11 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
u32 intmask, unexpected = 0;
int cardint = 0, max_loops = 16;

- spin_lock(&host->lock);
+ sdhci_lock(host);
+

if (host->runtime_suspended) {
- spin_unlock(&host->lock);
+ sdhci_unlock(host);
pr_warning("%s: got irq while runtime suspended\n",
mmc_hostname(host->mmc));
return IRQ_HANDLED;
@@ -2421,7 +2456,7 @@ again:
if (intmask && --max_loops)
goto again;
out:
- spin_unlock(&host->lock);
+ sdhci_unlock(host);

if (unexpected) {
pr_err("%s: Unexpected interrupt 0x%08x.\n",
@@ -2557,15 +2592,15 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host)
host->flags &= ~SDHCI_NEEDS_RETUNING;
}

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);
sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK);
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);

synchronize_irq(host->irq);

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);
host->runtime_suspended = true;
- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);

return ret;
}
@@ -2596,7 +2631,7 @@ int sdhci_runtime_resume_host(struct sdhci_host *host)
if (host->flags & SDHCI_USING_RETUNING_TIMER)
host->flags |= SDHCI_NEEDS_RETUNING;

- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

host->runtime_suspended = false;

@@ -2607,7 +2642,7 @@ int sdhci_runtime_resume_host(struct sdhci_host *host)
/* Enable Card Detection */
sdhci_enable_card_detection(host);

- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);

return ret;
}
@@ -3132,7 +3167,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
unsigned long flags;

if (dead) {
- spin_lock_irqsave(&host->lock, flags);
+ sdhci_lock_irqsave(host, flags);

host->flags |= SDHCI_DEVICE_DEAD;

@@ -3144,7 +3179,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
tasklet_schedule(&host->finish_tasklet);
}

- spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_unlock_irqrestore(host, flags);
}

sdhci_disable_card_detection(host);
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 97653ea..e2444918 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -388,4 +388,28 @@ extern int sdhci_runtime_suspend_host(struct sdhci_host *host);
extern int sdhci_runtime_resume_host(struct sdhci_host *host);
#endif

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+extern void sdhci_lock(struct sdhci_host *host);
+extern void sdhci_unlock(struct sdhci_host *host);
+
+extern void _sdhci_lock_irqsave(struct sdhci_host *host,
+ unsigned long *flags_ptr);
+extern void sdhci_unlock_irqrestore(struct sdhci_host *host,
+ unsigned long flags);
+
+#define sdhci_lock_irqsave(host, flags) _sdhci_lock_irqsave(host, &(flags))
+
+#else
+
+#define sdhci_lock(host) spin_lock(&(host)->lock)
+#define sdhci_unlock(host) spin_unlock(&(host)->lock)
+
+#define sdhci_lock_irqsave(host, flags) \
+ spin_lock_irqsave(&(host)->lock, flags)
+#define sdhci_unlock_irqrestore(host, flags) \
+ spin_unlock_irqrestore(&(host)->lock, flags)
+
+#endif
+
#endif /* __SDHCI_HW_H */
--
1.7.9.5

2012-11-08 13:04:10

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 14/26] mmc: panic write: bypass bus ref locking

From: Adrian Hunter <[email protected]>

We want to avoid it while panic dumping.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 4fd7061..3daec19 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1418,6 +1418,11 @@ static inline void mmc_bus_get(struct mmc_host *host)
{
unsigned long flags;

+ if (mmc_am_panic_task(host)) {
+ host->bus_refs++;
+ return;
+ }
+
spin_lock_irqsave(&host->lock, flags);
host->bus_refs++;
spin_unlock_irqrestore(&host->lock, flags);
@@ -1431,6 +1436,11 @@ static inline void mmc_bus_put(struct mmc_host *host)
{
unsigned long flags;

+ if (mmc_am_panic_task(host)) {
+ host->bus_refs--;
+ return;
+ }
+
spin_lock_irqsave(&host->lock, flags);
host->bus_refs--;
if ((host->bus_refs == 0) && host->bus_ops)
--
1.7.9.5

2012-11-08 13:08:26

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 12/26] mmc: panic write: bypass regulators

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index f7552af..d3caa7e 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1128,6 +1128,9 @@ int mmc_regulator_set_ocr(struct mmc_host *mmc,
int result = 0;
int min_uV, max_uV;

+ if (mmc_am_panic_task(mmc))
+ return 0;
+
if (vdd_bit) {
int tmp;
int voltage;
--
1.7.9.5

2012-11-08 13:08:42

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 10/26] mmc: panic write: do not msleep

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 6 +++---
drivers/mmc/core/core.h | 6 ++++--
drivers/mmc/core/mmc_ops.c | 10 +++++-----
drivers/mmc/core/sd_ops.c | 2 +-
drivers/mmc/core/sdio_ops.c | 2 +-
5 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 0b904bc..204a9bd 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1331,7 +1331,7 @@ static void mmc_power_up(struct mmc_host *host)
* This delay should be sufficient to allow the power supply
* to reach the minimum voltage.
*/
- mmc_delay(10);
+ mmc_delay(host, 10);

host->ios.clock = host->f_init;

@@ -1342,7 +1342,7 @@ static void mmc_power_up(struct mmc_host *host)
* This delay must be at least 74 clock sizes, or 1 ms, or the
* time required to reach a stable voltage.
*/
- mmc_delay(10);
+ mmc_delay(host, 10);

mmc_host_clk_release(host);
}
@@ -1378,7 +1378,7 @@ void mmc_power_off(struct mmc_host *host)
* XO-1.5, require a short delay after poweroff before the card
* can be successfully turned on again.
*/
- mmc_delay(1);
+ mmc_delay(host, 1);

mmc_host_clk_release(host);
}
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 3bdafbc..4cba1f1 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -46,9 +46,11 @@ void mmc_set_timing(struct mmc_host *host, unsigned int timing);
void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type);
void mmc_power_off(struct mmc_host *host);

-static inline void mmc_delay(unsigned int ms)
+static inline void mmc_delay(struct mmc_host *host, unsigned int ms)
{
- if (ms < 1000 / HZ) {
+ if (mmc_am_panic_task(host)) {
+ mdelay(ms);
+ } else if (ms < 1000 / HZ) {
cond_resched();
mdelay(ms);
} else {
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index a0e1720..23eaeab 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -83,7 +83,7 @@ int mmc_card_sleepawake(struct mmc_host *host, int sleep)
* others) is invalid while the card sleeps.
*/
if (!(host->caps & MMC_CAP_WAIT_WHILE_BUSY))
- mmc_delay(DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000));
+ mmc_delay(host, DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000));

if (!sleep)
err = mmc_select_card(card);
@@ -107,7 +107,7 @@ int mmc_go_idle(struct mmc_host *host)
*/
if (!mmc_host_is_spi(host)) {
mmc_set_chip_select(host, MMC_CS_HIGH);
- mmc_delay(1);
+ mmc_delay(host, 1);
}

cmd.opcode = MMC_GO_IDLE_STATE;
@@ -116,11 +116,11 @@ int mmc_go_idle(struct mmc_host *host)

err = mmc_wait_for_cmd(host, &cmd, 0);

- mmc_delay(1);
+ mmc_delay(host, 1);

if (!mmc_host_is_spi(host)) {
mmc_set_chip_select(host, MMC_CS_DONTCARE);
- mmc_delay(1);
+ mmc_delay(host, 1);
}

host->use_spi_crc = 0;
@@ -159,7 +159,7 @@ int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)

err = -ETIMEDOUT;

- mmc_delay(10);
+ mmc_delay(host, 10);
}

if (rocr && !mmc_host_is_spi(host))
diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c
index 274ef00..d4573fb 100644
--- a/drivers/mmc/core/sd_ops.c
+++ b/drivers/mmc/core/sd_ops.c
@@ -181,7 +181,7 @@ int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)

err = -ETIMEDOUT;

- mmc_delay(10);
+ mmc_delay(host, 10);
}

if (rocr && !mmc_host_is_spi(host))
diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c
index d29e206..099908c 100644
--- a/drivers/mmc/core/sdio_ops.c
+++ b/drivers/mmc/core/sdio_ops.c
@@ -56,7 +56,7 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)

err = -ETIMEDOUT;

- mmc_delay(10);
+ mmc_delay(host, 10);
}

if (rocr)
--
1.7.9.5

2012-11-08 13:09:04

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 09/26] mmc: panic write: suppress host not claimed warnings

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 13 +++++++------
drivers/mmc/core/mmc.c | 4 ++--
drivers/mmc/core/sd.c | 4 ++--
drivers/mmc/core/sdio.c | 4 ++--
drivers/mmc/core/sdio_irq.c | 4 ++--
5 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 6b2377a..0b904bc 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -224,7 +224,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
mrq->stop->arg, mrq->stop->flags);
}

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

mrq->cmd->error = 0;
mrq->cmd->mrq = mrq;
@@ -558,7 +558,7 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries
{
struct mmc_request mrq = {NULL};

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

memset(cmd->resp, 0, sizeof(cmd->resp));
cmd->retries = retries;
@@ -843,7 +843,7 @@ void mmc_release_host(struct mmc_host *host)
{
unsigned long flags;

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

if (host->ops->disable && host->claim_cnt == 1)
host->ops->disable(host);
@@ -1433,7 +1433,7 @@ void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
BUG_ON(!host);
BUG_ON(!ops);

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

spin_lock_irqsave(&host->lock, flags);

@@ -1456,7 +1456,7 @@ void mmc_detach_bus(struct mmc_host *host)

BUG_ON(!host);

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));
WARN_ON(!host->bus_ops);

spin_lock_irqsave(&host->lock, flags);
@@ -2106,7 +2106,8 @@ int mmc_detect_card_removed(struct mmc_host *host)
struct mmc_card *card = host->card;
int ret;

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));
+

if (!card)
return 1;
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 7cc4638..9cc36c4 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -823,7 +823,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
u8 *ext_csd = NULL;

BUG_ON(!host);
- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

/* Set correct bus mode for MMC before attempting init */
if (!mmc_host_is_spi(host))
@@ -1478,7 +1478,7 @@ int mmc_attach_mmc(struct mmc_host *host)
u32 ocr;

BUG_ON(!host);
- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

/* Set correct bus mode for MMC before attempting attach */
if (!mmc_host_is_spi(host))
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 74972c2..4ad079f 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -899,7 +899,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
u32 rocr = 0;

BUG_ON(!host);
- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

err = mmc_sd_get_cid(host, ocr, cid, &rocr);
if (err)
@@ -1146,7 +1146,7 @@ int mmc_attach_sd(struct mmc_host *host)
u32 ocr;

BUG_ON(!host);
- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

/* Disable preset value enable if already set since last time */
if (host->ops->enable_preset_value) {
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index 2273ce6..af89c84 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -585,7 +585,7 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
int err;

BUG_ON(!host);
- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

/*
* Inform the card of the voltage
@@ -1051,7 +1051,7 @@ int mmc_attach_sdio(struct mmc_host *host)
struct mmc_card *card;

BUG_ON(!host);
- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

err = mmc_send_io_op_cond(host, 0, &ocr);
if (err)
diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c
index 3d8ceb4..719701c 100644
--- a/drivers/mmc/core/sdio_irq.c
+++ b/drivers/mmc/core/sdio_irq.c
@@ -175,7 +175,7 @@ static int sdio_card_irq_get(struct mmc_card *card)
{
struct mmc_host *host = card->host;

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));

if (!host->sdio_irqs++) {
atomic_set(&host->sdio_irq_thread_abort, 0);
@@ -196,7 +196,7 @@ static int sdio_card_irq_put(struct mmc_card *card)
{
struct mmc_host *host = card->host;

- WARN_ON(!host->claimed);
+ WARN_ON(!host->claimed && !mmc_am_panic_task(host));
BUG_ON(host->sdio_irqs < 1);

if (!--host->sdio_irqs) {
--
1.7.9.5

2012-11-08 13:03:55

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 05/26] block: add panic write

From: Adrian Hunter <[email protected]>

Add a small interface to allow block devices to attempt to write
during an oops or panic.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/block/Kconfig | 3 ++
include/linux/blkdev.h | 77 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/genhd.h | 3 ++
3 files changed, 83 insertions(+)

diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index af5b325..922cfd6 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -554,4 +554,7 @@ config BLK_DEV_OOPS

See <file:Documentation/blockdev/blkoops.txt> for more information.

+config BLK_DEV_PANIC_WRITE
+ bool
+
endif # BLK_DEV
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 1756001..bedfaab 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1461,6 +1461,83 @@ static inline bool blk_integrity_is_initialized(struct gendisk *g)

#endif /* CONFIG_BLK_DEV_INTEGRITY */

+#ifdef CONFIG_BLK_DEV_PANIC_WRITE
+
+/**
+ * struct panic_write_operations - operations to support writing during an oops
+ * or panic.
+ * @init: allocate resources
+ * @cleanup: release resources
+ * @write: called in an oops or panic context to write data
+ * @flush: called in an oops or panic context to finish write operations
+ *
+ * @write may need resources that cannot be allocated in a panic or oops
+ * context. @init can be used to allocate those resources in advance.
+ * @cleanup is called if panic writing becomes disabled.
+ */
+struct panic_write_operations {
+ int (*init)(struct block_device *);
+ void (*cleanup)(struct block_device *);
+ int (*write)(struct block_device *, sector_t, void *, unsigned long);
+ int (*flush)(struct block_device *);
+};
+
+static inline int blk_panic_init(struct block_device *bdev)
+{
+ if (!bdev->bd_disk->pwops || !bdev->bd_disk->pwops->write)
+ return -EOPNOTSUPP;
+ if (bdev->bd_disk->pwops && bdev->bd_disk->pwops->init)
+ return bdev->bd_disk->pwops->init(bdev);
+ return 0;
+}
+
+static inline void blk_panic_cleanup(struct block_device *bdev)
+{
+ if (bdev->bd_disk->pwops && bdev->bd_disk->pwops->cleanup)
+ bdev->bd_disk->pwops->cleanup(bdev);
+}
+
+static inline int blk_panic_write(struct block_device *bdev, sector_t sect,
+ void *addr, unsigned long len)
+{
+ if (!bdev->bd_disk->pwops || !bdev->bd_disk->pwops->write)
+ return -EOPNOTSUPP;
+ if (bdev != bdev->bd_contains)
+ sect += bdev->bd_part->start_sect;
+ return bdev->bd_disk->pwops->write(bdev, sect, addr, len);
+}
+
+static inline int blk_panic_flush(struct block_device *bdev)
+{
+ if (bdev->bd_disk->pwops && bdev->bd_disk->pwops->flush)
+ return bdev->bd_disk->pwops->flush(bdev);
+ return 0;
+}
+
+#else
+
+static inline int blk_panic_init(struct block_device *bdev)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void blk_panic_cleanup(struct block_device *bdev)
+{
+}
+
+static inline int blk_panic_write(struct block_device *bdev, sector_t sect,
+ void *addr, unsigned long len)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int blk_panic_flush(struct block_device *bdev)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif /* CONFIG_BLK_DEV_PANIC_WRITE */
+
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
diff --git a/include/linux/genhd.h b/include/linux/genhd.h
index 4f440b3..73470af 100644
--- a/include/linux/genhd.h
+++ b/include/linux/genhd.h
@@ -195,6 +195,9 @@ struct gendisk {
#ifdef CONFIG_BLK_DEV_INTEGRITY
struct blk_integrity *integrity;
#endif
+#ifdef CONFIG_BLK_DEV_PANIC_WRITE
+ const struct panic_write_operations *pwops;
+#endif
int node_id;
};

--
1.7.9.5

2012-11-08 13:09:35

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 08/26] mmc: panic write: bypass request completion

From: Adrian Hunter <[email protected]>

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index dbe5332..6b2377a 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -148,6 +148,9 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
cmd->retries = 0;
}

+ if (mmc_am_panic_task(host))
+ return;
+
if (err && cmd->retries && !mmc_card_removed(host->card)) {
/*
* Request starter must handle retries - see
@@ -340,7 +343,9 @@ static void mmc_wait_for_req_done(struct mmc_host *host,
struct mmc_command *cmd;

while (1) {
- wait_for_completion(&mrq->completion);
+ /* Panic task requests must be completed in ->request() */
+ if (!mmc_am_panic_task(host))
+ wait_for_completion(&mrq->completion);

cmd = mrq->cmd;
if (!cmd->error || !cmd->retries ||
--
1.7.9.5

2012-11-08 13:09:50

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 07/26] mmc: panic write: bypass host claiming

From: Adrian Hunter <[email protected]>

We don't want to claim/release host during a panic dump.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/core/core.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 06c42cf..dbe5332 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -767,6 +767,9 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
unsigned long flags;
int stop;

+ if (mmc_am_panic_task(host))
+ return 0;
+
might_sleep();

add_wait_queue(&host->wq, &wait);
@@ -807,6 +810,9 @@ int mmc_try_claim_host(struct mmc_host *host)
int claimed_host = 0;
unsigned long flags;

+ if (mmc_am_panic_task(host))
+ return 1;
+
spin_lock_irqsave(&host->lock, flags);
if (!host->claimed || host->claimer == current) {
host->claimed = 1;
@@ -837,6 +843,9 @@ void mmc_release_host(struct mmc_host *host)
if (host->ops->disable && host->claim_cnt == 1)
host->ops->disable(host);

+ if (mmc_am_panic_task(host))
+ return;
+
spin_lock_irqsave(&host->lock, flags);
if (--host->claim_cnt) {
/* Release for nested claim */
--
1.7.9.5

2012-11-08 13:10:16

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 06/26] mmc: block: add panic write support

From: Adrian Hunter <[email protected]>

Define panic_write_operations for mmc block devices.
Also define host mmc panic ops for controling panic dumping.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/mmc/card/Kconfig | 11 ++
drivers/mmc/card/block.c | 257 +++++++++++++++++++++++++++++++++++++++++++++-
include/linux/mmc/host.h | 92 +++++++++++++++++
3 files changed, 359 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
index 3b1f783..06c09a3 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -50,6 +50,17 @@ config MMC_BLOCK_BOUNCE

If unsure, say Y here.

+config MMC_BLOCK_PANIC_WRITE
+ bool "Panic write support"
+ depends on MMC_BLOCK
+ select BLK_DEV_PANIC_WRITE
+ default n
+ help
+ Say Y here to support panic write. Panic write enables upper
+ layers to write to MMC/SD cards during an oops or panic.
+ However a host controller driver that supports panic writes
+ is also needed. Panic writes are defined by mmc_panic_ops.
+
config SDIO_UART
tristate "SDIO UART/GPS class support"
help
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index 172a768..e18ce4a 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -108,6 +108,11 @@ struct mmc_blk_data {
struct device_attribute force_ro;
struct device_attribute power_ro_lock;
int area_type;
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ int panic;
+ struct scatterlist *panic_sg;
+ struct mmc_blk_request *panic_brq;
+#endif
};

static DEFINE_MUTEX(open_lock);
@@ -517,7 +522,7 @@ static const struct block_device_operations mmc_bdops = {
#endif
};

-static inline int mmc_blk_part_switch(struct mmc_card *card,
+static int mmc_blk_part_switch(struct mmc_card *card,
struct mmc_blk_data *md)
{
int ret;
@@ -1432,6 +1437,253 @@ out:
return ret;
}

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+static int mmc_blk_panic_init_cleanup(struct block_device *bdev, int init)
+{
+ struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk);
+ struct mmc_card *card;
+ int err = 0;
+
+ if (!md)
+ return -ENODEV;
+
+ card = md->queue.card;
+ if (!card) {
+ err = -ENODEV;
+ goto out_put;
+ }
+
+ mmc_claim_host(card->host);
+
+ if (!init) {
+ mmc_panic_cleanup_host(card->host);
+ goto out_free_brq;
+ }
+
+ md->panic_sg = kmalloc(sizeof(struct scatterlist), GFP_KERNEL);
+ if (!md->panic_sg) {
+ err = -ENOMEM;
+ goto out_release;
+ }
+ sg_init_table(md->panic_sg, 1);
+
+ md->panic_brq = kmalloc(sizeof(struct mmc_blk_request), GFP_KERNEL);
+ if (!md->panic_brq) {
+ err = -ENOMEM;
+ goto out_free_sg;
+ }
+
+ err = mmc_panic_init_host(card->host);
+ if (err)
+ goto out_free_brq;
+
+ goto out_release;
+
+out_free_brq:
+ kfree(md->panic_brq);
+out_free_sg:
+ kfree(md->panic_sg);
+out_release:
+ mmc_release_host(card->host);
+out_put:
+ mmc_blk_put(md);
+ return err;
+}
+
+static int mmc_blk_panic_init(struct block_device *bdev)
+{
+ return mmc_blk_panic_init_cleanup(bdev, 1);
+}
+
+static void mmc_blk_panic_cleanup(struct block_device *bdev)
+{
+ mmc_blk_panic_init_cleanup(bdev, 0);
+}
+
+static int get_card_status(struct mmc_card *card, u32 *status, int retries);
+
+static int mmc_blk_panic_do_write(struct mmc_blk_data *md,
+ struct mmc_card *card, sector_t sect,
+ void *addr, unsigned long len)
+{
+ struct mmc_blk_request *brq = md->panic_brq;
+ unsigned int blocks = len >> 9;
+ unsigned int blksz = 512;
+
+ int err = 0;
+
+ sg_init_one(md->panic_sg, addr, len);
+
+ memset(brq, 0, sizeof(struct mmc_blk_request));
+
+ brq->mrq.cmd = &brq->cmd;
+ brq->mrq.data = &brq->data;
+ brq->mrq.stop = &brq->stop;
+
+ if (blocks > 1)
+ brq->mrq.cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
+ else
+ brq->mrq.cmd->opcode = MMC_WRITE_BLOCK;
+
+ brq->mrq.cmd->arg = sect;
+ if (!mmc_card_blockaddr(card))
+ brq->mrq.cmd->arg <<= 9;
+
+ brq->mrq.cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ if (blocks == 1)
+ brq->mrq.stop = NULL;
+ else {
+ brq->mrq.stop->opcode = MMC_STOP_TRANSMISSION;
+ brq->mrq.stop->arg = 0;
+ brq->mrq.stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
+ }
+
+ brq->mrq.data->blksz = blksz;
+ brq->mrq.data->blocks = blocks;
+ brq->mrq.data->flags = MMC_DATA_WRITE;
+ brq->mrq.data->sg = md->panic_sg;
+ brq->mrq.data->sg_len = 1;
+
+ mmc_set_data_timeout(brq->mrq.data, card);
+
+ mmc_wait_for_req(card->host, &brq->mrq);
+
+ if (brq->cmd.error)
+ err = brq->cmd.error;
+ else if (brq->stop.error)
+ err = brq->stop.error;
+ else if (brq->data.error)
+ err = brq->data.error;
+
+ if (!mmc_host_is_spi(card->host)) {
+ u32 status;
+
+ do {
+ err = get_card_status(card, &status, 5);
+ if (err)
+ break;
+ } while (!(status & R1_READY_FOR_DATA) ||
+ (R1_CURRENT_STATE(status) == R1_STATE_PRG));
+ }
+
+ return err;
+}
+
+static int mmc_blk_panic_reset(struct mmc_blk_data *md, struct mmc_host *host)
+{
+ struct mmc_blk_data *main_md = mmc_get_drvdata(host->card);
+ int err;
+
+ err = mmc_blk_reset(md, host, 0);
+ if (err != -EOPNOTSUPP)
+ goto out;
+
+ /* No hardware reset so try a software reset */
+ mmc_power_save_host(host);
+ mmc_power_restore_host(host);
+out:
+ /* Partition may have changed, force a switch */
+ main_md->part_curr = -1;
+
+ return err;
+}
+
+/*
+ * Tuning while panicing is not supported, so disable speeds that require it
+ * and reset.
+ */
+static int mmc_blk_panic_no_tuning(struct mmc_blk_data *md,
+ struct mmc_host *host)
+{
+ if (host->ocr & SD_OCR_S18R) {
+ host->caps &= ~(MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+ MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 |
+ MMC_CAP_UHS_DDR50);
+ host->ocr &= ~SD_OCR_S18R;
+ return mmc_blk_panic_reset(md, host);
+ }
+ return 0;
+}
+
+static int mmc_blk_panic_write(struct block_device *bdev, sector_t sect,
+ void *addr, unsigned long len)
+{
+ struct mmc_blk_data *md = bdev->bd_disk->private_data;
+ struct mmc_card *card;
+ size_t n;
+ int err = 0, err2;
+
+ if (!md)
+ return -ENODEV;
+ card = md->queue.card;
+ if (!card)
+ return -ENODEV;
+
+ card->host->panic_task = current;
+
+ if (!md->panic) {
+ u32 status;
+
+ md->panic = 1;
+ mmc_panic_begin_host(card->host);
+ mmc_blk_panic_no_tuning(md, card->host);
+ err2 = get_card_status(card, &status, 0);
+ if (err2 || !(status & R1_READY_FOR_DATA) ||
+ R1_CURRENT_STATE(status) != R1_STATE_TRAN)
+ mmc_blk_panic_reset(md, card->host);
+ mmc_set_blocklen(card, 512);
+ }
+
+ err = mmc_blk_part_switch(card, md);
+ if (err)
+ return err;
+
+ while (len) {
+ n = min_t(size_t, card->host->panic_max_size, len);
+ err2 = mmc_blk_panic_do_write(md, card, sect, addr, n);
+ if (err2 && !err)
+ err = err2;
+ len -= n;
+ addr += n;
+ sect += n >> 9;
+ }
+
+ return err;
+}
+
+static int mmc_blk_panic_flush(struct block_device *bdev)
+{
+ struct mmc_blk_data *md = bdev->bd_disk->private_data;
+ struct mmc_card *card;
+
+ if (!md)
+ return -ENODEV;
+ card = md->queue.card;
+ if (!card)
+ return -ENODEV;
+
+ if (md->panic) {
+ card->host->panic_task = current;
+ mmc_panic_end_host(card->host);
+ md->panic = 0;
+ }
+
+ card->host->panic_task = NULL;
+
+ return 0;
+}
+
+static const struct panic_write_operations mmc_pwops = {
+ .init = mmc_blk_panic_init,
+ .cleanup = mmc_blk_panic_cleanup,
+ .write = mmc_blk_panic_write,
+ .flush = mmc_blk_panic_flush,
+};
+
+#endif
+
static inline int mmc_blk_readonly(struct mmc_card *card)
{
return mmc_card_readonly(card) ||
@@ -1500,6 +1752,9 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
md->disk->major = MMC_BLOCK_MAJOR;
md->disk->first_minor = devidx * perdev_minors;
md->disk->fops = &mmc_bdops;
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ md->disk->pwops = &mmc_pwops;
+#endif
md->disk->private_data = md;
md->disk->queue = md->queue.queue;
md->disk->driverfs_dev = parent;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 7abb0e1..4205d2d 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -13,6 +13,7 @@
#include <linux/leds.h>
#include <linux/mutex.h>
#include <linux/sched.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fault-inject.h>

@@ -138,6 +139,17 @@ struct mmc_host_ops {
void (*hw_reset)(struct mmc_host *host);
};

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+struct mmc_panic_ops {
+ int (*init)(struct mmc_host *host);
+ void (*cleanup)(struct mmc_host *host);
+ void (*begin)(struct mmc_host *host);
+ void (*end)(struct mmc_host *host);
+};
+
+#endif
+
struct mmc_card;
struct device;

@@ -257,6 +269,7 @@ struct mmc_host {
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
+#define MMC_CAP_PANIC_WRITE (1 << 12) /* Panic write support */

mmc_pm_flag_t pm_caps; /* supported pm features */

@@ -337,6 +350,12 @@ struct mmc_host {

unsigned int actual_clock; /* Actual HC clock rate */

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ struct task_struct *panic_task; /* task that is panic writing */
+ const struct mmc_panic_ops *pops; /* panic operations */
+ size_t panic_max_size; /* max data transfer size */
+#endif
+
unsigned long private[0] ____cacheline_aligned;
};

@@ -452,4 +471,77 @@ static inline unsigned int mmc_host_clk_rate(struct mmc_host *host)
return host->ios.clock;
}
#endif
+
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+static inline int mmc_panic_task_active(struct mmc_host *host)
+{
+ return host->panic_task != NULL;
+}
+
+static inline int mmc_am_panic_task(struct mmc_host *host)
+{
+ return host->panic_task == current;
+}
+
+static inline int mmc_am_nonpanic_task(struct mmc_host *host)
+{
+ return host->panic_task && host->panic_task != current;
+}
+
+static inline void mmc_trap_nonpanic_tasks(struct mmc_host *host)
+{
+ while (host->panic_task && host->panic_task != current)
+ msleep(100);
+}
+
+static inline int mmc_panic_init_host(struct mmc_host *host)
+{
+ if (!(host->caps2 & MMC_CAP_PANIC_WRITE))
+ return -EOPNOTSUPP;
+ if (host->pops->init)
+ return host->pops->init(host);
+ return 0;
+}
+
+static inline void mmc_panic_cleanup_host(struct mmc_host *host)
+{
+ if (host->pops->cleanup)
+ host->pops->cleanup(host);
+}
+
+static inline void mmc_panic_begin_host(struct mmc_host *host)
+{
+if (host->pops->begin)
+ host->pops->begin(host);
+}
+
+static inline void mmc_panic_end_host(struct mmc_host *host)
+{
+ if (host->pops->end)
+ host->pops->end(host);
+}
+
+#else
+
+static inline int mmc_panic_task_active(struct mmc_host *host)
+{
+ return 0;
+}
+
+static inline int mmc_am_panic_task(struct mmc_host *host)
+{
+ return 0;
+}
+
+static inline int mmc_am_nonpanic_task(struct mmc_host *host)
+{
+ return 0;
+}
+
+static inline void mmc_trap_nonpanic_tasks(struct mmc_host *host)
+{
+}
+
+#endif
#endif /* LINUX_MMC_HOST_H */
--
1.7.9.5

2012-11-08 13:03:46

by Tatulea, Dragos

[permalink] [raw]
Subject: [PATCH v2 01/26] pstore: allow for big files

From: Adrian Hunter <[email protected]>

pstore reads all files into memory at mount time. To allow for back ends
that will store arbitrarily large files, enhance pstore also to be able
to read from the back end as needed.

Signed-off-by: Adrian Hunter <[email protected]>
Signed-off-by: Irina Tirdea <[email protected]>
---
drivers/acpi/apei/erst.c | 16 +++++++++-------
fs/pstore/inode.c | 26 ++++++++++++++++++++------
fs/pstore/internal.h | 5 +++--
fs/pstore/platform.c | 11 +++++++----
fs/pstore/ram.c | 15 +++++++--------
include/linux/pstore.h | 7 +++++--
6 files changed, 51 insertions(+), 29 deletions(-)

diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c
index e4d9d24..f70ba37 100644
--- a/drivers/acpi/apei/erst.c
+++ b/drivers/acpi/apei/erst.c
@@ -931,9 +931,9 @@ static int erst_check_table(struct acpi_table_erst *erst_tab)

static int erst_open_pstore(struct pstore_info *psi);
static int erst_close_pstore(struct pstore_info *psi);
-static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
- struct timespec *time, char **buf,
- struct pstore_info *psi);
+static int erst_reader(u64 *id, enum pstore_type_id *type,
+ struct timespec *time, char **buf, loff_t *size,
+ struct pstore_info *psi);
static int erst_writer(enum pstore_type_id type, enum kmsg_dump_reason reason,
u64 *id, unsigned int part,
size_t size, struct pstore_info *psi);
@@ -987,9 +987,9 @@ static int erst_close_pstore(struct pstore_info *psi)
return 0;
}

-static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
- struct timespec *time, char **buf,
- struct pstore_info *psi)
+static int erst_reader(u64 *id, enum pstore_type_id *type,
+ struct timespec *time, char **buf, loff_t *size,
+ struct pstore_info *psi)
{
int rc;
ssize_t len = 0;
@@ -1051,7 +1051,9 @@ skip:

out:
kfree(rcd);
- return (rc < 0) ? rc : (len - sizeof(*rcd));
+ if (!rc)
+ *size = len - sizeof(*rcd);
+ return rc;
}

static int erst_writer(enum pstore_type_id type, enum kmsg_dump_reason reason,
diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c
index 4ab572e..ff0970f 100644
--- a/fs/pstore/inode.c
+++ b/fs/pstore/inode.c
@@ -49,7 +49,8 @@ struct pstore_private {
struct pstore_info *psi;
enum pstore_type_id type;
u64 id;
- ssize_t size;
+ loff_t size;
+ bool big;
char data[];
};

@@ -65,12 +66,13 @@ static void *pstore_ftrace_seq_start(struct seq_file *s, loff_t *pos)
{
struct pstore_private *ps = s->private;
struct pstore_ftrace_seq_data *data;
+ loff_t size = ps->size;

data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;

- data->off = ps->size % REC_SIZE;
+ data->off = do_div(size, REC_SIZE);
data->off += *pos * REC_SIZE;
if (data->off + REC_SIZE > ps->size) {
kfree(data);
@@ -127,6 +129,12 @@ static ssize_t pstore_file_read(struct file *file, char __user *userbuf,

if (ps->type == PSTORE_TYPE_FTRACE)
return seq_read(file, userbuf, count, ppos);
+ if (ps->big) {
+ if (ps->psi->file_read)
+ return ps->psi->file_read(ps->id, ps->type, userbuf,
+ count, ppos, ps->psi);
+ return -EFBIG;
+ }
return simple_read_from_buffer(userbuf, count, ppos, ps->data, ps->size);
}

@@ -271,8 +279,8 @@ int pstore_is_mounted(void)
* Set the mtime & ctime to the date that this record was originally stored.
*/
int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id,
- char *data, size_t size, struct timespec time,
- struct pstore_info *psi)
+ char *data, loff_t size, struct timespec time,
+ struct pstore_info *psi, bool big)
{
struct dentry *root = pstore_sb->s_root;
struct dentry *dentry;
@@ -281,6 +289,7 @@ int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id,
char name[PSTORE_NAMELEN];
struct pstore_private *private, *pos;
unsigned long flags;
+ size_t inline_size;

spin_lock_irqsave(&allpstore_lock, flags);
list_for_each_entry(pos, &allpstore, list) {
@@ -301,12 +310,17 @@ int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id,
goto fail;
inode->i_mode = S_IFREG | 0444;
inode->i_fop = &pstore_file_operations;
- private = kmalloc(sizeof *private + size, GFP_KERNEL);
+ if (big)
+ inline_size = 0;
+ else
+ inline_size = size;
+ private = kmalloc(sizeof(*private) + inline_size, GFP_KERNEL);
if (!private)
goto fail_alloc;
private->type = type;
private->id = id;
private->psi = psi;
+ private->big = big;

switch (type) {
case PSTORE_TYPE_DMESG:
@@ -336,7 +350,7 @@ int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id,
if (IS_ERR(dentry))
goto fail_lockedalloc;

- memcpy(private->data, data, size);
+ memcpy(private->data, data, inline_size);
inode->i_size = private->size = size;

inode->i_private = private;
diff --git a/fs/pstore/internal.h b/fs/pstore/internal.h
index 4847f58..f840159 100644
--- a/fs/pstore/internal.h
+++ b/fs/pstore/internal.h
@@ -50,8 +50,9 @@ extern struct pstore_info *psinfo;
extern void pstore_set_kmsg_bytes(int);
extern void pstore_get_records(int);
extern int pstore_mkfile(enum pstore_type_id, char *psname, u64 id,
- char *data, size_t size,
- struct timespec time, struct pstore_info *psi);
+ char *data, loff_t size,
+ struct timespec time, struct pstore_info *psi,
+ bool big);
extern int pstore_is_mounted(void);

#endif
diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c
index a40da07..108bd69 100644
--- a/fs/pstore/platform.c
+++ b/fs/pstore/platform.c
@@ -264,7 +264,7 @@ void pstore_get_records(int quiet)
{
struct pstore_info *psi = psinfo;
char *buf = NULL;
- ssize_t size;
+ loff_t size;
u64 id;
enum pstore_type_id type;
struct timespec time;
@@ -277,9 +277,12 @@ void pstore_get_records(int quiet)
if (psi->open && psi->open(psi))
goto out;

- while ((size = psi->read(&id, &type, &time, &buf, psi)) > 0) {
- rc = pstore_mkfile(type, psi->name, id, buf, (size_t)size,
- time, psi);
+ while (1) {
+ rc = psi->read(&id, &type, &time, &buf, &size, psi);
+ if (rc && rc != -EFBIG)
+ break;
+ rc = pstore_mkfile(type, psi->name, id, buf, size,
+ time, psi, rc == -EFBIG);
kfree(buf);
buf = NULL;
if (rc && (rc != -EEXIST || !quiet))
diff --git a/fs/pstore/ram.c b/fs/pstore/ram.c
index 1a4f6da..dd47a87 100644
--- a/fs/pstore/ram.c
+++ b/fs/pstore/ram.c
@@ -131,12 +131,11 @@ ramoops_get_next_prz(struct persistent_ram_zone *przs[], uint *c, uint max,
return prz;
}

-static ssize_t ramoops_pstore_read(u64 *id, enum pstore_type_id *type,
+static int ramoops_pstore_read(u64 *id, enum pstore_type_id *type,
struct timespec *time,
- char **buf,
+ char **buf, loff_t *size,
struct pstore_info *psi)
{
- ssize_t size;
struct ramoops_context *cxt = psi->data;
struct persistent_ram_zone *prz;

@@ -150,19 +149,19 @@ static ssize_t ramoops_pstore_read(u64 *id, enum pstore_type_id *type,
prz = ramoops_get_next_prz(&cxt->fprz, &cxt->ftrace_read_cnt,
1, id, type, PSTORE_TYPE_FTRACE, 0);
if (!prz)
- return 0;
+ return -EINVAL;

/* TODO(kees): Bogus time for the moment. */
time->tv_sec = 0;
time->tv_nsec = 0;

- size = persistent_ram_old_size(prz);
- *buf = kmalloc(size, GFP_KERNEL);
+ *size = persistent_ram_old_size(prz);
+ *buf = kmalloc(*size, GFP_KERNEL);
if (*buf == NULL)
return -ENOMEM;
- memcpy(*buf, persistent_ram_old(prz), size);
+ memcpy(*buf, persistent_ram_old(prz), *size);

- return size;
+ return 0;
}

static size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz)
diff --git a/include/linux/pstore.h b/include/linux/pstore.h
index ee3034a..3a293ff 100644
--- a/include/linux/pstore.h
+++ b/include/linux/pstore.h
@@ -49,8 +49,11 @@ struct pstore_info {
struct mutex read_mutex; /* serialize open/read/close */
int (*open)(struct pstore_info *psi);
int (*close)(struct pstore_info *psi);
- ssize_t (*read)(u64 *id, enum pstore_type_id *type,
- struct timespec *time, char **buf,
+ int (*read)(u64 *id, enum pstore_type_id *type,
+ struct timespec *time, char **buf, loff_t *size,
+ struct pstore_info *psi);
+ ssize_t (*file_read)(u64 id, enum pstore_type_id type,
+ char __user *userbuf, size_t count, loff_t *ppos,
struct pstore_info *psi);
int (*write)(enum pstore_type_id type,
enum kmsg_dump_reason reason, u64 *id,
--
1.7.9.5