* [PATCH 0/4][Diskdump]Update patches
@ 2004-06-16 12:39 Takao Indoh
2004-06-16 12:45 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh
` (4 more replies)
0 siblings, 5 replies; 9+ messages in thread
From: Takao Indoh @ 2004-06-16 12:39 UTC (permalink / raw)
To: linux-kernel
Hi!
I fixed diskdump patches except timer problem.
- Fix some codes which Arjan van de Ven pointed out
- Replace dev_t with block_device
Source code of tool(diskdumptuils) can be downloaded from
http://sourceforge.net/projects/lkdump
Regarding timer problem, please see previous mail I sent.
http://marc.theaimsgroup.com/?l=linux-kernel&m=108722344204595&w=2
In short, Timer problem is as follows.
> What is a problem? Scsi driver uses timer/tasklet to complete I/O.
> But, diskdump disables interrupt, so timer/taslket doesn't work!
There are three ways to solve this problem so far.
(1) Redefine timer/taslket routines. This method was adopted in the
first patch.
+#if defined(CONFIG_DISKDUMP) || defined(CONFIG_DISKDUMP_MODULE)
+#undef add_timer
+#define add_timer diskdump_add_timer
+#undef del_timer_sync
+#define del_timer_sync diskdump_del_timer
+#undef del_timer
+#define del_timer diskdump_del_timer
+#undef mod_timer
+#define mod_timer diskdump_mod_timer
+
+#define tasklet_schedule diskdump_tasklet_schedule
+#endif
(2) Add new code into the timer/taslket routines themselves.
static inline void add_timer(struct timer_list * timer)
{
- __mod_timer(timer, timer->expires);
+ if(crashdump_mode())
+ diskdump_add_timer(timer);
+ else
+ __mod_timer(timer, timer->expires);
}
int del_timer_sync(struct timer_list *timer)
{
tvec_base_t *base;
int i, ret = 0;
+ if(crashdump_mode()) {
+ diskdump_del_timer(timer);
+ return 0;
+ }
+
check_timer(timer);
int mod_timer(struct timer_list *timer, unsigned long expires)
{
BUG_ON(!timer->function);
+ if(crashdump_mode()) {
+ diskdump_mod_timer(timer, expires);
+ return 0;
+ }
+
check_timer(timer);
static inline void tasklet_schedule(struct tasklet_struct *t)
{
+ if(crashdump_mode()) {
+ diskdump_tasklet_schedule(t);
+ return;
+ }
+
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
(3) Change polling handler of driver not to use timer/tasklet.
The method (1) was already rejected because of its ugliness. The
method (3) needs many extra codes and makes handler of driver too big
and complex. I think the method (2) is the simplest though it make
common codes dirty.
Please feel free to comment.
Best Regards,
Takao Indoh
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 1/4][Diskdump]Update patches 2004-06-16 12:39 [PATCH 0/4][Diskdump]Update patches Takao Indoh @ 2004-06-16 12:45 ` Takao Indoh 2004-06-16 12:46 ` [PATCH 2/4][Diskdump]Update patches Takao Indoh ` (3 subsequent siblings) 4 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-06-16 12:45 UTC (permalink / raw) To: linux-kernel This is a patch for diskdump common layer. diff -Nur linux-2.6.6.org/arch/i386/kernel/nmi.c linux-2.6.6/arch/i386/kernel/nmi.c --- linux-2.6.6.org/arch/i386/kernel/nmi.c 2004-06-04 21:21:51.000000000 +0900 +++ linux-2.6.6/arch/i386/kernel/nmi.c 2004-06-16 19:34:16.000000000 +0900 @@ -517,3 +517,4 @@ EXPORT_SYMBOL(release_lapic_nmi); EXPORT_SYMBOL(disable_timer_nmi_watchdog); EXPORT_SYMBOL(enable_timer_nmi_watchdog); +EXPORT_SYMBOL(touch_nmi_watchdog); diff -Nur linux-2.6.6.org/arch/i386/kernel/reboot.c linux-2.6.6/arch/i386/kernel/reboot.c --- linux-2.6.6.org/arch/i386/kernel/reboot.c 2004-06-04 21:21:51.000000000 +0900 +++ linux-2.6.6/arch/i386/kernel/reboot.c 2004-06-16 19:34:16.000000000 +0900 @@ -252,7 +252,8 @@ * Stop all CPUs and turn off local APICs and the IO-APIC, so * other OSs see a clean IRQ state. */ - smp_send_stop(); + if (!crashdump_mode()) + smp_send_stop(); #elif defined(CONFIG_X86_LOCAL_APIC) if (cpu_has_apic) { local_irq_disable(); diff -Nur linux-2.6.6.org/arch/i386/kernel/traps.c linux-2.6.6/arch/i386/kernel/traps.c --- linux-2.6.6.org/arch/i386/kernel/traps.c 2004-06-04 21:21:51.000000000 +0900 +++ linux-2.6.6/arch/i386/kernel/traps.c 2004-06-16 19:34:16.000000000 +0900 @@ -258,7 +258,8 @@ int nl = 0; console_verbose(); - spin_lock_irq(&die_lock); + if (!crashdump_mode()) + spin_lock_irq(&die_lock); bust_spinlocks(1); handle_BUG(regs); printk("%s: %04lx [#%d]\n", str, err & 0xffff, ++die_counter); @@ -277,6 +278,7 @@ if (nl) printk("\n"); show_registers(regs); + try_crashdump(regs); bust_spinlocks(0); spin_unlock_irq(&die_lock); if (in_interrupt()) diff -Nur linux-2.6.6.org/arch/i386/mm/init.c linux-2.6.6/arch/i386/mm/init.c --- linux-2.6.6.org/arch/i386/mm/init.c 2004-06-04 21:21:52.000000000 +0900 +++ linux-2.6.6/arch/i386/mm/init.c 2004-06-16 19:34:16.000000000 +0900 @@ -168,7 +168,7 @@ extern int is_available_memory(efi_memory_desc_t *); -static inline int page_is_ram(unsigned long pagenr) +int page_is_ram(unsigned long pagenr) { int i; unsigned long addr, end; @@ -205,6 +205,7 @@ } return 0; } +EXPORT_SYMBOL(page_is_ram); #ifdef CONFIG_HIGHMEM pte_t *kmap_pte; diff -Nur linux-2.6.6.org/drivers/block/Kconfig linux-2.6.6/drivers/block/Kconfig --- linux-2.6.6.org/drivers/block/Kconfig 2004-06-04 21:22:15.000000000 +0900 +++ linux-2.6.6/drivers/block/Kconfig 2004-06-16 19:34:16.000000000 +0900 @@ -346,6 +346,11 @@ your machine, or if you want to have a raid or loopback device bigger than 2TB. Otherwise say N. +config DISKDUMP + tristate "Disk dump support" + ---help--- + Disk dump support. + source "drivers/s390/block/Kconfig" endmenu diff -Nur linux-2.6.6.org/drivers/block/Makefile linux-2.6.6/drivers/block/Makefile --- linux-2.6.6.org/drivers/block/Makefile 2004-06-04 21:22:15.000000000 +0900 +++ linux-2.6.6/drivers/block/Makefile 2004-06-16 19:34:16.000000000 +0900 @@ -43,3 +43,4 @@ obj-$(CONFIG_VIODASD) += viodasd.o obj-$(CONFIG_BLK_DEV_CARMEL) += carmel.o +obj-$(CONFIG_DISKDUMP) += diskdump.o diskdumplib.o diff -Nur linux-2.6.6.org/drivers/block/diskdump.c linux-2.6.6/drivers/block/diskdump.c --- linux-2.6.6.org/drivers/block/diskdump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/drivers/block/diskdump.c 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,973 @@ +/* + * linux/drivers/block/diskdump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Copyright (C) 2002 Red Hat, Inc. + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + * Some codes were derived from netdump and copyright belongs to + * Red Hat, Inc. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/file.h> +#include <linux/reboot.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/highmem.h> +#include <linux/utsname.h> +#include <linux/console.h> +#include <linux/smp_lock.h> +#include <linux/nmi.h> +#include <linux/genhd.h> +#include <linux/crc32.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/diskdump.h> +#include <linux/diskdumplib.h> +#include <asm/diskdump.h> + +#define Dbg(x, ...) pr_debug("disk_dump: " x "\n", ## __VA_ARGS__) +#define Err(x, ...) pr_err ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Warn(x, ...) pr_warn ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Info(x, ...) pr_info ("disk_dump: " x "\n", ## __VA_ARGS__) + +#ifndef TRUE +#define TRUE !0 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define ROUNDUP(x, y) (((x) + ((y)-1))/(y)) + +/* 512byte sectors to blocks */ +#define SECTOR_BLOCK(s) ((s) >> (DUMP_BLOCK_SHIFT - 9)) + +static unsigned int fallback_on_err = 1; +static unsigned int allow_risky_dumps = 1; +static unsigned int block_order = 2; +static int sample_rate = 8; +module_param(fallback_on_err, uint, 0); +module_param(allow_risky_dumps, uint, 0); +module_param(block_order, uint, 0); +module_param(sample_rate, int, 0); + +static unsigned long timestamp_1sec; +static uint32_t module_crc; +static char *scratch; +static struct disk_dump_header dump_header; +static struct disk_dump_sub_header dump_sub_header; + +/* Registered dump devices */ +static LIST_HEAD(disk_dump_devices); + +/* Registered dump types, e.g. SCSI, ... */ +static LIST_HEAD(disk_dump_types); + +static spinlock_t disk_dump_lock = SPIN_LOCK_UNLOCKED; + +static unsigned int header_blocks; /* The size of all headers */ +static unsigned int bitmap_blocks; /* The size of bitmap header */ +static unsigned int total_ram_blocks; /* The size of memory */ +static unsigned int total_blocks; /* The sum of above */ + +struct notifier_block *disk_dump_notifier_list; + +extern int panic_timeout; +extern unsigned long max_pfn; + +#if CONFIG_SMP +static void freeze_cpu(void *dummy) +{ + unsigned int cpu = smp_processor_id(); + + dump_header.tasks[cpu] = current; + + platform_freeze_cpu(); +} +#endif + +static int lapse = 0; /* 200msec unit */ + +static inline unsigned long eta(unsigned long nr, unsigned long maxnr) +{ + unsigned long long eta; + + eta = ((maxnr << 8) / nr) * (unsigned long long)lapse; + + return (unsigned long)(eta >> 8) - lapse; +} + +static inline void print_status(unsigned int nr, unsigned int maxnr) +{ + static char *spinner = "/|\\-"; + static unsigned long long prev_timestamp = 0; + unsigned long long timestamp; + + platform_timestamp(timestamp); + + if (timestamp - prev_timestamp > (timestamp_1sec/5)) { + prev_timestamp = timestamp; + lapse++; + printk("%u/%u %lu ETA %c \r", + nr, maxnr, eta(nr, maxnr) / 5, spinner[lapse & 3]); + } +} + +static inline void clear_status(int nr, int maxnr) +{ + printk(" \r"); + lapse = 0; +} + +/* + * Checking the signature on a block. The format is as follows. + * + * 1st word = 'disk' + * 2nd word = 'dump' + * 3rd word = block number + * 4th word = ((block number + 7) * 11) & 0xffffffff + * 5th word = ((4th word + 7)* 11) & 0xffffffff + * .. + * + * Return TRUE if the signature is correct, else return FALSE + */ +static int check_block_signature(void *buf, unsigned int block_nr) +{ + int word_nr = PAGE_SIZE / sizeof(int); + int *words = buf; + unsigned int val; + int i; + + if (memcmp(buf, DUMP_PARTITION_SIGNATURE, sizeof(*words))) + return FALSE; + + val = block_nr; + for (i = 2; i < word_nr; i++) { + if (words[i] != val) + return FALSE; + val = (val + 7) * 11; + } + + return TRUE; +} + +/* + * Read one block into the dump partition + */ +static int read_blocks(struct disk_dump_partition *dump_part, unsigned int nr, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, READ, nr, buf, len); + if (ret < 0) { + Err("read error on block %u", nr); + return ret; + } + return 0; +} + +static int write_blocks(struct disk_dump_partition *dump_part, unsigned int offs, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, WRITE, offs, buf, len); + if (ret < 0) { + Err("write error on block %u", offs); + return ret; + } + return 0; +} + +/* + * Initialize the common header + */ + +/* + * Write the common header + */ +static int write_header(struct disk_dump_partition *dump_part) +{ + memset(scratch, '\0', PAGE_SIZE); + memcpy(scratch, &dump_header, sizeof(dump_header)); + + return write_blocks(dump_part, 1, scratch, 1); +} + +/* + * Check the signaures in all blocks of the dump partition + * Return TRUE if the signature is correct, else return FALSE + */ +static int check_dump_partition(struct disk_dump_partition *dump_part, unsigned int partition_size) +{ + unsigned int blk; + int ret; + unsigned int chunk_blks, skips; + int i; + + if (sample_rate < 0) /* No check */ + return TRUE; + + /* + * If the device has limitations of transfer size, use it. + */ + chunk_blks = 1 << block_order; + if (dump_part->device->max_blocks) + chunk_blks = min(chunk_blks, dump_part->device->max_blocks); + skips = chunk_blks << sample_rate; + + lapse = 0; + for (blk = 0; blk < partition_size; blk += skips) { + unsigned int len; +redo: + len = min(chunk_blks, partition_size - blk); + if ((ret = read_blocks(dump_part, blk, scratch, len)) < 0) + return FALSE; + print_status(blk + 1, partition_size); + for (i = 0; i < len; i++) + if (!check_block_signature(scratch + i * DUMP_BLOCK_SIZE, blk + i)) { + Err("bad signature in block %u", blk + i); + return FALSE; + } + } + /* Check the end of the dump partition */ + if (blk - skips + chunk_blks < partition_size) { + blk = partition_size - chunk_blks; + goto redo; + } + clear_status(blk, partition_size); + return TRUE; +} + +/* + * Write memory bitmap after location of dump headers. + */ +#define IDX2PAGENR(nr, byte, bit) (((nr) * PAGE_SIZE + (byte)) * 8 + (bit)) +static int write_bitmap(struct disk_dump_partition *dump_part, unsigned int bitmap_offset, unsigned int bitmap_blocks) +{ + unsigned int nr; + int bit, byte; + int ret = 0; + unsigned char val; + + for (nr = 0; nr < bitmap_blocks; nr++) { + for (byte = 0; byte < PAGE_SIZE; byte++) { + val = 0; + for (bit = 0; bit < 8; bit++) + if (page_is_ram(IDX2PAGENR(nr, byte, bit))) + val |= (1 << bit); + scratch[byte] = (char)val; + } + if ((ret = write_blocks(dump_part, bitmap_offset + nr, scratch, 1)) < 0) { + Err("I/O error %d on block %u", ret, bitmap_offset + nr); + break; + } + } + return ret; +} + +/* + * Write whole memory to dump partition. + * Return value is the number of writen blocks. + */ +static int write_memory(struct disk_dump_partition *dump_part, int offset, unsigned int max_blocks_written, unsigned int *blocks_written) +{ + char *kaddr; + unsigned int blocks = 0; + struct page *page; + unsigned int nr; + int ret = 0; + int blk_in_chunk = 0; + + for (nr = 0; nr < max_pfn; nr++) { + print_status(blocks, max_blocks_written); + + if (!page_is_ram(nr)) + continue; + + if (blocks >= max_blocks_written) { + Warn("dump device is too small. %lu pages were not saved", max_pfn - blocks); + goto out; + } + page = pfn_to_page(nr); + kaddr = (char *)kmap_atomic(page, KM_DISKDUMP); + /* + * need to copy because adapter drivers use virt_to_bus() + */ + memcpy(scratch + blk_in_chunk * PAGE_SIZE, kaddr, PAGE_SIZE); + blk_in_chunk++; + blocks++; + kunmap_atomic(kaddr, KM_DISKDUMP); + + if (blk_in_chunk >= (1 << block_order)) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) { + Err("I/O error %d on block %u", ret, offset); + break; + } + offset += blk_in_chunk; + blk_in_chunk = 0; + } + } + if (ret >= 0 && blk_in_chunk > 0) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) + Err("I/O error %d on block %u", ret, offset); + } + +out: + clear_status(nr, max_blocks_written); + + *blocks_written = blocks; + return ret; +} + +/* + * Select most suitable dump device. sanity_check() returns the state + * of each dump device. 0 means OK, negative value means NG, and + * positive value means it maybe work. select_dump_partition() first + * try to select a sane device and if it has no sane device and + * allow_risky_dumps is set, it select one from maybe OK devices. + * + * XXX We cannot handle multiple partitions yet. + */ +static struct disk_dump_partition *select_dump_partition(void) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + int sanity; + int strict_check = 1; + +redo: + /* + * Select a sane polling driver. + */ + list_for_each_entry(dump_device, &disk_dump_devices, list) { + sanity = 0; + if (dump_device->ops.sanity_check) + sanity = dump_device->ops.sanity_check(dump_device); + if (sanity < 0 || (sanity > 0 && strict_check)) + continue; + list_for_each_entry(dump_part, &dump_device->partitions, list) + return dump_part; + } + if (allow_risky_dumps && strict_check) { + strict_check = 0; + goto redo; + } + return NULL; +} + +static void disk_dump(struct pt_regs *regs, void *platform_arg) +{ + unsigned long flags; + int ret = -EIO; + struct pt_regs myregs; + unsigned int max_written_blocks, written_blocks; + int i; + struct disk_dump_device *dump_device = NULL; + struct disk_dump_partition *dump_part = NULL; + + /* Inhibit interrupt and stop other CPUs */ + local_save_flags(flags); + local_irq_disable(); + + diskdump_lib_init(); + + /* + * Check the checksum of myself + */ + if (!spin_trylock(&disk_dump_lock)) { + Err("disk_dump_lock failed."); + goto done; + } + + spin_trylock(&disk_dump_lock); + if (!check_crc_module()) { + Err("checksum error. diskdump common module may be compromised."); + goto done; + } + + diskdump_mode = 1; + + Dbg("notify dump start."); + notifier_call_chain(&disk_dump_notifier_list, 0, NULL); + + dump_header.tasks[smp_processor_id()] = current; +#if CONFIG_SMP + smp_call_function(freeze_cpu, NULL, 1, 0); + mdelay(3000); + printk("CPU frozen: "); + for (i = 0; i < NR_CPUS; i++) { + if (dump_header.tasks[i] != NULL) + printk("#%d", i); + + } + printk("\n"); + printk("CPU#%d is executing diskdump.\n", smp_processor_id()); +#else + mdelay(1000); +#endif + + platform_fix_regs(); + + if (list_empty(&disk_dump_devices)) { + Err("adapter driver is not registered."); + goto done; + } + + printk("start dumping\n"); + + if (!(dump_part = select_dump_partition())) { + Err("No sane dump device found"); + goto done; + } + dump_device = dump_part->device; + + /* + * Stop ongoing I/O with polling driver and make the shift to I/O mode + * for dump + */ + Dbg("do quiesce"); + if (dump_device->ops.quiesce) + if ((ret = dump_device->ops.quiesce(dump_device)) < 0) { + Err("quiesce failed. error %d", ret); + goto done; + } + + if (SECTOR_BLOCK(dump_part->nr_sects) < header_blocks + bitmap_blocks) { + Warn("dump partition is too small. Aborted"); + goto done; + } + + /* Check dump partition */ + printk("check dump partition...\n"); + if (!check_dump_partition(dump_part, total_blocks)) { + Err("check partition failed."); + goto done; + } + + /* + * Write the common header + */ + memcpy(dump_header.signature, DISK_DUMP_SIGNATURE, sizeof(dump_header.signature)); + dump_header.utsname = system_utsname; + dump_header.timestamp = xtime; + dump_header.status = DUMP_HEADER_INCOMPLETED; + dump_header.block_size = PAGE_SIZE; + dump_header.sub_hdr_size = size_of_sub_header(); + dump_header.bitmap_blocks = bitmap_blocks; + dump_header.max_mapnr = max_pfn; + dump_header.total_ram_blocks = total_ram_blocks; + dump_header.device_blocks = SECTOR_BLOCK(dump_part->nr_sects); + dump_header.current_cpu = smp_processor_id(); + dump_header.nr_cpus = num_online_cpus(); + dump_header.written_blocks = 2; + + write_header(dump_part); + + /* + * Write the architecture dependent header + */ + Dbg("write sub header"); + if ((ret = write_sub_header()) < 0) { + Err("writing sub header failed. error %d", ret); + goto done; + } + + Dbg("writing memory bitmaps.."); + if ((ret = write_bitmap(dump_part, header_blocks, bitmap_blocks)) < 0) + goto done; + + max_written_blocks = total_ram_blocks; + if (dump_header.device_blocks < total_blocks) { + Warn("dump partition is too small. actual blocks %u. expected blocks %u. whole memory will not be saved", + dump_header.device_blocks, total_blocks); + max_written_blocks -= (total_blocks - dump_header.device_blocks); + } + + dump_header.written_blocks += dump_header.sub_hdr_size; + dump_header.written_blocks += dump_header.bitmap_blocks; + write_header(dump_part); + + printk("dumping memory..\n"); + if ((ret = write_memory(dump_part, header_blocks + bitmap_blocks, + max_written_blocks, &written_blocks)) < 0) + goto done; + + /* + * Set the number of block that is written into and write it + * into partition again. + */ + dump_header.written_blocks += written_blocks; + dump_header.status = DUMP_HEADER_COMPLETED; + write_header(dump_part); + + ret = 0; + +done: + Dbg("do adapter shutdown."); + if (dump_device && dump_device->ops.shutdown) + if (dump_device->ops.shutdown(dump_device)) + Err("adapter shutdown failed."); + + /* + * If diskdump failed and fallback_on_err is set, + * We just return and leave panic to netdump. + */ + if (fallback_on_err && ret != 0) + return; + + Dbg("notify panic."); + notifier_call_chain(&panic_notifier_list, 0, NULL); + + diskdump_lib_exit(); + + if (panic_timeout > 0) { + int i; + + printk(KERN_EMERG "Rebooting in %d second%s..", + panic_timeout, "s" + (panic_timeout == 1)); + for (i = 0; i < panic_timeout; i++) { + touch_nmi_watchdog(); + mdelay(1000); + } + printk("\n"); + machine_restart(NULL); + } + printk(KERN_EMERG "halt\n"); + for (;;) { + touch_nmi_watchdog(); + machine_halt(); + mdelay(1000); + } +} + +static struct disk_dump_partition *find_dump_partition(dev_t dev) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + list_for_each_entry(dump_part, &dump_device->partitions, list) + if (dump_part->dentry->d_inode->i_rdev == dev) + return dump_part; + return NULL; +} + +static struct disk_dump_device *find_dump_device(void *real_device) +{ + struct disk_dump_device *dump_device; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + if (real_device == dump_device->device) + return dump_device; + return NULL; +} + +static void *find_real_device(struct block_device *bdev, struct disk_dump_type **_dump_type) +{ + void *real_device; + struct disk_dump_type *dump_type; + + list_for_each_entry(dump_type, &disk_dump_types, list) + if ((real_device = dump_type->probe(bdev)) != NULL) { + *_dump_type = dump_type; + return real_device; + } + return NULL; +} + +/* + * Add dump partition structure corresponding to file to the dump device + * structure. + */ +static int add_dump_partition(struct disk_dump_device *dump_device, struct file *file) +{ + struct disk_dump_partition *dump_part; + struct inode *inode = file->f_dentry->d_inode; + dev_t dev = inode->i_rdev; + struct block_device *bdev = inode->i_bdev; + char buffer[32]; + + if (!(dump_part = kmalloc(sizeof(*dump_part), GFP_KERNEL))) + return -ENOMEM; + + dump_part->device = dump_device; + dump_part->vfsmount = mntget(file->f_vfsmnt); + dump_part->dentry = dget(file->f_dentry); + + if (!bdev || !bdev->bd_part) + return -EINVAL; + dump_part->nr_sects = bdev->bd_part->nr_sects; + dump_part->start_sect = bdev->bd_part->start_sect; + + if (SECTOR_BLOCK(dump_part->nr_sects) < total_blocks) { + format_dev_t(buffer, dev); + Warn("%s is too small to save whole system memory\n", buffer); + } + + list_add(&dump_part->list, &dump_device->partitions); + + return 0; +} + +/* + * Add dump partition corresponding to file. + * Must be called with disk_dump_lock held. + */ +static int add_dump(struct file *file) +{ + struct disk_dump_type *dump_type = NULL; + struct disk_dump_device *dump_device; + void *real_device; + struct inode *dump_inode = file->f_dentry->d_inode; + dev_t dev = dump_inode->i_rdev; + int ret; + + /* Check whether this inode is already registered */ + if (find_dump_partition(dev)) + return -EEXIST; + + /* find dump_type and real device for this inode */ + if (!(real_device = find_real_device(dump_inode->i_bdev, &dump_type))) + return -ENXIO; + + dump_device = find_dump_device(real_device); + if (dump_device == NULL) { + /* real_device is not registered. create new dump_device */ + if (!(dump_device = kmalloc(sizeof(*dump_device), GFP_KERNEL))) + return -ENOMEM; + + memset(dump_device, 0, sizeof(*dump_device)); + INIT_LIST_HEAD(&dump_device->partitions); + + dump_device->dump_type = dump_type; + dump_device->device = real_device; + if ((ret = dump_type->add_device(dump_device)) < 0) { + kfree(dump_device); + return ret; + } + if (!try_module_get(dump_type->owner)) + return -EINVAL; + list_add(&dump_device->list, &disk_dump_devices); + } + + ret = add_dump_partition(dump_device, file); + if (ret < 0) { + dump_type->remove_device(dump_device); + module_put(dump_type->owner); + list_del(&dump_device->list); + kfree(dump_device); + } + + return ret; +} + +/* + * Remove dump partition corresponding to file. + * Must be called with disk_dump_lock held. + */ +static int remove_dump(struct file *file) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + struct disk_dump_type *dump_type; + dev_t dev = file->f_dentry->d_inode->i_rdev; + + if (!(dump_part = find_dump_partition(dev))) + return -ENOENT; + + dump_device = dump_part->device; + + list_del(&dump_part->list); + mntput(dump_part->vfsmount); + dput(dump_part->dentry); + kfree(dump_part); + + if (list_empty(&dump_device->partitions)) { + dump_type = dump_device->dump_type; + dump_type->remove_device(dump_device); + module_put(dump_type->owner); + module_put(dump_type->owner); + list_del(&dump_device->list); + kfree(dump_device); + } + + return 0; +} + +#ifdef CONFIG_PROC_FS +static int proc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long param) +{ + int fd = (int)param; + int ret; + struct file *dump_file; + struct inode *dump_inode; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + dump_file = fget(fd); + if (!dump_file) + return -EBADF; + dump_inode = dump_file->f_dentry->d_inode; + if (!dump_inode->i_bdev) { + fput(dump_file); + return -EBADF; + } + + spin_lock(&disk_dump_lock); + switch (cmd) { + case BLKADDDUMPDEVICE: + ret = add_dump(dump_file); + break; + case BLKREMOVEDUMPDEVICE: + ret = remove_dump(dump_file); + break; + default: + ret = -EINVAL; + } + + set_crc_modules(); + spin_unlock(&disk_dump_lock); + + fput(dump_file); + + return ret; +} + +static void *disk_dump_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + loff_t n = *pos; + + spin_lock(&disk_dump_lock); + list_for_each_entry(dump_device, &disk_dump_devices, list) { + seq->private = dump_device; + list_for_each_entry(dump_part, &dump_device->partitions, list) { + if (!n--) + return dump_part; + } + } + return NULL; +} + +static void *disk_dump_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct list_head *partition = v; + struct list_head *device = seq->private; + struct disk_dump_device *dump_device; + + dump_device = list_entry(device, struct disk_dump_device, list); + + (*pos)++; + partition = partition->next; + if (partition != &dump_device->partitions) + return partition; + + device = device->next; + seq->private = device; + if (device == &disk_dump_devices) + return NULL; + + dump_device = list_entry(device, struct disk_dump_device, list); + + return dump_device->partitions.next; +} + +static void disk_dump_seq_stop(struct seq_file *seq, void *v) +{ + spin_unlock(&disk_dump_lock); +} + +static int disk_dump_seq_show(struct seq_file *seq, void *v) +{ + struct disk_dump_partition *dump_part = v; + char *page; + char *path; + + if (!(page = (char *)__get_free_page(GFP_KERNEL))) + return -ENOMEM; + path = d_path(dump_part->dentry, dump_part->vfsmount, page, PAGE_SIZE); + seq_printf(seq, "%s %lu %lu\n", + path, dump_part->start_sect, dump_part->nr_sects); + free_page((unsigned long)page); + return 0; +} + +static struct seq_operations disk_dump_seq_ops = { + .start = disk_dump_seq_start, + .next = disk_dump_seq_next, + .stop = disk_dump_seq_stop, + .show = disk_dump_seq_show, +}; + +static int disk_dump_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &disk_dump_seq_ops); +} + +static struct file_operations disk_dump_fops = { + .owner = THIS_MODULE, + .open = disk_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, + .ioctl = proc_ioctl, +}; +#endif + + +int register_disk_dump_type(struct disk_dump_type *dump_type) +{ + spin_lock(&disk_dump_lock); + list_add(&dump_type->list, &disk_dump_types); + set_crc_modules(); + spin_unlock(&disk_dump_lock); + + return 0; +} + +int unregister_disk_dump_type(struct disk_dump_type *dump_type) +{ + spin_lock(&disk_dump_lock); + list_del(&dump_type->list); + set_crc_modules(); + spin_unlock(&disk_dump_lock); + + return 0; +} + +EXPORT_SYMBOL(register_disk_dump_type); +EXPORT_SYMBOL(unregister_disk_dump_type); +EXPORT_SYMBOL(disk_dump_notifier_list); + + +static void compute_total_blocks(void) +{ + unsigned int nr; + + /* + * the number of block of the common header and the header + * that is depend on the architecture + * + * block 0: dump partition header + * block 1: dump header + * block 2: dump subheader + * block 3..n: memory bitmap + * block (n + 1)...: saved memory + * + * We never overwrite block 0 + */ + header_blocks = 2 + size_of_sub_header(); + + total_ram_blocks = 0; + for (nr = 0; nr < max_pfn; nr++) { + if (page_is_ram(nr)) + total_ram_blocks++; + } + + bitmap_blocks = ROUNDUP(max_pfn, 8 * PAGE_SIZE); + + /* + * The necessary size of area for dump is: + * 1 block for common header + * m blocks for architecture dependent header + * n blocks for memory bitmap + * and whole memory + */ + total_blocks = header_blocks + bitmap_blocks + total_ram_blocks; + + Info("total blocks required: %u (header %u + bitmap %u + memory %u)", + total_blocks, header_blocks, bitmap_blocks, total_ram_blocks); +} + +static int init_diskdump(void) +{ + unsigned long long t0; + unsigned long long t1; + struct page *page; + + if (!platform_supports_diskdump) { + Err("platform does not support diskdump."); + return -1; + } + + /* Allocate one block that is used temporally */ + do { + page = alloc_pages(GFP_KERNEL, block_order); + if (page != NULL) + break; + } while (--block_order >= 0); + if (!page) { + Err("alloc_pages failed."); + return -1; + } + scratch = page_address(page); + Info("Maximum block size: %lu", PAGE_SIZE << block_order); + + if (diskdump_register_hook(disk_dump)) { + Err("failed to register hooks."); + return -1; + } + + compute_total_blocks(); + + platform_timestamp(t0); + mdelay(1); + platform_timestamp(t1); + timestamp_1sec = (unsigned long)(t1 - t0) * 1000; + +#ifdef CONFIG_PROC_FS + { + struct proc_dir_entry *p; + + p = create_proc_entry("diskdump", S_IRUGO|S_IWUSR, NULL); + if (p) + p->proc_fops = &disk_dump_fops; + } +#endif + + return 0; +} + +static void cleanup_diskdump(void) +{ + Info("shut down."); + diskdump_unregister_hook(); + free_pages((unsigned long)scratch, block_order); +#ifdef CONFIG_PROC_FS + remove_proc_entry("diskdump", NULL); +#endif +} + +module_init(init_diskdump); +module_exit(cleanup_diskdump); +MODULE_LICENSE("GPL"); diff -Nur linux-2.6.6.org/drivers/block/diskdumplib.c linux-2.6.6/drivers/block/diskdumplib.c --- linux-2.6.6.org/drivers/block/diskdumplib.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/drivers/block/diskdumplib.c 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,203 @@ +/* + * linux/drivers/block/diskdumplib.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/file.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/smp_lock.h> +#include <linux/genhd.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/diskdump.h> +#include <linux/diskdumplib.h> +#include <asm/diskdump.h> + +/* + * timer list and tasklet_struct holder + */ +unsigned long volatile diskdump_base_jiffies; +static unsigned long long timestamp_base; +static unsigned long timestamp_hz; + +#define DISKDUMP_NUM_TASKLETS 8 + +/* + * We can't use next field of tasklet because it breaks the original + * tasklets chain and we have no way to know which chain the tasklet is + * linked. + */ +static struct tasklet_struct *diskdump_tasklets[DISKDUMP_NUM_TASKLETS]; + +static LIST_HEAD(diskdump_timers); +static LIST_HEAD(diskdump_workq); + + +static int store_tasklet(struct tasklet_struct *tasklet) +{ + int i; + + for (i = 0; i < DISKDUMP_NUM_TASKLETS; i++) + if (diskdump_tasklets[i] == NULL) { + diskdump_tasklets[i] = tasklet; + return 0; + } + return -1; +} + +static struct tasklet_struct *find_tasklet(struct tasklet_struct *tasklet) +{ + int i; + + for (i = 0; i < DISKDUMP_NUM_TASKLETS; i++) + if (diskdump_tasklets[i] == tasklet) + return diskdump_tasklets[i]; + return NULL; +} + +void _diskdump_tasklet_schedule(struct tasklet_struct *tasklet) +{ + if (!find_tasklet(tasklet)) + if (store_tasklet(tasklet)) + printk(KERN_ERR "diskdumplib: too many tasklet. Ignored\n"); + set_bit(TASKLET_STATE_SCHED, &tasklet->state); +} + +int _diskdump_schedule_work(struct work_struct *work) +{ + list_add_tail(&work->entry, &diskdump_workq); + return 1; +} + +void _diskdump_add_timer(struct timer_list *timer) +{ + timer->base = (void *)1; + list_add(&timer->entry, &diskdump_timers); +} + +int _diskdump_del_timer(struct timer_list *timer) +{ + if (timer->base != NULL) { + list_del(&timer->entry); + return 1; + } else { + timer->base = NULL; + return 0; + } +} + +int _diskdump_mod_timer(struct timer_list *timer, unsigned long expires) +{ + int ret; + + ret = _diskdump_del_timer(timer); + timer->expires = expires; + _diskdump_add_timer(timer); + + return ret; +} + +static void update_jiffies(void) +{ + unsigned long long t; + + platform_timestamp(t); + while (t > timestamp_base + timestamp_hz) { + timestamp_base += timestamp_hz; + jiffies++; + platform_timestamp(t); + } +} + +void diskdump_update(void) +{ + struct tasklet_struct *tasklet; + struct work_struct *work; + struct timer_list *timer; + struct list_head *t, *n, head; + int i; + + update_jiffies(); + + /* run timers */ + list_for_each_safe(t, n, &diskdump_timers) { + timer = list_entry(t, struct timer_list, entry); + if (time_before_eq(timer->expires, jiffies)) { + list_del(t); + timer->function(timer->data); + } + } + + /* run tasklet */ + for (i = 0; i < DISKDUMP_NUM_TASKLETS; i++) + if ((tasklet = diskdump_tasklets[i])) + if (!atomic_read(&tasklet->count)) + if (test_and_clear_bit(TASKLET_STATE_SCHED, &tasklet->state)) + tasklet->func(tasklet->data); + + /* run work queue */ + list_add(&head, &diskdump_workq); + list_del_init(&diskdump_workq); + n = head.next; + while (n != &head) { + work = list_entry(t, struct work_struct, entry); + n = n->next; + if (work->func) + work->func(work->wq_data); + } +} + +void diskdump_lib_init(void) +{ + unsigned long long t; + + /* Save original jiffies value */ + diskdump_base_jiffies = jiffies; + + platform_timestamp(timestamp_base); + udelay(1000000/HZ); + platform_timestamp(t); + timestamp_hz = (unsigned long)(t - timestamp_base); + + diskdump_update(); +} + +void diskdump_lib_exit(void) +{ + /* Resotre original jiffies. */ + jiffies = diskdump_base_jiffies; +} + +EXPORT_SYMBOL(diskdump_lib_init); +EXPORT_SYMBOL(diskdump_lib_exit); +EXPORT_SYMBOL(diskdump_update); +EXPORT_SYMBOL(_diskdump_add_timer); +EXPORT_SYMBOL(_diskdump_del_timer); +EXPORT_SYMBOL(_diskdump_mod_timer); +EXPORT_SYMBOL(_diskdump_tasklet_schedule); +EXPORT_SYMBOL(_diskdump_schedule_work); + +MODULE_LICENSE("GPL"); diff -Nur linux-2.6.6.org/drivers/block/genhd.c linux-2.6.6/drivers/block/genhd.c --- linux-2.6.6.org/drivers/block/genhd.c 2004-06-04 21:22:15.000000000 +0900 +++ linux-2.6.6/drivers/block/genhd.c 2004-06-16 19:34:16.000000000 +0900 @@ -629,3 +629,28 @@ } EXPORT_SYMBOL(invalidate_partition); + +/* + * Dump stuff. + */ +void (*diskdump_func) (struct pt_regs *regs, void *platform_arg) = NULL; +EXPORT_SYMBOL_GPL(diskdump_func); + +int diskdump_register_hook(void (*dump_func) (struct pt_regs *, void *)) +{ + if (diskdump_func) + return -EEXIST; + + diskdump_func = dump_func; + + return 0; +} + +EXPORT_SYMBOL_GPL(diskdump_register_hook); + +void diskdump_unregister_hook(void) +{ + diskdump_func = NULL; +} + +EXPORT_SYMBOL_GPL(diskdump_unregister_hook); diff -Nur linux-2.6.6.org/drivers/char/sysrq.c linux-2.6.6/drivers/char/sysrq.c --- linux-2.6.6.org/drivers/char/sysrq.c 2004-06-04 21:22:29.000000000 +0900 +++ linux-2.6.6/drivers/char/sysrq.c 2004-06-16 19:34:16.000000000 +0900 @@ -107,6 +107,19 @@ .action_msg = "Resetting", }; +/* crash sysrq handler */ +static void sysrq_handle_crash(int key, struct pt_regs *pt_regs, + struct tty_struct *tty) +{ + *( (char *) 0) = 0; +} + +static struct sysrq_key_op sysrq_crash_op = { + .handler = sysrq_handle_crash, + .help_msg = "Crash", + .action_msg = "Crashing the kernel by request", +}; + static void sysrq_handle_sync(int key, struct pt_regs *pt_regs, struct tty_struct *tty) { @@ -235,7 +248,7 @@ it is handled specially on the sparc and will never arrive */ /* b */ &sysrq_reboot_op, -/* c */ NULL, +/* c */ &sysrq_crash_op, /* d */ NULL, /* e */ &sysrq_term_op, /* f */ NULL, diff -Nur linux-2.6.6.org/include/asm-i386/diskdump.h linux-2.6.6/include/asm-i386/diskdump.h --- linux-2.6.6.org/include/asm-i386/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/include/asm-i386/diskdump.h 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,82 @@ +#ifndef _ASM_I386_DISKDUMP_H +#define _ASM_I386_DISKDUMP_H + +/* + * linux/include/asm-i386/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * Copyright (c) 2003 Red Hat, Inc. All rights reserved. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifdef __KERNEL__ + +#include <linux/elf.h> + +extern int page_is_ram(unsigned long); +const static int platform_supports_diskdump = 1; + +#define platform_fix_regs() \ +{ \ + unsigned long esp; \ + unsigned short ss; \ + esp = (unsigned long) ((char *)regs + sizeof (struct pt_regs)); \ + ss = __KERNEL_DS; \ + if (regs->xcs & 3) { \ + esp = regs->esp; \ + ss = regs->xss & 0xffff; \ + } \ + myregs = *regs; \ + myregs.esp = esp; \ + myregs.xss = (myregs.xss & 0xffff0000) | ss; \ +} + +struct disk_dump_sub_header { + elf_gregset_t elf_regs; +}; + +#define platform_timestamp(x) rdtscll(x) + +#define size_of_sub_header() ((sizeof(struct disk_dump_sub_header) + PAGE_SIZE - 1) / DUMP_BLOCK_SIZE) + +#define write_sub_header() \ +({ \ + int ret; \ + \ + ELF_CORE_COPY_REGS(dump_sub_header.elf_regs, (&myregs)); \ + clear_page(scratch); \ + memcpy(scratch, &dump_sub_header, sizeof(dump_sub_header)); \ + \ + if ((ret = write_blocks(dump_part, 2, scratch, 1)) >= 0) \ + ret = 1; /* size of sub header in page */; \ + ret; \ +}) + +#define platform_freeze_cpu() \ +{ \ + for (;;) local_irq_disable(); \ +} + +#define platform_start_diskdump(func, regs) \ +{ \ + func(regs, NULL); \ +} + +#endif /* __KERNEL__ */ + +#endif /* _ASM_I386_DISKDUMP_H */ diff -Nur linux-2.6.6.org/include/asm-i386/kmap_types.h linux-2.6.6/include/asm-i386/kmap_types.h --- linux-2.6.6.org/include/asm-i386/kmap_types.h 2004-06-04 21:22:02.000000000 +0900 +++ linux-2.6.6/include/asm-i386/kmap_types.h 2004-06-16 19:34:16.000000000 +0900 @@ -24,7 +24,8 @@ D(11) KM_IRQ1, D(12) KM_SOFTIRQ0, D(13) KM_SOFTIRQ1, -D(14) KM_TYPE_NR +D(14) KM_DISKDUMP, +D(15) KM_TYPE_NR }; #undef D diff -Nur linux-2.6.6.org/include/linux/diskdump.h linux-2.6.6/include/linux/diskdump.h --- linux-2.6.6.org/include/linux/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/include/linux/diskdump.h 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,157 @@ +#ifndef _LINUX_DISKDUMP_H +#define _LINUX_DISKDUMP_H + +/* + * linux/include/linux/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/list.h> +#include <linux/mount.h> +#include <linux/dcache.h> +#include <linux/blkdev.h> +#include <linux/utsname.h> + +/* The minimum Dump I/O unit. Must be the same of PAGE_SIZE */ +#define DUMP_BLOCK_SIZE PAGE_SIZE +#define DUMP_BLOCK_SHIFT PAGE_SHIFT + +/* Dump ioctls */ +#define BLKADDDUMPDEVICE 0xdf00 /* Add a dump device */ +#define BLKREMOVEDUMPDEVICE 0xdf01 /* Delete a dump device */ + +int diskdump_register_hook(void (*dump_func)(struct pt_regs *, void *)); +void diskdump_unregister_hook(void); + +/* + * The handler that adapter driver provides for the common module of + * dump + */ +struct disk_dump_partition; +struct disk_dump_device; + +struct disk_dump_type { + void *(*probe)(struct block_device *); + int (*add_device)(struct disk_dump_device *); + void (*remove_device)(struct disk_dump_device *); + struct module *owner; + struct list_head list; +}; + +struct disk_dump_device_ops { + int (*sanity_check)(struct disk_dump_device *); + int (*quiesce)(struct disk_dump_device *); + int (*shutdown)(struct disk_dump_device *); + int (*rw_block)(struct disk_dump_partition *, int rw, unsigned long block_nr, void *buf, int len); +}; + +/* The data structure for a dump device */ +struct disk_dump_device { + struct list_head list; + struct disk_dump_device_ops ops; + struct disk_dump_type *dump_type; + void *device; + unsigned int max_blocks; + struct list_head partitions; +}; + +/* The data structure for a dump partition */ +struct disk_dump_partition { + struct list_head list; + struct disk_dump_device *device; + struct vfsmount *vfsmount; + struct dentry *dentry; + unsigned long start_sect; + unsigned long nr_sects; +}; + + +int register_disk_dump_type(struct disk_dump_type *); +int unregister_disk_dump_type(struct disk_dump_type *); + + +/* + * Architecture-independent dump header + */ + +/* The signature which is written in each block in the dump partition */ +#define DISK_DUMP_SIGNATURE "DISKDUMP" +#define DISK_DUMP_HEADER_VERSION 1 + +#define DUMP_PARTITION_SIGNATURE "diskdump" + +#define DUMP_HEADER_COMPLETED 0 +#define DUMP_HEADER_INCOMPLETED 1 + +struct disk_dump_header { + char signature[8]; /* = "DISKDUMP" */ + int header_version; /* Dump header version */ + struct new_utsname utsname; /* copy of system_utsname */ + struct timespec timestamp; /* Time stamp */ + unsigned int status; /* Above flags */ + int block_size; /* Size of a block in byte */ + int sub_hdr_size; /* Size of arch dependent + header in blocks */ + unsigned int bitmap_blocks; /* Size of Memory bitmap in + block */ + unsigned int max_mapnr; /* = max_mapnr */ + unsigned int total_ram_blocks;/* Size of Memory in block */ + unsigned int device_blocks; /* Number of total blocks in + * the dump device */ + unsigned int written_blocks; /* Number of written blocks */ + unsigned int current_cpu; /* CPU# which handles dump */ + int nr_cpus; /* Number of CPUs */ + struct task_struct *tasks[NR_CPUS]; +}; + +/* + * Calculate the check sum of the whole module + */ +#define get_crc_module() \ +({ \ + struct module *module = &__this_module; \ + crc32_le(0, (char *)(module->module_core), \ + ((unsigned long)module - (unsigned long)(module->module_core))); \ +}) + +/* Calculate the checksum of the whole module */ +#define set_crc_modules() \ +({ \ + module_crc = 0; \ + module_crc = get_crc_module(); \ +}) + +/* + * Compare the checksum value that is stored in module_crc to the check + * sum of current whole module. Must be called with holding disk_dump_lock. + * Return TRUE if they are the same, else return FALSE + * + */ +#define check_crc_module() \ +({ \ + uint32_t orig_crc, cur_crc; \ + \ + orig_crc = module_crc; module_crc = 0; \ + cur_crc = get_crc_module(); \ + module_crc = orig_crc; \ + orig_crc == cur_crc; \ +}) + + +#endif /* _LINUX_DISKDUMP_H */ diff -Nur linux-2.6.6.org/include/linux/diskdumplib.h linux-2.6.6/include/linux/diskdumplib.h --- linux-2.6.6.org/include/linux/diskdumplib.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/include/linux/diskdumplib.h 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,67 @@ +#ifndef _LINUX_DISKDUMPLIB_H +#define _LINUX_DISKDUMPLIB_H + +#include <linux/interrupt.h> +#include <linux/timer.h> + +void diskdump_lib_init(void); +void diskdump_lib_exit(void); +void diskdump_update(void); + +void _diskdump_add_timer(struct timer_list *); +int _diskdump_del_timer(struct timer_list *); +int _diskdump_mod_timer(struct timer_list *, unsigned long); +void _diskdump_tasklet_schedule(struct tasklet_struct *); +int _diskdump_schedule_work(struct work_struct *); + +static inline void diskdump_add_timer(struct timer_list *timer) +{ + if (crashdump_mode()) + _diskdump_add_timer(timer); + else + add_timer(timer); +} + +static inline int diskdump_del_timer(struct timer_list *timer) +{ + if (crashdump_mode()) + return _diskdump_del_timer(timer); + else + return del_timer(timer); +} + +static inline int diskdump_mod_timer(struct timer_list *timer, unsigned long expires) +{ + if (crashdump_mode()) + return _diskdump_mod_timer(timer, expires); + else + return mod_timer(timer, expires); +} + +static inline void diskdump_tasklet_schedule(struct tasklet_struct *tasklet) +{ + if (crashdump_mode()) + return _diskdump_tasklet_schedule(tasklet); + else + return tasklet_schedule(tasklet); +} + +static inline int diskdump_schedule_work(struct work_struct *work) +{ + if (crashdump_mode()) + return _diskdump_schedule_work(work); + else + return schedule_work(work); +} + +static inline long diskdump_schedule_timeout(signed long timeout) +{ + if (crashdump_mode()) { + mdelay(timeout); + set_current_state(TASK_RUNNING); + return timeout; + } else + return schedule_timeout(timeout); +} + +#endif /* _LINUX_DISKDUMPLIB_H */ diff -Nur linux-2.6.6.org/include/linux/kernel.h linux-2.6.6/include/linux/kernel.h --- linux-2.6.6.org/include/linux/kernel.h 2004-06-04 21:22:08.000000000 +0900 +++ linux-2.6.6/include/linux/kernel.h 2004-06-16 19:34:16.000000000 +0900 @@ -112,6 +112,11 @@ extern int system_state; /* See values below */ extern int tainted; extern const char *print_tainted(void); +struct pt_regs; +extern void try_crashdump(struct pt_regs *); +extern void (*diskdump_func) (struct pt_regs *regs, void *platform_arg); +extern int diskdump_mode; +#define crashdump_mode() (diskdump_mode) /* Values used for system_state */ #define SYSTEM_BOOTING 0 @@ -136,6 +141,12 @@ #define pr_info(fmt,arg...) \ printk(KERN_INFO fmt,##arg) +#define pr_err(fmt,arg...) \ + printk(KERN_ERR fmt,##arg) + +#define pr_warn(fmt,arg...) \ + printk(KERN_WARNING fmt,##arg) + /* * Display an IP address in readable format. */ diff -Nur linux-2.6.6.org/kernel/panic.c linux-2.6.6/kernel/panic.c --- linux-2.6.6.org/kernel/panic.c 2004-06-04 21:21:58.000000000 +0900 +++ linux-2.6.6/kernel/panic.c 2004-06-16 19:34:16.000000000 +0900 @@ -23,8 +23,10 @@ int panic_timeout; int panic_on_oops; int tainted; +int diskdump_mode = 0; EXPORT_SYMBOL(panic_timeout); +EXPORT_SYMBOL_GPL(diskdump_mode); struct notifier_block *panic_notifier_list; @@ -60,6 +62,8 @@ vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); printk(KERN_EMERG "Kernel panic: %s\n",buf); + if (diskdump_func) + BUG(); if (in_interrupt()) printk(KERN_EMERG "In interrupt handler - not syncing\n"); else if (!current->pid) @@ -134,3 +138,23 @@ snprintf(buf, sizeof(buf), "Not tainted"); return(buf); } + +/* + * Try crashdump. Diskdump is first, netdump is second. + * We clear diskdump_func before call of diskdump_func, so + * If double panic would occur in diskdump, netdump can handle + * it. + */ +#include <asm/diskdump.h> +void try_crashdump(struct pt_regs *regs) +{ + void (*func)(struct pt_regs *, void *); + + if (diskdump_func) { + func = diskdump_func; + diskdump_func = NULL; + platform_start_diskdump(func, regs); + } + if (panic_on_oops) + panic("Fatal exception"); +} diff -Nur linux-2.6.6.org/mm/bootmem.c linux-2.6.6/mm/bootmem.c --- linux-2.6.6.org/mm/bootmem.c 2004-06-04 21:22:37.000000000 +0900 +++ linux-2.6.6/mm/bootmem.c 2004-06-16 19:34:16.000000000 +0900 @@ -16,6 +16,7 @@ #include <linux/init.h> #include <linux/bootmem.h> #include <linux/mmzone.h> +#include <linux/module.h> #include <asm/dma.h> #include <asm/io.h> @@ -26,6 +27,7 @@ unsigned long max_low_pfn; unsigned long min_low_pfn; unsigned long max_pfn; +EXPORT_SYMBOL(max_pfn); /* return the number of _pages_ that will be allocated for the boot bitmap */ unsigned long __init bootmem_bootmap_pages (unsigned long pages) ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/4][Diskdump]Update patches 2004-06-16 12:39 [PATCH 0/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:45 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh @ 2004-06-16 12:46 ` Takao Indoh 2004-06-16 12:48 ` [PATCH 3/4][Diskdump]Update patches Takao Indoh ` (2 subsequent siblings) 4 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-06-16 12:46 UTC (permalink / raw) To: linux-kernel This is a patch for scsi common layer. diff -Nur linux-2.6.6.org/drivers/scsi/Kconfig linux-2.6.6/drivers/scsi/Kconfig --- linux-2.6.6.org/drivers/scsi/Kconfig 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/Kconfig 2004-06-16 19:34:16.000000000 +0900 @@ -55,6 +55,12 @@ In this case, do not compile the driver for your SCSI host adapter (below) as a module either. +config SCSI_DUMP + tristate "SCSI dump support" + depends on DISKDUMP && SCSI + help + SCSI dump support + config CHR_DEV_ST tristate "SCSI tape support" depends on SCSI diff -Nur linux-2.6.6.org/drivers/scsi/Makefile linux-2.6.6/drivers/scsi/Makefile --- linux-2.6.6.org/drivers/scsi/Makefile 2004-06-04 21:22:21.000000000 +0900 +++ linux-2.6.6/drivers/scsi/Makefile 2004-06-16 19:34:16.000000000 +0900 @@ -133,6 +133,8 @@ obj-$(CONFIG_BLK_DEV_SR) += sr_mod.o obj-$(CONFIG_CHR_DEV_SG) += sg.o +obj-$(CONFIG_SCSI_DUMP) += scsi_dump.o + scsi_mod-y += scsi.o hosts.o scsi_ioctl.o constants.o \ scsicam.o scsi_error.o scsi_lib.o \ scsi_scan.o scsi_syms.o scsi_sysfs.o \ diff -Nur linux-2.6.6.org/drivers/scsi/scsi.c linux-2.6.6/drivers/scsi/scsi.c --- linux-2.6.6.org/drivers/scsi/scsi.c 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/scsi.c 2004-06-16 19:34:16.000000000 +0900 @@ -691,6 +691,9 @@ { unsigned long flags; + if (crashdump_mode()) + return; + /* * We don't have to worry about this one timing out any more. * If we are unable to remove the timer, then the command diff -Nur linux-2.6.6.org/drivers/scsi/scsi_dump.c linux-2.6.6/drivers/scsi/scsi_dump.c --- linux-2.6.6.org/drivers/scsi/scsi_dump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/drivers/scsi/scsi_dump.c 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,579 @@ +/* + * linux/drivers/scsi/scsi_dump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + * Some codes are derived from drivers/scsi/sd.c + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> + +#include <linux/blkdev.h> +#include <linux/blkpg.h> + +#include <linux/genhd.h> +#include <linux/utsname.h> +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/diskdump.h> +#include <linux/diskdumplib.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_ioctl.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_dump.h> + +#include "scsi.h" +#include "scsi_priv.h" + +#define MAX_RETRIES 5 +#define SD_TIMEOUT (60 * HZ) + +#define Dbg(x, ...) pr_debug("scsi_dump: " x "\n", ## __VA_ARGS__) +#define Err(x, ...) pr_err ("scsi_dump: " x "\n", ## __VA_ARGS__) +#define Warn(x, ...) pr_warn ("scsi_dump: " x "\n", ## __VA_ARGS__) +#define Info(x, ...) pr_info ("scsi_dump: " x "\n", ## __VA_ARGS__) + +/* blocks to 512byte sectors */ +#define BLOCK_SECTOR(s) ((s) << (DUMP_BLOCK_SHIFT - 9)) + +static int quiesce_ok = 0; +static struct scsi_cmnd scsi_dump_cmnd; +static struct request scsi_dump_req; +static uint32_t module_crc; + +static void rw_intr(struct scsi_cmnd * scmd) +{ + scmd->done = NULL; +} + +/* + * Common code to make Scsi_Cmnd + */ +static void init_scsi_command(struct scsi_device *sdev, struct scsi_cmnd *scmd, + void *buf, int len, unsigned char direction, + int set_lun) +{ + scmd->request = &scsi_dump_req; + scmd->sc_magic = SCSI_CMND_MAGIC; + scmd->owner = SCSI_OWNER_MIDLEVEL; + scmd->device = sdev; + scmd->buffer = scmd->request_buffer = buf; + scmd->bufflen = scmd->request_bufflen = len; + + + scmd->sc_data_direction = direction; + + memcpy(scmd->data_cmnd, scmd->cmnd, sizeof(scmd->cmnd)); + scmd->cmd_len = COMMAND_SIZE(scmd->cmnd[0]); + scmd->old_cmd_len = scmd->cmd_len; + + + if (set_lun) + scmd->cmnd[1] |= (sdev->scsi_level <= SCSI_2) ? + ((sdev->lun << 5) & 0xe0) : 0; + + scmd->transfersize = sdev->sector_size; + if (direction == SCSI_DATA_WRITE) + scmd->underflow = len; + + scmd->allowed = MAX_RETRIES; + scmd->timeout_per_command = SD_TIMEOUT; + + /* + * This is the completion routine we use. This is matched in terms + * of capability to this function. + */ + scmd->done = rw_intr; +} + +/* MODE SENSE */ +static void init_mode_sense_command(struct scsi_device *sdev, + struct scsi_cmnd *scmd, void *buf) +{ + memset(scmd, 0, sizeof(*scmd)); + scmd->cmnd[0] = MODE_SENSE; + scmd->cmnd[1] = 0x00; /* DBD=0 */ + scmd->cmnd[2] = 0x08; /* PCF=0 Page 8(Cache) */ + scmd->cmnd[4] = 255; + + init_scsi_command(sdev, scmd, buf, 256, SCSI_DATA_READ, 1); +} + +/* MODE SELECT */ +static void init_mode_select_command(struct scsi_device *sdev, + struct scsi_cmnd *scmd, void *buf, int len) +{ + memset(scmd, 0, sizeof(*scmd)); + scmd->cmnd[0] = MODE_SELECT; + scmd->cmnd[1] = 0x10; /* PF=1 SP=0 */ + scmd->cmnd[4] = len; + + init_scsi_command(sdev, scmd, buf, len, SCSI_DATA_WRITE, 1); +} + +/* SYNCHRONIZE CACHE */ +static void init_sync_command(struct scsi_device *sdev, struct scsi_cmnd * scmd) +{ + memset(scmd, 0, sizeof(*scmd)); + scmd->cmnd[0] = SYNCHRONIZE_CACHE; + + init_scsi_command(sdev, scmd, NULL, 0, SCSI_DATA_NONE, 0); +} + +/* REQUEST SENSE */ +static void init_sense_command(struct scsi_device *sdev, struct scsi_cmnd *scmd, + void *buf) +{ + memset(scmd, 0, sizeof(*scmd)); + scmd->cmnd[0] = REQUEST_SENSE; + scmd->cmnd[4] = 255; + + init_scsi_command(sdev, scmd, buf, 256, SCSI_DATA_READ, 1); +} + +/* READ/WRITE */ +static int init_rw_command(struct disk_dump_partition *dump_part, + struct scsi_device *sdev, struct scsi_cmnd * scmd, + int rw, int block, void *buf, unsigned int len) +{ + int this_count = len >> 9; + + memset(scmd, 0, sizeof(*scmd)); + + if (block + this_count > dump_part->nr_sects) { + Err("block number %d is larger than %lu", + block + this_count, dump_part->nr_sects); + return -EFBIG; + } + + block += dump_part->start_sect; + + /* + * If we have a 1K hardware sectorsize, prevent access to single + * 512 byte sectors. In theory we could handle this - in fact + * the scsi cdrom driver must be able to handle this because + * we typically use 1K blocksizes, and cdroms typically have + * 2K hardware sectorsizes. Of course, things are simpler + * with the cdrom, since it is read-only. For performance + * reasons, the filesystems should be able to handle this + * and not force the scsi disk driver to use bounce buffers + * for this. + */ + if (sdev->sector_size == 1024) { + block = block >> 1; + this_count = this_count >> 1; + } + if (sdev->sector_size == 2048) { + block = block >> 2; + this_count = this_count >> 2; + } + if (sdev->sector_size == 4096) { + block = block >> 3; + this_count = this_count >> 3; + } + switch (rw) { + case WRITE: + if (!sdev->writeable) { + Err("writable media"); + return 0; + } + scmd->cmnd[0] = WRITE_10; + break; + case READ: + scmd->cmnd[0] = READ_10; + break; + default: + Err("Unknown command %d", rw); + return -EINVAL; + } + + if (this_count > 0xffff) + this_count = 0xffff; + + scmd->cmnd[2] = (unsigned char) (block >> 24) & 0xff; + scmd->cmnd[3] = (unsigned char) (block >> 16) & 0xff; + scmd->cmnd[4] = (unsigned char) (block >> 8) & 0xff; + scmd->cmnd[5] = (unsigned char) block & 0xff; + scmd->cmnd[7] = (unsigned char) (this_count >> 8) & 0xff; + scmd->cmnd[8] = (unsigned char) this_count & 0xff; + + init_scsi_command(sdev, scmd, buf, len, + (rw == WRITE ? SCSI_DATA_WRITE : SCSI_DATA_READ), 1); + return 0; +} + +/* + * Check the status of scsi command and determine whether it is + * success, fail, or retriable. + * + * Return code + * > 0: should retry + * = 0: success + * < 0: fail + */ +static int cmd_result(struct scsi_cmnd *scmd) +{ + int status; + + status = status_byte(scmd->result); + + switch (scsi_decide_disposition(scmd)) { + case FAILED: + break; + case NEEDS_RETRY: + case ADD_TO_MLQUEUE: + return 1 /* retry */; + case SUCCESS: + if (host_byte(scmd->result) != DID_OK) + break; + + if (status == GOOD || status == INTERMEDIATE_GOOD + || status == INTERMEDIATE_C_GOOD) + return 0; + if (status == CHECK_CONDITION && + scmd->sense_buffer[2] == RECOVERED_ERROR) + return 0; + break; + default: + Err("bad disposition: %d", scmd->result); + return -EIO; + } + + Err("command %x failed with 0x%x", scmd->cmnd[0], scmd->result); + return -EIO; +} + +static int send_command(struct scsi_cmnd *scmd) +{ + struct Scsi_Host *host = scmd->device->host; + struct scsi_device *sdev = scmd->device; + int ret; + + do { + if (!scsi_device_online(sdev)) { + Err("Scsi disk is not online"); + return -EIO; + } + if (sdev->changed) { + Err("SCSI disk has been changed. Prohibiting further I/O"); + return -EIO; + } + + spin_lock(host->host_lock); + host->hostt->queuecommand(scmd, rw_intr); + spin_unlock(host->host_lock); + + while (scmd->done != NULL) { + host->hostt->dump_poll(scmd->device); + udelay(100); + diskdump_update(); + } + scmd->done = rw_intr; + } while ((ret = cmd_result(scmd)) > 0); + + return ret; +} + +/* + * If Write Cache Enable of disk device is not set, write I/O takes + * long long time. So enable WCE temporary and issue SYNCHRONIZE CACHE + * after all write I/Os are done, Following system reboot will reset + * WCE bit to original value. + */ +static void +enable_write_cache(struct scsi_device *sdev) +{ + char buf[256]; + int ret; + int data_len; + + Dbg("enable write cache"); + memset(buf, 0, 256); + + init_mode_sense_command(sdev, &scsi_dump_cmnd, buf); + if ((ret = send_command(&scsi_dump_cmnd)) < 0) { + Warn("MODE SENSE failed"); + return; + } + + if (buf[14] & 0x04) /* WCE is already set */ + return; + + data_len = buf[0] + 1; /* Data length in mode parameter header */ + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[12] &= 0x7f; /* clear PS */ + buf[14] |= 0x04; /* set WCE */ + + init_mode_select_command(sdev, &scsi_dump_cmnd, buf, data_len); + if ((ret = send_command(&scsi_dump_cmnd)) < 0) { + Warn("MODE SELECT failed"); + + init_sense_command(sdev, &scsi_dump_cmnd, buf); + if ((ret = send_command(&scsi_dump_cmnd)) < 0) { + Err("sense failed"); + } + } +} + +/* + * Check whether the dump device is sane enough to handle I/O. + * + * Return value: + * 0: the device is ok + * < 0: the device is not ok + * > 0: Cannot determine + */ +static int +scsi_dump_sanity_check(struct disk_dump_device *dump_device) +{ + struct scsi_device *sdev = dump_device->device; + struct Scsi_Host *host = sdev->host; + int adapter_sanity = 0; + int sanity = 0; + + if (!check_crc_module()) { + Err("checksum error. scsi dump module may be compromised."); + return -EINVAL; + } + /* + * If host's spinlock is already taken, assume it's part + * of crash and skip it. + */ + if (!scsi_device_online(sdev)) { + Warn("device not online: host %d channel %d id %d lun %d", + host->host_no, sdev->channel, sdev->id, sdev->lun); + return -EIO; + } + if (sdev->changed) { + Err("SCSI disk has been changed. Prohibiting further I/O: host %d channel %d id %d lun %d", + host->host_no, sdev->channel, sdev->id, sdev->lun); + return -EIO; + } + + if (host->hostt->dump_sanity_check) { + adapter_sanity = host->hostt->dump_sanity_check(sdev); + if (adapter_sanity < 0) { + Warn("adapter status is not sane"); + return adapter_sanity; + } + } + + if (!spin_is_locked(host->host_lock)) { + sanity = 0; + } else { + Warn("host_lock is held: host %d channel %d id %d lun %d", + host->host_no, sdev->channel, sdev->id, sdev->lun); + return -EIO; + } + return sanity + adapter_sanity; +} + +/* + * Try to reset the host adapter. If the adapter does not have its host reset + * handler, try to use its bus device reset handler. + */ +static int scsi_dump_reset(struct scsi_device *sdev) +{ + struct Scsi_Host *host = sdev->host; + struct scsi_host_template *hostt = host->hostt; + char buf[256]; + int ret, i; + + init_sense_command(sdev, &scsi_dump_cmnd, buf); + + if (hostt->eh_host_reset_handler) { + spin_lock(host->host_lock); + ret = hostt->eh_host_reset_handler(&scsi_dump_cmnd); + } else if (hostt->eh_bus_reset_handler) { + spin_lock(host->host_lock); + ret = hostt->eh_bus_reset_handler(&scsi_dump_cmnd); + } else + return 0; + spin_unlock(host->host_lock); + + if (ret != SUCCESS) { + Err("adapter reset failed"); + return -EIO; + } + + /* bus reset settle time. 5sec for old disk devices */ + for (i = 0; i < 5000; i++) { + diskdump_update(); + mdelay(1); + } + + Dbg("request sense"); + if ((ret = send_command(&scsi_dump_cmnd)) < 0) { + Err("sense failed"); + return -EIO; + } + return 0; +} + +static int +scsi_dump_quiesce(struct disk_dump_device *dump_device) +{ + struct scsi_device *sdev = dump_device->device; + struct Scsi_Host *host = sdev->host; + int ret; + + if (host->hostt->dump_quiesce) { + ret = host->hostt->dump_quiesce(sdev); + if (ret < 0) + return ret; + } + + Dbg("do bus reset"); + if ((ret = scsi_dump_reset(sdev)) < 0) + return ret; + + if (sdev->scsi_level >= SCSI_2) + enable_write_cache(sdev); + + quiesce_ok = 1; + return 0; +} + +static int scsi_dump_rw_block(struct disk_dump_partition *dump_part, int rw, + unsigned long dump_block_nr, void *buf, int len) +{ + struct disk_dump_device *dump_device = dump_part->device; + struct scsi_device *sdev = dump_device->device; + int block_nr = BLOCK_SECTOR(dump_block_nr); + int ret; + + if (!quiesce_ok) { + Err("quiesce not called"); + return -EIO; + } + + ret = init_rw_command(dump_part, sdev, &scsi_dump_cmnd, rw, + block_nr, buf, DUMP_BLOCK_SIZE * len); + if (ret < 0) { + Err("init_rw_command failed"); + return ret; + } + return send_command(&scsi_dump_cmnd); +} + +static int +scsi_dump_shutdown(struct disk_dump_device *dump_device) +{ + struct scsi_device *sdev = dump_device->device; + struct Scsi_Host *host = sdev->host; + + if (sdev->scsi_level >= SCSI_2) { + init_sync_command(sdev, &scsi_dump_cmnd); + send_command(&scsi_dump_cmnd); + } + + if (host->hostt->dump_shutdown) + return host->hostt->dump_shutdown(sdev); + + return 0; +} + +static void *scsi_dump_probe(struct block_device *bdev) +{ + struct scsi_device *sdev; + + sdev = sd_find_scsi_device(bdev); + if (sdev == NULL) + return NULL; + if (!sdev->host->hostt->dump_poll) + return NULL; + + return sdev; +} + + +struct disk_dump_device_ops scsi_dump_device_ops = { + .sanity_check = scsi_dump_sanity_check, + .rw_block = scsi_dump_rw_block, + .quiesce = scsi_dump_quiesce, + .shutdown = scsi_dump_shutdown, +}; + +static int scsi_dump_add_device(struct disk_dump_device *dump_device) +{ + struct scsi_device *sdev; + int error; + + sdev = dump_device->device; + if (!sdev->host->hostt->dump_poll) + return -ENOTSUPP; + + if ((error = scsi_device_get(sdev)) != 0) + return error; + + memcpy(&dump_device->ops, &scsi_dump_device_ops, + sizeof(scsi_dump_device_ops)); + if (sdev->host->max_sectors) { + dump_device->max_blocks = + (sdev->sector_size * sdev->host->max_sectors) + >> DUMP_BLOCK_SHIFT; + } + return 0; +} + +static void scsi_dump_remove_device(struct disk_dump_device *dump_device) +{ + struct scsi_device *sdev = dump_device->device; + + scsi_device_put(sdev); +} + +static struct disk_dump_type scsi_dump_type = { + .probe = scsi_dump_probe, + .add_device = scsi_dump_add_device, + .remove_device = scsi_dump_remove_device, + .owner = THIS_MODULE, +}; + +static int init_scsi_dump(void) +{ + int ret; + + if ((ret = register_disk_dump_type(&scsi_dump_type)) < 0) { + Err("register failed"); + return ret; + } + set_crc_modules(); + return ret; +} + +static void cleanup_scsi_dump(void) +{ + if (unregister_disk_dump_type(&scsi_dump_type) < 0) + Err("register failed"); +} + +module_init(init_scsi_dump); +module_exit(cleanup_scsi_dump); +MODULE_LICENSE("GPL"); diff -Nur linux-2.6.6.org/drivers/scsi/scsi_error.c linux-2.6.6/drivers/scsi/scsi_error.c --- linux-2.6.6.org/drivers/scsi/scsi_error.c 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/scsi_error.c 2004-06-16 19:34:16.000000000 +0900 @@ -402,6 +402,9 @@ **/ static void scsi_eh_done(struct scsi_cmnd *scmd) { + if (crashdump_mode()) + return; + /* * if the timeout handler is already running, then just set the * flag which says we finished late, and return. we have no diff -Nur linux-2.6.6.org/drivers/scsi/scsi_syms.c linux-2.6.6/drivers/scsi/scsi_syms.c --- linux-2.6.6.org/drivers/scsi/scsi_syms.c 2004-06-04 21:22:21.000000000 +0900 +++ linux-2.6.6/drivers/scsi/scsi_syms.c 2004-06-16 19:34:16.000000000 +0900 @@ -25,6 +25,7 @@ #include "scsi.h" #include "scsi_logging.h" +#include "scsi_priv.h" /* @@ -107,3 +108,5 @@ */ EXPORT_SYMBOL(scsi_add_timer); EXPORT_SYMBOL(scsi_delete_timer); + +EXPORT_SYMBOL(scsi_decide_disposition); diff -Nur linux-2.6.6.org/drivers/scsi/sd.c linux-2.6.6/drivers/scsi/sd.c --- linux-2.6.6.org/drivers/scsi/sd.c 2004-06-04 21:22:22.000000000 +0900 +++ linux-2.6.6/drivers/scsi/sd.c 2004-06-16 19:34:16.000000000 +0900 @@ -680,6 +680,24 @@ .revalidate_disk = sd_revalidate_disk, }; +#if defined(CONFIG_DISKDUMP) || defined(CONFIG_DISKDUMP_MODULE) +struct scsi_device *sd_find_scsi_device(struct block_device *bdev) +{ + struct gendisk *disk = bdev->bd_disk; + + /* Check whether dev is scsi or not */ + if (!disk || (disk->fops != &sd_fops)) + return NULL; + + if(disk->private_data) + return scsi_disk(disk)->device; + else + return NULL; +} + +EXPORT_SYMBOL(sd_find_scsi_device); +#endif + /** * sd_rw_intr - bottom half handler: called when the lower level * driver has completed (successfully or otherwise) a scsi command. diff -Nur linux-2.6.6.org/include/scsi/scsi_dump.h linux-2.6.6/include/scsi/scsi_dump.h --- linux-2.6.6.org/include/scsi/scsi_dump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.6/include/scsi/scsi_dump.h 2004-06-16 19:34:16.000000000 +0900 @@ -0,0 +1,31 @@ +#ifndef _SCSI_DUMP_H +#define _SCSI_DUMP_H + +/* + * linux/include/scsi/scsi_dump.h + * + * Copyright (C) 2004 FUJITSU LIMITED + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* sd.c function */ +extern struct scsi_device *sd_find_scsi_device(struct block_device *); + +#endif /* _SCSI_DUMP_H */ diff -Nur linux-2.6.6.org/include/scsi/scsi_host.h linux-2.6.6/include/scsi/scsi_host.h --- linux-2.6.6.org/include/scsi/scsi_host.h 2004-06-04 21:22:02.000000000 +0900 +++ linux-2.6.6/include/scsi/scsi_host.h 2004-06-16 19:34:16.000000000 +0900 @@ -345,6 +345,47 @@ * module_init/module_exit. */ struct list_head legacy_hosts; + +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) + /* operations for dump */ + + /* + * dump_sanity_check() checks if the selected device works normally. + * A device which returns an error status will not be selected as + * the dump device. + * + * Status: OPTIONAL + */ + int (* dump_sanity_check)(struct scsi_device *); + + /* + * dump_quiesce() is called after the device is selected as the + * dump device. Usually, host reset is executed and Write Cache + * Enable bit of the disk device is temporarily set for the + * dump operation. + * + * Status: OPTIONAL + */ + int (* dump_quiesce)(struct scsi_device *); + + /* + * dump_shutdown() is called after dump is completed. Usually + * "SYNCHRONIZE CACHE" command is issued to the disk. + * + * Status: OPTIONAL + */ + int (* dump_shutdown)(struct scsi_device *); + + /* + * dump_poll() should call the interrupt handler. It is called + * repeatedly after queuecommand() is issued, and until the command + * is completed. If the low level device driver support crash dump, + * it must have this routine. + * + * Status: OPTIONAL + */ + void (* dump_poll)(struct scsi_device *); +#endif }; /* ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/4][Diskdump]Update patches 2004-06-16 12:39 [PATCH 0/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:45 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:46 ` [PATCH 2/4][Diskdump]Update patches Takao Indoh @ 2004-06-16 12:48 ` Takao Indoh 2004-06-16 12:50 ` [PATCH 4/4][Diskdump]Update patches Takao Indoh 2004-06-16 17:25 ` [PATCH 0/4][Diskdump]Update patches Jeff Garzik 4 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-06-16 12:48 UTC (permalink / raw) To: linux-kernel This is a patch for aic7xxx driver. diff -Nur linux-2.6.6.org/drivers/scsi/aic7xxx/aic7xxx_osm.c linux-2.6.6/drivers/scsi/aic7xxx/aic7xxx_osm.c --- linux-2.6.6.org/drivers/scsi/aic7xxx/aic7xxx_osm.c 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/aic7xxx/aic7xxx_osm.c 2004-06-16 19:34:16.000000000 +0900 @@ -774,6 +774,10 @@ static int ahc_linux_bus_reset(Scsi_Cmnd *); static int ahc_linux_dev_reset(Scsi_Cmnd *); static int ahc_linux_abort(Scsi_Cmnd *); +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +static int ahc_linux_sanity_check(struct scsi_device *); +static void ahc_linux_poll(struct scsi_device *); +#endif /* * Calculate a safe value for AHC_NSEG (as expressed through ahc_linux_nseg). @@ -1313,6 +1317,10 @@ .slave_alloc = ahc_linux_slave_alloc, .slave_configure = ahc_linux_slave_configure, .slave_destroy = ahc_linux_slave_destroy, +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) + .dump_sanity_check = ahc_linux_sanity_check, + .dump_poll = ahc_linux_poll, +#endif }; /**************************** Tasklet Handler *********************************/ @@ -3863,6 +3871,42 @@ return IRQ_RETVAL(ours); } +static int +ahc_linux_sanity_check(struct scsi_device *device) +{ + struct ahc_softc *ahc; + struct ahc_linux_device *dev; + + ahc = *(struct ahc_softc **)device->host->hostdata; + dev = ahc_linux_get_device(ahc, device->channel, + device->id, device->lun, + /*alloc*/FALSE); + if (dev == NULL) + return -ENXIO; + if (ahc->platform_data->qfrozen || dev->qfrozen) + return -EBUSY; + if (spin_is_locked(&ahc->platform_data->spin_lock)) + return -EBUSY; + return 0; +} + +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +static void +ahc_linux_poll(struct scsi_device *device) +{ + struct ahc_softc *ahc; + struct ahc_linux_device *dev; + + ahc = *(struct ahc_softc **)device->host->hostdata; + ahc_intr(ahc); + while ((dev = ahc_linux_next_device_to_run(ahc)) != NULL) { + TAILQ_REMOVE(&ahc->platform_data->device_runq, dev, links); + dev->flags &= ~AHC_DEV_ON_RUN_LIST; + ahc_linux_check_device_queue(ahc, dev); + } + ahc_linux_run_complete_queue(ahc); +} + void ahc_platform_flushwork(struct ahc_softc *ahc) { @@ -3870,6 +3914,7 @@ while (ahc_linux_run_complete_queue(ahc) != NULL) ; } +#endif static struct ahc_linux_target* ahc_linux_alloc_target(struct ahc_softc *ahc, u_int channel, u_int target) diff -Nur linux-2.6.6.org/drivers/scsi/aic7xxx/aic7xxx_osm.h linux-2.6.6/drivers/scsi/aic7xxx/aic7xxx_osm.h --- linux-2.6.6.org/drivers/scsi/aic7xxx/aic7xxx_osm.h 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/aic7xxx/aic7xxx_osm.h 2004-06-16 19:34:16.000000000 +0900 @@ -67,6 +67,7 @@ #include <linux/smp_lock.h> #include <linux/version.h> #include <linux/module.h> +#include <linux/diskdumplib.h> #include <asm/byteorder.h> #include <asm/io.h> @@ -82,6 +83,8 @@ #include <linux/malloc.h> #endif +#include <scsi/scsi_dump.h> + /* Core SCSI definitions */ #define AIC_LIB_PREFIX ahc #include "scsi.h" @@ -253,6 +256,22 @@ */ #define ahc_dmamap_sync(ahc, dma_tag, dmamap, offset, len, op) + +/******************************** Disk dump ***********************************/ +#if defined(CONFIG_DISKDUMP) || defined(CONFIG_DISKDUMP_MODULE) +#undef add_timer +#define add_timer diskdump_add_timer +#undef del_timer_sync +#define del_timer_sync diskdump_del_timer +#undef del_timer +#define del_timer diskdump_del_timer +#undef mod_timer +#define mod_timer diskdump_mod_timer + +#define tasklet_schedule diskdump_tasklet_schedule +#endif + + /************************** Timer DataStructures ******************************/ typedef struct timer_list ahc_timer_t; ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 4/4][Diskdump]Update patches 2004-06-16 12:39 [PATCH 0/4][Diskdump]Update patches Takao Indoh ` (2 preceding siblings ...) 2004-06-16 12:48 ` [PATCH 3/4][Diskdump]Update patches Takao Indoh @ 2004-06-16 12:50 ` Takao Indoh 2004-06-16 17:25 ` [PATCH 0/4][Diskdump]Update patches Jeff Garzik 4 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-06-16 12:50 UTC (permalink / raw) To: linux-kernel This is a patch for aic79xx driver. diff -Nur linux-2.6.6.org/drivers/scsi/aic7xxx/aic79xx_osm.c linux-2.6.6/drivers/scsi/aic7xxx/aic79xx_osm.c --- linux-2.6.6.org/drivers/scsi/aic7xxx/aic79xx_osm.c 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/aic7xxx/aic79xx_osm.c 2004-06-16 19:34:16.000000000 +0900 @@ -786,6 +786,10 @@ static int ahd_linux_bus_reset(Scsi_Cmnd *); static int ahd_linux_dev_reset(Scsi_Cmnd *); static int ahd_linux_abort(Scsi_Cmnd *); +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +static int ahd_linux_sanity_check(struct scsi_device *); +static void ahd_linux_poll(struct scsi_device *); +#endif /* * Calculate a safe value for AHD_NSEG (as expressed through ahd_linux_nseg). @@ -1684,6 +1688,10 @@ .slave_alloc = ahd_linux_slave_alloc, .slave_configure = ahd_linux_slave_configure, .slave_destroy = ahd_linux_slave_destroy, +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) + .dump_sanity_check = ahd_linux_sanity_check, + .dump_poll = ahd_linux_poll, +#endif }; /**************************** Tasklet Handler *********************************/ @@ -4190,6 +4198,41 @@ return IRQ_RETVAL(ours); } +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +static int +ahd_linux_sanity_check(struct scsi_device *device) +{ + struct ahd_softc *ahd; + struct ahd_linux_device *dev; + + ahd = *(struct ahd_softc **)device->host->hostdata; + dev = ahd_linux_get_device(ahd, device->channel, + device->id, device->lun, + /*alloc*/FALSE); + + if (dev == NULL) + return -ENXIO; + if (ahd->platform_data->qfrozen || dev->qfrozen) + return -EBUSY; + if (spin_is_locked(&ahd->platform_data->spin_lock)) + return -EBUSY; + return 0; +} + +static void +ahd_linux_poll(struct scsi_device *device) +{ + struct ahd_softc *ahd; + int ours; + + ahd = *(struct ahd_softc **)device->host->hostdata; + ours = ahd_intr(ahd); + if (ahd_linux_next_device_to_run(ahd) != NULL) + ahd_schedule_runq(ahd); + ahd_linux_run_complete_queue(ahd); +} +#endif + void ahd_platform_flushwork(struct ahd_softc *ahd) { diff -Nur linux-2.6.6.org/drivers/scsi/aic7xxx/aic79xx_osm.h linux-2.6.6/drivers/scsi/aic7xxx/aic79xx_osm.h --- linux-2.6.6.org/drivers/scsi/aic7xxx/aic79xx_osm.h 2004-06-04 21:22:20.000000000 +0900 +++ linux-2.6.6/drivers/scsi/aic7xxx/aic79xx_osm.h 2004-06-16 19:34:16.000000000 +0900 @@ -50,6 +50,9 @@ #include <linux/smp_lock.h> #include <linux/version.h> #include <linux/module.h> +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +#include <linux/diskdumplib.h> +#endif #include <asm/byteorder.h> #include <asm/io.h> @@ -65,6 +68,10 @@ #include <linux/malloc.h> #endif +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +#include <scsi/scsi_dump.h> +#endif + /* Core SCSI definitions */ #define AIC_LIB_PREFIX ahd #include "scsi.h" @@ -96,6 +103,20 @@ /* No debugging code. */ #endif +/********************************** Disk Dump *********************************/ +#if defined(CONFIG_SCSI_DUMP) || defined(CONFIG_SCSI_DUMP_MODULE) +#undef add_timer +#define add_timer diskdump_add_timer +#undef del_timer_sync +#define del_timer_sync diskdump_del_timer +#undef del_timer +#define del_timer diskdump_del_timer +#undef mod_timer +#define mod_timer diskdump_mod_timer + +#define tasklet_schedule diskdump_tasklet_schedule +#endif + /********************************** Misc Macros *******************************/ #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) #define powerof2(x) ((((x)-1)&(x))==0) ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 0/4][Diskdump]Update patches 2004-06-16 12:39 [PATCH 0/4][Diskdump]Update patches Takao Indoh ` (3 preceding siblings ...) 2004-06-16 12:50 ` [PATCH 4/4][Diskdump]Update patches Takao Indoh @ 2004-06-16 17:25 ` Jeff Garzik 4 siblings, 0 replies; 9+ messages in thread From: Jeff Garzik @ 2004-06-16 17:25 UTC (permalink / raw) To: Takao Indoh; +Cc: linux-kernel Takao Indoh wrote: > static inline void tasklet_schedule(struct tasklet_struct *t) > { > + if(crashdump_mode()) { > + diskdump_tasklet_schedule(t); > + return; > + } > + I would suggest if (unlikely(crashdump_mode())) ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 0/4][Diskdump]Update patches
@ 2004-06-22 13:47 Takao Indoh
2004-06-22 13:59 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh
0 siblings, 1 reply; 9+ messages in thread
From: Takao Indoh @ 2004-06-22 13:47 UTC (permalink / raw)
To: linux-kernel; +Cc: Ingo Molnar, Christoph Hellwig, Andi Kleen
Hi!
I update the Diskdump patches!
- fix timer problem
- support kernel 2.6.7
Source code of tool(diskdumptuils) can be downloaded from
http://sourceforge.net/projects/lkdump
TODO:
- Replace proc interface with sysfs.
- Merge scsi_dump with scsi_mod.
I solved the timer problem by the method which Ingo Molnar proposed.
On Thu, 17 Jun 2004 14:13:56 +0200, Ingo Molnar wrote:
>but there's another possible method (suggested by Alan Cox) that
>requires no changes to the timer API hotpaths: 'clear' all timer lists
>upon a crash [once all CPUs have stopped and irqs are disabled] and just
>let the drivers use the normal timer APIs. Drive timer execution via a
>polling method.
>
>this basically approximates your polling based implementation but uses
>the existing kernel timer data structures and timer mechanism so should
>be robust and compatible. It doesnt rely on any previous state (because
>all currently pending timers are discarded) so it's as crash-safe as
>possible.
The following is core part of patches related to timer problem.
The complete patch is posted later.
diff -Nur linux-2.6.7.org/include/linux/interrupt.h linux-2.6.7/include/linux/interrupt.h
--- linux-2.6.7.org/include/linux/interrupt.h 2004-06-22 10:27:34.000000000 +0900
+++ linux-2.6.7/include/linux/interrupt.h 2004-06-22 22:26:39.000000000 +0900
@@ -246,4 +246,8 @@
extern int probe_irq_off(unsigned long); /* returns 0 or negative on failure */
extern unsigned int probe_irq_mask(unsigned long); /* returns mask of ISA interrupts */
+
+extern void dump_clear_tasklet(void);
+extern void dump_run_tasklet(void);
+
#endif
diff -Nur linux-2.6.7.org/include/linux/timer.h linux-2.6.7/include/linux/timer.h
--- linux-2.6.7.org/include/linux/timer.h 2004-06-22 10:27:31.000000000 +0900
+++ linux-2.6.7/include/linux/timer.h 2004-06-22 22:26:39.000000000 +0900
@@ -99,4 +99,7 @@
extern void run_local_timers(void);
extern void it_real_fn(unsigned long);
+extern void dump_clear_timers(void);
+extern void dump_run_timers(void);
+
#endif
diff -Nur linux-2.6.7.org/include/linux/workqueue.h linux-2.6.7/include/linux/workqueue.h
--- linux-2.6.7.org/include/linux/workqueue.h 2004-06-22 10:27:35.000000000 +0900
+++ linux-2.6.7/include/linux/workqueue.h 2004-06-22 22:26:39.000000000 +0900
@@ -84,4 +84,7 @@
return ret;
}
+extern void dump_clear_workqueue(void);
+extern void dump_run_workqueue(void);
+
#endif
diff -Nur linux-2.6.7.org/kernel/softirq.c linux-2.6.7/kernel/softirq.c
--- linux-2.6.7.org/kernel/softirq.c 2004-06-22 10:27:25.000000000 +0900
+++ linux-2.6.7/kernel/softirq.c 2004-06-22 22:26:39.000000000 +0900
@@ -314,6 +314,38 @@
EXPORT_SYMBOL(tasklet_kill);
+struct tasklet_head saved_tasklet;
+
+void dump_clear_tasklet(void)
+{
+ saved_tasklet.list = __get_cpu_var(tasklet_vec).list;
+ __get_cpu_var(tasklet_vec).list = NULL;
+}
+
+EXPORT_SYMBOL(dump_clear_tasklet);
+
+void dump_run_tasklet(void)
+{
+ struct tasklet_struct *list;
+
+ list = __get_cpu_var(tasklet_vec).list;
+ __get_cpu_var(tasklet_vec).list = NULL;
+
+ while (list) {
+ struct tasklet_struct *t = list;
+ list = list->next;
+
+ if (!atomic_read(&t->count) &&
+ (test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)))
+ t->func(t->data);
+
+ t->next = __get_cpu_var(tasklet_vec).list;
+ __get_cpu_var(tasklet_vec).list = t;
+ }
+}
+
+EXPORT_SYMBOL(dump_run_tasklet);
+
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
diff -Nur linux-2.6.7.org/kernel/timer.c linux-2.6.7/kernel/timer.c
--- linux-2.6.7.org/kernel/timer.c 2004-06-22 10:27:25.000000000 +0900
+++ linux-2.6.7/kernel/timer.c 2004-06-22 22:26:39.000000000 +0900
@@ -31,6 +31,7 @@
#include <linux/time.h>
#include <linux/jiffies.h>
#include <linux/cpu.h>
+#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
@@ -424,7 +425,6 @@
{
struct timer_list *timer;
- spin_lock_irq(&base->lock);
while (time_after_eq(jiffies, base->timer_jiffies)) {
struct list_head work_list = LIST_HEAD_INIT(work_list);
struct list_head *head = &work_list;
@@ -460,6 +460,12 @@
}
}
set_running_timer(base, NULL);
+}
+
+static inline void _run_timers(tvec_base_t *base)
+{
+ spin_lock_irq(&base->lock);
+ __run_timers(base);
spin_unlock_irq(&base->lock);
}
@@ -909,7 +915,7 @@
tvec_base_t *base = &__get_cpu_var(tvec_bases);
if (time_after_eq(jiffies, base->timer_jiffies))
- __run_timers(base);
+ _run_timers(base);
}
/*
@@ -1105,6 +1111,12 @@
struct timer_list timer;
unsigned long expire;
+ if (unlikely(crashdump_mode())) {
+ mdelay(timeout);
+ set_current_state(TASK_RUNNING);
+ return timeout;
+ }
+
switch (timeout)
{
case MAX_SCHEDULE_TIMEOUT:
@@ -1308,7 +1320,7 @@
return 0;
}
-static void __devinit init_timers_cpu(int cpu)
+static void /* __devinit */ init_timers_cpu(int cpu)
{
int j;
tvec_base_t *base;
@@ -1327,6 +1339,27 @@
base->timer_jiffies = jiffies;
}
+static tvec_base_t saved_tvec_base;
+
+void dump_clear_timers(void)
+{
+ tvec_base_t *base = &per_cpu(tvec_bases, smp_processor_id());
+
+ memcpy(&saved_tvec_base, base, sizeof(saved_tvec_base));
+ init_timers_cpu(smp_processor_id());
+}
+
+EXPORT_SYMBOL(dump_clear_timers);
+
+void dump_run_timers(void)
+{
+ tvec_base_t *base = &__get_cpu_var(tvec_bases);
+
+ __run_timers(base);
+}
+
+EXPORT_SYMBOL(dump_run_timers);
+
#ifdef CONFIG_HOTPLUG_CPU
static int migrate_timer_list(tvec_base_t *new_base, struct list_head *head)
{
diff -Nur linux-2.6.7.org/kernel/workqueue.c linux-2.6.7/kernel/workqueue.c
--- linux-2.6.7.org/kernel/workqueue.c 2004-06-22 10:27:25.000000000 +0900
+++ linux-2.6.7/kernel/workqueue.c 2004-06-22 22:26:39.000000000 +0900
@@ -424,6 +424,37 @@
}
+struct cpu_workqueue_struct saved_cwq;
+
+void dump_clear_workqueue(void)
+{
+ int cpu = smp_processor_id();
+ struct cpu_workqueue_struct *cwq = keventd_wq->cpu_wq + cpu;
+
+ memcpy(&saved_cwq, cwq, sizeof(saved_cwq));
+ spin_lock_init(&cwq->lock);
+ INIT_LIST_HEAD(&cwq->worklist);
+ init_waitqueue_head(&cwq->more_work);
+ init_waitqueue_head(&cwq->work_done);
+}
+
+void dump_run_workqueue(void)
+{
+ struct cpu_workqueue_struct *cwq;
+
+ cwq = keventd_wq->cpu_wq + smp_processor_id();
+ while (!list_empty(&cwq->worklist)) {
+ struct work_struct *work = list_entry(cwq->worklist.next,
+ struct work_struct, entry);
+ void (*f) (void *) = work->func;
+ void *data = work->data;
+
+ list_del_init(cwq->worklist.next);
+ clear_bit(0, &work->pending);
+ f(data);
+ }
+}
+
#ifdef CONFIG_HOTPLUG_CPU
/* Take the work from this (downed) CPU. */
static void take_over_work(struct workqueue_struct *wq, unsigned int cpu)
@@ -507,3 +538,6 @@
EXPORT_SYMBOL(schedule_delayed_work);
EXPORT_SYMBOL(flush_scheduled_work);
+EXPORT_SYMBOL(dump_clear_workqueue);
+EXPORT_SYMBOL(dump_run_workqueue);
+
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 1/4][Diskdump]Update patches 2004-06-22 13:47 Takao Indoh @ 2004-06-22 13:59 ` Takao Indoh 0 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-06-22 13:59 UTC (permalink / raw) To: linux-kernel This is a patch for diskdump common layer. diff -Nur linux-2.6.7.org/arch/i386/kernel/nmi.c linux-2.6.7/arch/i386/kernel/nmi.c --- linux-2.6.7.org/arch/i386/kernel/nmi.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/nmi.c 2004-06-22 22:26:39.237364592 +0900 @@ -524,3 +524,4 @@ EXPORT_SYMBOL(release_lapic_nmi); EXPORT_SYMBOL(disable_timer_nmi_watchdog); EXPORT_SYMBOL(enable_timer_nmi_watchdog); +EXPORT_SYMBOL(touch_nmi_watchdog); diff -Nur linux-2.6.7.org/arch/i386/kernel/reboot.c linux-2.6.7/arch/i386/kernel/reboot.c --- linux-2.6.7.org/arch/i386/kernel/reboot.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/reboot.c 2004-06-22 22:26:39.238364440 +0900 @@ -252,7 +252,8 @@ * Stop all CPUs and turn off local APICs and the IO-APIC, so * other OSs see a clean IRQ state. */ - smp_send_stop(); + if (!crashdump_mode()) + smp_send_stop(); #elif defined(CONFIG_X86_LOCAL_APIC) if (cpu_has_apic) { local_irq_disable(); diff -Nur linux-2.6.7.org/arch/i386/kernel/smp.c linux-2.6.7/arch/i386/kernel/smp.c --- linux-2.6.7.org/arch/i386/kernel/smp.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/smp.c 2004-06-22 22:26:39.238364440 +0900 @@ -520,7 +520,8 @@ return 0; /* Can deadlock when called with interrupts disabled */ - WARN_ON(irqs_disabled()); + if (!crashdump_mode()) + WARN_ON(irqs_disabled()); data.func = func; data.info = info; diff -Nur linux-2.6.7.org/arch/i386/kernel/traps.c linux-2.6.7/arch/i386/kernel/traps.c --- linux-2.6.7.org/arch/i386/kernel/traps.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/traps.c 2004-06-22 22:26:39.239364288 +0900 @@ -301,7 +301,8 @@ int nl = 0; console_verbose(); - spin_lock_irq(&die_lock); + if (!crashdump_mode()) + spin_lock_irq(&die_lock); bust_spinlocks(1); handle_BUG(regs); printk(KERN_ALERT "%s: %04lx [#%d]\n", str, err & 0xffff, ++die_counter); @@ -320,6 +321,7 @@ if (nl) printk("\n"); show_registers(regs); + try_crashdump(regs); bust_spinlocks(0); spin_unlock_irq(&die_lock); if (in_interrupt()) diff -Nur linux-2.6.7.org/arch/i386/mm/init.c linux-2.6.7/arch/i386/mm/init.c --- linux-2.6.7.org/arch/i386/mm/init.c 2004-06-22 10:27:17.000000000 +0900 +++ linux-2.6.7/arch/i386/mm/init.c 2004-06-22 22:26:39.240364136 +0900 @@ -168,7 +168,7 @@ extern int is_available_memory(efi_memory_desc_t *); -static inline int page_is_ram(unsigned long pagenr) +int page_is_ram(unsigned long pagenr) { int i; unsigned long addr, end; @@ -205,6 +205,7 @@ } return 0; } +EXPORT_SYMBOL(page_is_ram); #ifdef CONFIG_HIGHMEM pte_t *kmap_pte; diff -Nur linux-2.6.7.org/drivers/block/Kconfig linux-2.6.7/drivers/block/Kconfig --- linux-2.6.7.org/drivers/block/Kconfig 2004-06-22 10:27:42.000000000 +0900 +++ linux-2.6.7/drivers/block/Kconfig 2004-06-22 22:26:39.240364136 +0900 @@ -347,6 +347,11 @@ your machine, or if you want to have a raid or loopback device bigger than 2TB. Otherwise say N. +config DISKDUMP + tristate "Disk dump support" + ---help--- + Disk dump support. + source "drivers/s390/block/Kconfig" endmenu diff -Nur linux-2.6.7.org/drivers/block/Makefile linux-2.6.7/drivers/block/Makefile --- linux-2.6.7.org/drivers/block/Makefile 2004-06-22 10:27:43.000000000 +0900 +++ linux-2.6.7/drivers/block/Makefile 2004-06-22 22:26:39.241363984 +0900 @@ -43,3 +43,4 @@ obj-$(CONFIG_VIODASD) += viodasd.o obj-$(CONFIG_BLK_DEV_CARMEL) += carmel.o +obj-$(CONFIG_DISKDUMP) += diskdump.o diff -Nur linux-2.6.7.org/drivers/block/diskdump.c linux-2.6.7/drivers/block/diskdump.c --- linux-2.6.7.org/drivers/block/diskdump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/drivers/block/diskdump.c 2004-06-22 22:26:39.243363680 +0900 @@ -0,0 +1,1047 @@ +/* + * linux/drivers/block/diskdump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Copyright (C) 2002 Red Hat, Inc. + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + * Some codes were derived from netdump and copyright belongs to + * Red Hat, Inc. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/file.h> +#include <linux/reboot.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/highmem.h> +#include <linux/utsname.h> +#include <linux/console.h> +#include <linux/smp_lock.h> +#include <linux/nmi.h> +#include <linux/genhd.h> +#include <linux/crc32.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/diskdump.h> +#include <asm/diskdump.h> + +#define Dbg(x, ...) pr_debug("disk_dump: " x "\n", ## __VA_ARGS__) +#define Err(x, ...) pr_err ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Warn(x, ...) pr_warn ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Info(x, ...) pr_info ("disk_dump: " x "\n", ## __VA_ARGS__) + +#define ROUNDUP(x, y) (((x) + ((y)-1))/(y)) + +/* 512byte sectors to blocks */ +#define SECTOR_BLOCK(s) ((s) >> (DUMP_BLOCK_SHIFT - 9)) + +static unsigned int fallback_on_err = 1; +static unsigned int allow_risky_dumps = 1; +static unsigned int block_order = 2; +static int sample_rate = 8; +module_param(fallback_on_err, uint, 0); +module_param(allow_risky_dumps, uint, 0); +module_param(block_order, uint, 0); +module_param(sample_rate, int, 0); + +static unsigned long timestamp_1sec; +static uint32_t module_crc; +static char *scratch; +static struct disk_dump_header dump_header; +static struct disk_dump_sub_header dump_sub_header; + +/* Registered dump devices */ +static LIST_HEAD(disk_dump_devices); + +/* Registered dump types, e.g. SCSI, ... */ +static LIST_HEAD(disk_dump_types); + +static DECLARE_MUTEX(disk_dump_mutex); + +static unsigned int header_blocks; /* The size of all headers */ +static unsigned int bitmap_blocks; /* The size of bitmap header */ +static unsigned int total_ram_blocks; /* The size of memory */ +static unsigned int total_blocks; /* The sum of above */ + +struct notifier_block *disk_dump_notifier_list; + +unsigned long volatile diskdump_base_jiffies; +static unsigned long long timestamp_base; +static unsigned long timestamp_hz; + +extern int panic_timeout; +extern unsigned long max_pfn; + +#if CONFIG_SMP +static void freeze_cpu(void *dummy) +{ + unsigned int cpu = smp_processor_id(); + + dump_header.tasks[cpu] = current; + + platform_freeze_cpu(); +} +#endif + +static int lapse = 0; /* 200msec unit */ + +static inline unsigned long eta(unsigned long nr, unsigned long maxnr) +{ + unsigned long long eta; + + eta = ((maxnr << 8) / nr) * (unsigned long long)lapse; + + return (unsigned long)(eta >> 8) - lapse; +} + +static inline void print_status(unsigned int nr, unsigned int maxnr) +{ + static char *spinner = "/|\\-"; + static unsigned long long prev_timestamp = 0; + unsigned long long timestamp; + + platform_timestamp(timestamp); + + if (timestamp - prev_timestamp > (timestamp_1sec/5)) { + prev_timestamp = timestamp; + lapse++; + printk("%u/%u %lu ETA %c \r", + nr, maxnr, eta(nr, maxnr) / 5, spinner[lapse & 3]); + } +} + +static inline void clear_status(int nr, int maxnr) +{ + printk(" \r"); + lapse = 0; +} + +/* + * Checking the signature on a block. The format is as follows. + * + * 1st word = 'disk' + * 2nd word = 'dump' + * 3rd word = block number + * 4th word = ((block number + 7) * 11) & 0xffffffff + * 5th word = ((4th word + 7)* 11) & 0xffffffff + * .. + * + * Return 1 if the signature is correct, else return 0 + */ +static int check_block_signature(void *buf, unsigned int block_nr) +{ + int word_nr = PAGE_SIZE / sizeof(int); + int *words = buf; + unsigned int val; + int i; + + if (memcmp(buf, DUMP_PARTITION_SIGNATURE, sizeof(*words))) + return 0; + + val = block_nr; + for (i = 2; i < word_nr; i++) { + if (words[i] != val) + return 0; + val = (val + 7) * 11; + } + + return 1; +} + +/* + * Read one block into the dump partition + */ +static int read_blocks(struct disk_dump_partition *dump_part, unsigned int nr, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, READ, nr, buf, len); + if (ret < 0) { + Err("read error on block %u", nr); + return ret; + } + return 0; +} + +static int write_blocks(struct disk_dump_partition *dump_part, unsigned int offs, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, WRITE, offs, buf, len); + if (ret < 0) { + Err("write error on block %u", offs); + return ret; + } + return 0; +} + +/* + * Initialize the common header + */ + +/* + * Write the common header + */ +static int write_header(struct disk_dump_partition *dump_part) +{ + memset(scratch, '\0', PAGE_SIZE); + memcpy(scratch, &dump_header, sizeof(dump_header)); + + return write_blocks(dump_part, 1, scratch, 1); +} + +/* + * Check the signaures in all blocks of the dump partition + * Return 1 if the signature is correct, else return 0 + */ +static int check_dump_partition(struct disk_dump_partition *dump_part, unsigned int partition_size) +{ + unsigned int blk; + int ret; + unsigned int chunk_blks, skips; + int i; + + if (sample_rate < 0) /* No check */ + return 1; + + /* + * If the device has limitations of transfer size, use it. + */ + chunk_blks = 1 << block_order; + if (dump_part->device->max_blocks) + chunk_blks = min(chunk_blks, dump_part->device->max_blocks); + skips = chunk_blks << sample_rate; + + lapse = 0; + for (blk = 0; blk < partition_size; blk += skips) { + unsigned int len; +redo: + len = min(chunk_blks, partition_size - blk); + if ((ret = read_blocks(dump_part, blk, scratch, len)) < 0) + return 0; + print_status(blk + 1, partition_size); + for (i = 0; i < len; i++) + if (!check_block_signature(scratch + i * DUMP_BLOCK_SIZE, blk + i)) { + Err("bad signature in block %u", blk + i); + return 0; + } + } + /* Check the end of the dump partition */ + if (blk - skips + chunk_blks < partition_size) { + blk = partition_size - chunk_blks; + goto redo; + } + clear_status(blk, partition_size); + return 1; +} + +/* + * Write memory bitmap after location of dump headers. + */ +#define IDX2PAGENR(nr, byte, bit) (((nr) * PAGE_SIZE + (byte)) * 8 + (bit)) +static int write_bitmap(struct disk_dump_partition *dump_part, unsigned int bitmap_offset, unsigned int bitmap_blocks) +{ + unsigned int nr; + int bit, byte; + int ret = 0; + unsigned char val; + + for (nr = 0; nr < bitmap_blocks; nr++) { + for (byte = 0; byte < PAGE_SIZE; byte++) { + val = 0; + for (bit = 0; bit < 8; bit++) + if (page_is_ram(IDX2PAGENR(nr, byte, bit))) + val |= (1 << bit); + scratch[byte] = (char)val; + } + if ((ret = write_blocks(dump_part, bitmap_offset + nr, scratch, 1)) < 0) { + Err("I/O error %d on block %u", ret, bitmap_offset + nr); + break; + } + } + return ret; +} + +/* + * Write whole memory to dump partition. + * Return value is the number of writen blocks. + */ +static int write_memory(struct disk_dump_partition *dump_part, int offset, unsigned int max_blocks_written, unsigned int *blocks_written) +{ + char *kaddr; + unsigned int blocks = 0; + struct page *page; + unsigned int nr; + int ret = 0; + int blk_in_chunk = 0; + + for (nr = 0; nr < max_pfn; nr++) { + print_status(blocks, max_blocks_written); + + if (!page_is_ram(nr)) + continue; + + if (blocks >= max_blocks_written) { + Warn("dump device is too small. %lu pages were not saved", max_pfn - blocks); + goto out; + } + page = pfn_to_page(nr); + kaddr = (char *)kmap_atomic(page, KM_DISKDUMP); + /* + * need to copy because adapter drivers use virt_to_bus() + */ + memcpy(scratch + blk_in_chunk * PAGE_SIZE, kaddr, PAGE_SIZE); + blk_in_chunk++; + blocks++; + kunmap_atomic(kaddr, KM_DISKDUMP); + + if (blk_in_chunk >= (1 << block_order)) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) { + Err("I/O error %d on block %u", ret, offset); + break; + } + offset += blk_in_chunk; + blk_in_chunk = 0; + } + } + if (ret >= 0 && blk_in_chunk > 0) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) + Err("I/O error %d on block %u", ret, offset); + } + +out: + clear_status(nr, max_blocks_written); + + *blocks_written = blocks; + return ret; +} + +/* + * Select most suitable dump device. sanity_check() returns the state + * of each dump device. 0 means OK, negative value means NG, and + * positive value means it maybe work. select_dump_partition() first + * try to select a sane device and if it has no sane device and + * allow_risky_dumps is set, it select one from maybe OK devices. + * + * XXX We cannot handle multiple partitions yet. + */ +static struct disk_dump_partition *select_dump_partition(void) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + int sanity; + int strict_check = 1; + +redo: + /* + * Select a sane polling driver. + */ + list_for_each_entry(dump_device, &disk_dump_devices, list) { + sanity = 0; + if (dump_device->ops.sanity_check) + sanity = dump_device->ops.sanity_check(dump_device); + if (sanity < 0 || (sanity > 0 && strict_check)) + continue; + list_for_each_entry(dump_part, &dump_device->partitions, list) + return dump_part; + } + if (allow_risky_dumps && strict_check) { + strict_check = 0; + goto redo; + } + return NULL; +} + +void diskdump_update(void) +{ + unsigned long long t; + + touch_nmi_watchdog(); + + /* update jiffies */ + platform_timestamp(t); + while (t > timestamp_base + timestamp_hz) { + timestamp_base += timestamp_hz; + jiffies++; + platform_timestamp(t); + } + + dump_run_timers(); + dump_run_tasklet(); + dump_run_workqueue(); +} + +EXPORT_SYMBOL(diskdump_update); + + +static void disk_dump(struct pt_regs *regs, void *platform_arg) +{ + unsigned long flags; + int ret = -EIO; + struct pt_regs myregs; + unsigned int max_written_blocks, written_blocks; + int i; + struct disk_dump_device *dump_device = NULL; + struct disk_dump_partition *dump_part = NULL; + unsigned long long t; + + /* Inhibit interrupt and stop other CPUs */ + local_save_flags(flags); + local_irq_disable(); + + /* + * Check the checksum of myself + */ + if (down_trylock(&disk_dump_mutex)) { + Err("down_trylock(disk_dump_mutex) failed."); + goto done; + } + + if (!check_crc_module()) { + Err("checksum error. diskdump common module may be compromised."); + goto done; + } + + diskdump_mode = 1; + + Dbg("notify dump start."); + notifier_call_chain(&disk_dump_notifier_list, 0, NULL); + + dump_header.tasks[smp_processor_id()] = current; +#if CONFIG_SMP + smp_call_function(freeze_cpu, NULL, 1, 0); + mdelay(3000); + printk("CPU frozen: "); + for (i = 0; i < NR_CPUS; i++) { + if (dump_header.tasks[i] != NULL) + printk("#%d", i); + + } + printk("\n"); + printk("CPU#%d is executing diskdump.\n", smp_processor_id()); +#else + mdelay(1000); +#endif + + /* + * Setup timer/tasklet + */ + dump_clear_timers(); + dump_clear_tasklet(); + dump_clear_workqueue(); + + /* Save original jiffies value */ + diskdump_base_jiffies = jiffies; + + platform_timestamp(timestamp_base); + udelay(1000000/HZ); + platform_timestamp(t); + timestamp_hz = (unsigned long)(t - timestamp_base); + diskdump_update(); + + platform_fix_regs(); + + if (list_empty(&disk_dump_devices)) { + Err("adapter driver is not registered."); + goto done; + } + + printk("start dumping\n"); + + if (!(dump_part = select_dump_partition())) { + Err("No sane dump device found"); + goto done; + } + dump_device = dump_part->device; + + /* + * Stop ongoing I/O with polling driver and make the shift to I/O mode + * for dump + */ + Dbg("do quiesce"); + if (dump_device->ops.quiesce) + if ((ret = dump_device->ops.quiesce(dump_device)) < 0) { + Err("quiesce failed. error %d", ret); + goto done; + } + + if (SECTOR_BLOCK(dump_part->nr_sects) < header_blocks + bitmap_blocks) { + Warn("dump partition is too small. Aborted"); + goto done; + } + + /* Check dump partition */ + printk("check dump partition...\n"); + if (!check_dump_partition(dump_part, total_blocks)) { + Err("check partition failed."); + goto done; + } + + /* + * Write the common header + */ + memcpy(dump_header.signature, DISK_DUMP_SIGNATURE, sizeof(dump_header.signature)); + dump_header.utsname = system_utsname; + dump_header.timestamp = xtime; + dump_header.status = DUMP_HEADER_INCOMPLETED; + dump_header.block_size = PAGE_SIZE; + dump_header.sub_hdr_size = size_of_sub_header(); + dump_header.bitmap_blocks = bitmap_blocks; + dump_header.max_mapnr = max_pfn; + dump_header.total_ram_blocks = total_ram_blocks; + dump_header.device_blocks = SECTOR_BLOCK(dump_part->nr_sects); + dump_header.current_cpu = smp_processor_id(); + dump_header.nr_cpus = num_online_cpus(); + dump_header.written_blocks = 2; + + write_header(dump_part); + + /* + * Write the architecture dependent header + */ + Dbg("write sub header"); + if ((ret = write_sub_header()) < 0) { + Err("writing sub header failed. error %d", ret); + goto done; + } + + Dbg("writing memory bitmaps.."); + if ((ret = write_bitmap(dump_part, header_blocks, bitmap_blocks)) < 0) + goto done; + + max_written_blocks = total_ram_blocks; + if (dump_header.device_blocks < total_blocks) { + Warn("dump partition is too small. actual blocks %u. expected blocks %u. whole memory will not be saved", + dump_header.device_blocks, total_blocks); + max_written_blocks -= (total_blocks - dump_header.device_blocks); + } + + dump_header.written_blocks += dump_header.sub_hdr_size; + dump_header.written_blocks += dump_header.bitmap_blocks; + write_header(dump_part); + + printk("dumping memory..\n"); + if ((ret = write_memory(dump_part, header_blocks + bitmap_blocks, + max_written_blocks, &written_blocks)) < 0) + goto done; + + /* + * Set the number of block that is written into and write it + * into partition again. + */ + dump_header.written_blocks += written_blocks; + dump_header.status = DUMP_HEADER_COMPLETED; + write_header(dump_part); + + ret = 0; + +done: + Dbg("do adapter shutdown."); + if (dump_device && dump_device->ops.shutdown) + if (dump_device->ops.shutdown(dump_device)) + Err("adapter shutdown failed."); + + /* + * If diskdump failed and fallback_on_err is set, + * We just return and leave panic to netdump. + */ + if (fallback_on_err && ret != 0) + return; + + Dbg("notify panic."); + notifier_call_chain(&panic_notifier_list, 0, NULL); + + /* Resotre original jiffies. */ + jiffies = diskdump_base_jiffies; + + if (panic_timeout > 0) { + int i; + + printk(KERN_EMERG "Rebooting in %d second%s..", + panic_timeout, "s" + (panic_timeout == 1)); + for (i = 0; i < panic_timeout; i++) { + touch_nmi_watchdog(); + mdelay(1000); + } + printk("\n"); + machine_restart(NULL); + } + printk(KERN_EMERG "halt\n"); + for (;;) { + touch_nmi_watchdog(); + machine_halt(); + mdelay(1000); + } +} + +static struct disk_dump_partition *find_dump_partition(dev_t dev) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + list_for_each_entry(dump_part, &dump_device->partitions, list) + if (dump_part->dentry->d_inode->i_rdev == dev) + return dump_part; + return NULL; +} + +static struct disk_dump_device *find_dump_device(void *real_device) +{ + struct disk_dump_device *dump_device; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + if (real_device == dump_device->device) + return dump_device; + return NULL; +} + +static void *find_real_device(struct block_device *bdev, struct disk_dump_type **_dump_type) +{ + void *real_device; + struct disk_dump_type *dump_type; + + list_for_each_entry(dump_type, &disk_dump_types, list) + if ((real_device = dump_type->probe(bdev)) != NULL) { + *_dump_type = dump_type; + return real_device; + } + return NULL; +} + +/* + * Add dump partition structure corresponding to file to the dump device + * structure. + */ +static int add_dump_partition(struct disk_dump_device *dump_device, struct file *file) +{ + struct disk_dump_partition *dump_part; + struct inode *inode = file->f_dentry->d_inode; + struct block_device *bdev = inode->i_bdev; + char buffer[32]; + + if (!(dump_part = kmalloc(sizeof(*dump_part), GFP_KERNEL))) + return -ENOMEM; + + dump_part->device = dump_device; + dump_part->vfsmount = mntget(file->f_vfsmnt); + dump_part->dentry = dget(file->f_dentry); + + if (!bdev || !bdev->bd_part) + return -EINVAL; + dump_part->nr_sects = bdev->bd_part->nr_sects; + dump_part->start_sect = bdev->bd_part->start_sect; + + if (SECTOR_BLOCK(dump_part->nr_sects) < total_blocks) + Warn("%s is too small to save whole system memory\n", + bdevname(bdev, buffer)); + + list_add(&dump_part->list, &dump_device->partitions); + + return 0; +} + +/* + * Add dump partition corresponding to file. + * Must be called with disk_dump_mutex held. + */ +static int add_dump(struct file *file) +{ + struct disk_dump_type *dump_type = NULL; + struct disk_dump_device *dump_device; + void *real_device; + struct inode *dump_inode = file->f_dentry->d_inode; + dev_t dev = dump_inode->i_rdev; + int ret; + + /* Check whether this inode is already registered */ + if (find_dump_partition(dev)) + return -EEXIST; + + /* find dump_type and real device for this inode */ + if (!(real_device = find_real_device(dump_inode->i_bdev, &dump_type))) + return -ENXIO; + + dump_device = find_dump_device(real_device); + if (dump_device == NULL) { + /* real_device is not registered. create new dump_device */ + if (!(dump_device = kmalloc(sizeof(*dump_device), GFP_KERNEL))) + return -ENOMEM; + + memset(dump_device, 0, sizeof(*dump_device)); + INIT_LIST_HEAD(&dump_device->partitions); + + dump_device->dump_type = dump_type; + dump_device->device = real_device; + if ((ret = dump_type->add_device(dump_device)) < 0) { + kfree(dump_device); + return ret; + } + if (!try_module_get(dump_type->owner)) + return -EINVAL; + list_add(&dump_device->list, &disk_dump_devices); + } + + ret = add_dump_partition(dump_device, file); + if (ret < 0 && list_empty(&dump_device->list)) { + dump_type->remove_device(dump_device); + module_put(dump_type->owner); + list_del(&dump_device->list); + kfree(dump_device); + } + + return ret; +} + +/* + * Remove dump partition corresponding to file. + * Must be called with disk_dump_mutex held. + */ +static int remove_dump(struct file *file) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + struct disk_dump_type *dump_type; + dev_t dev = file->f_dentry->d_inode->i_rdev; + + if (!(dump_part = find_dump_partition(dev))) + return -ENOENT; + + dump_device = dump_part->device; + + list_del(&dump_part->list); + mntput(dump_part->vfsmount); + dput(dump_part->dentry); + kfree(dump_part); + + if (list_empty(&dump_device->partitions)) { + dump_type = dump_device->dump_type; + dump_type->remove_device(dump_device); + module_put(dump_type->owner); + module_put(dump_type->owner); + list_del(&dump_device->list); + kfree(dump_device); + } + + return 0; +} + +#ifdef CONFIG_PROC_FS + +static char *strsep2(char **s, const char *ct) +{ + char *tmp; + + do { + tmp = strsep(s, ct); + if (tmp == NULL) + return NULL; + } while (*tmp == '\0'); + + return tmp; +} + +static int proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char *kbuf, *tmp, *cmd, *filename; + int ret; + struct file *dump_file; + struct inode *dump_inode; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (count >= PAGE_SIZE) + return -EINVAL; + if (!(kbuf = (char *)get_zeroed_page(GFP_KERNEL))) + return -ENOMEM; + if (copy_from_user(kbuf, buffer, count)) + return -EFAULT; + kbuf[PAGE_SIZE - 1] = '\0'; + + tmp = kbuf; + cmd = strsep2(&tmp, " \t"); + if (!cmd) { + ret = -EINVAL; + goto out; + } + filename = strsep2(&tmp, " \t\n"); + if (!filename) { + ret = -EINVAL; + goto out; + } +printk("filename=[%s]\n", filename); + + dump_file = filp_open(filename, O_RDONLY, 0); + if (IS_ERR(dump_file)) { + ret = -EBADF; + goto out; + } + dump_inode = dump_file->f_dentry->d_inode; + if (!dump_inode->i_bdev) { + ret = -EBADF; + goto out_fclose; + } + + down(&disk_dump_mutex); + if (!strcmp(cmd, "add")) + ret = add_dump(dump_file); + else if (!strcmp(cmd, "remove")) + ret = remove_dump(dump_file); + else + ret = -EINVAL; + + set_crc_modules(); + up(&disk_dump_mutex); + +out_fclose: + filp_close(dump_file, 0); +out: + free_page((unsigned long)kbuf); + + if (ret >= 0) + ret = count; + return ret; +} + +static void *disk_dump_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + loff_t n = *pos; + + down(&disk_dump_mutex); + list_for_each_entry(dump_device, &disk_dump_devices, list) { + seq->private = dump_device; + list_for_each_entry(dump_part, &dump_device->partitions, list) { + if (!n--) + return dump_part; + } + } + return NULL; +} + +static void *disk_dump_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct list_head *partition = v; + struct list_head *device = seq->private; + struct disk_dump_device *dump_device; + + dump_device = list_entry(device, struct disk_dump_device, list); + + (*pos)++; + partition = partition->next; + if (partition != &dump_device->partitions) + return partition; + + device = device->next; + seq->private = device; + if (device == &disk_dump_devices) + return NULL; + + dump_device = list_entry(device, struct disk_dump_device, list); + + return dump_device->partitions.next; +} + +static void disk_dump_seq_stop(struct seq_file *seq, void *v) +{ + up(&disk_dump_mutex); +} + +static int disk_dump_seq_show(struct seq_file *seq, void *v) +{ + struct disk_dump_partition *dump_part = v; + char *page; + char *path; + + if (!(page = (char *)__get_free_page(GFP_KERNEL))) + return -ENOMEM; + path = d_path(dump_part->dentry, dump_part->vfsmount, page, PAGE_SIZE); + seq_printf(seq, "%s %lu %lu\n", + path, dump_part->start_sect, dump_part->nr_sects); + free_page((unsigned long)page); + return 0; +} + +static struct seq_operations disk_dump_seq_ops = { + .start = disk_dump_seq_start, + .next = disk_dump_seq_next, + .stop = disk_dump_seq_stop, + .show = disk_dump_seq_show, +}; + +static int disk_dump_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &disk_dump_seq_ops); +} + +static struct file_operations disk_dump_fops = { + .owner = THIS_MODULE, + .open = disk_dump_open, + .read = seq_read, + .write = proc_write, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + + +int register_disk_dump_type(struct disk_dump_type *dump_type) +{ + down(&disk_dump_mutex); + list_add(&dump_type->list, &disk_dump_types); + set_crc_modules(); + up(&disk_dump_mutex); + + return 0; +} + +int unregister_disk_dump_type(struct disk_dump_type *dump_type) +{ + down(&disk_dump_mutex); + list_del(&dump_type->list); + set_crc_modules(); + up(&disk_dump_mutex); + + return 0; +} + +EXPORT_SYMBOL(register_disk_dump_type); +EXPORT_SYMBOL(unregister_disk_dump_type); +EXPORT_SYMBOL(disk_dump_notifier_list); + + +static void compute_total_blocks(void) +{ + unsigned int nr; + + /* + * the number of block of the common header and the header + * that is depend on the architecture + * + * block 0: dump partition header + * block 1: dump header + * block 2: dump subheader + * block 3..n: memory bitmap + * block (n + 1)...: saved memory + * + * We never overwrite block 0 + */ + header_blocks = 2 + size_of_sub_header(); + + total_ram_blocks = 0; + for (nr = 0; nr < max_pfn; nr++) { + if (page_is_ram(nr)) + total_ram_blocks++; + } + + bitmap_blocks = ROUNDUP(max_pfn, 8 * PAGE_SIZE); + + /* + * The necessary size of area for dump is: + * 1 block for common header + * m blocks for architecture dependent header + * n blocks for memory bitmap + * and whole memory + */ + total_blocks = header_blocks + bitmap_blocks + total_ram_blocks; + + Info("total blocks required: %u (header %u + bitmap %u + memory %u)", + total_blocks, header_blocks, bitmap_blocks, total_ram_blocks); +} + +static int init_diskdump(void) +{ + unsigned long long t0; + unsigned long long t1; + struct page *page; + + if (!platform_supports_diskdump) { + Err("platform does not support diskdump."); + return -1; + } + + /* Allocate one block that is used temporally */ + do { + page = alloc_pages(GFP_KERNEL, block_order); + if (page != NULL) + break; + } while (--block_order >= 0); + if (!page) { + Err("alloc_pages failed."); + return -1; + } + scratch = page_address(page); + Info("Maximum block size: %lu", PAGE_SIZE << block_order); + + if (diskdump_register_hook(disk_dump)) { + Err("failed to register hooks."); + return -1; + } + + compute_total_blocks(); + + platform_timestamp(t0); + mdelay(1); + platform_timestamp(t1); + timestamp_1sec = (unsigned long)(t1 - t0) * 1000; + +#ifdef CONFIG_PROC_FS + { + struct proc_dir_entry *p; + + p = create_proc_entry("diskdump", S_IRUGO|S_IWUSR, NULL); + if (p) + p->proc_fops = &disk_dump_fops; + } +#endif + + return 0; +} + +static void cleanup_diskdump(void) +{ + Info("shut down."); + diskdump_unregister_hook(); + free_pages((unsigned long)scratch, block_order); +#ifdef CONFIG_PROC_FS + remove_proc_entry("diskdump", NULL); +#endif +} + +module_init(init_diskdump); +module_exit(cleanup_diskdump); +MODULE_LICENSE("GPL"); + + diff -Nur linux-2.6.7.org/drivers/char/sysrq.c linux-2.6.7/drivers/char/sysrq.c --- linux-2.6.7.org/drivers/char/sysrq.c 2004-06-22 10:27:55.000000000 +0900 +++ linux-2.6.7/drivers/char/sysrq.c 2004-06-22 22:26:39.243363680 +0900 @@ -107,6 +107,19 @@ .action_msg = "Resetting", }; +/* crash sysrq handler */ +static void sysrq_handle_crash(int key, struct pt_regs *pt_regs, + struct tty_struct *tty) +{ + *( (char *) 0) = 0; +} + +static struct sysrq_key_op sysrq_crash_op = { + .handler = sysrq_handle_crash, + .help_msg = "Crash", + .action_msg = "Crashing the kernel by request", +}; + static void sysrq_handle_sync(int key, struct pt_regs *pt_regs, struct tty_struct *tty) { @@ -235,7 +248,7 @@ it is handled specially on the sparc and will never arrive */ /* b */ &sysrq_reboot_op, -/* c */ NULL, +/* c */ &sysrq_crash_op, /* d */ NULL, /* e */ &sysrq_term_op, /* f */ NULL, diff -Nur linux-2.6.7.org/include/asm-i386/diskdump.h linux-2.6.7/include/asm-i386/diskdump.h --- linux-2.6.7.org/include/asm-i386/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/include/asm-i386/diskdump.h 2004-06-22 22:26:39.000000000 +0900 @@ -0,0 +1,82 @@ +#ifndef _ASM_I386_DISKDUMP_H +#define _ASM_I386_DISKDUMP_H + +/* + * linux/include/asm-i386/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * Copyright (c) 2003 Red Hat, Inc. All rights reserved. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifdef __KERNEL__ + +#include <linux/elf.h> + +extern int page_is_ram(unsigned long); +const static int platform_supports_diskdump = 1; + +#define platform_fix_regs() \ +{ \ + unsigned long esp; \ + unsigned short ss; \ + esp = (unsigned long) ((char *)regs + sizeof (struct pt_regs)); \ + ss = __KERNEL_DS; \ + if (regs->xcs & 3) { \ + esp = regs->esp; \ + ss = regs->xss & 0xffff; \ + } \ + myregs = *regs; \ + myregs.esp = esp; \ + myregs.xss = (myregs.xss & 0xffff0000) | ss; \ +} + +struct disk_dump_sub_header { + elf_gregset_t elf_regs; +}; + +#define platform_timestamp(x) rdtscll(x) + +#define size_of_sub_header() ((sizeof(struct disk_dump_sub_header) + PAGE_SIZE - 1) / DUMP_BLOCK_SIZE) + +#define write_sub_header() \ +({ \ + int ret; \ + \ + ELF_CORE_COPY_REGS(dump_sub_header.elf_regs, (&myregs)); \ + clear_page(scratch); \ + memcpy(scratch, &dump_sub_header, sizeof(dump_sub_header)); \ + \ + if ((ret = write_blocks(dump_part, 2, scratch, 1)) >= 0) \ + ret = 1; /* size of sub header in page */; \ + ret; \ +}) + +#define platform_freeze_cpu() \ +{ \ + for (;;) local_irq_disable(); \ +} + +#define platform_start_diskdump(func, regs) \ +{ \ + func(regs, NULL); \ +} + +#endif /* __KERNEL__ */ + +#endif /* _ASM_I386_DISKDUMP_H */ diff -Nur linux-2.6.7.org/include/asm-i386/kmap_types.h linux-2.6.7/include/asm-i386/kmap_types.h --- linux-2.6.7.org/include/asm-i386/kmap_types.h 2004-06-22 10:27:28.000000000 +0900 +++ linux-2.6.7/include/asm-i386/kmap_types.h 2004-06-22 22:26:39.000000000 +0900 @@ -23,7 +23,8 @@ D(10) KM_IRQ1, D(11) KM_SOFTIRQ0, D(12) KM_SOFTIRQ1, -D(13) KM_TYPE_NR +D(13) KM_DISKDUMP, +D(14) KM_TYPE_NR }; #undef D diff -Nur linux-2.6.7.org/include/linux/diskdump.h linux-2.6.7/include/linux/diskdump.h --- linux-2.6.7.org/include/linux/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/include/linux/diskdump.h 2004-06-22 22:26:39.000000000 +0900 @@ -0,0 +1,158 @@ +#ifndef _LINUX_DISKDUMP_H +#define _LINUX_DISKDUMP_H + +/* + * linux/include/linux/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/list.h> +#include <linux/mount.h> +#include <linux/dcache.h> +#include <linux/blkdev.h> +#include <linux/utsname.h> + +/* The minimum Dump I/O unit. Must be the same of PAGE_SIZE */ +#define DUMP_BLOCK_SIZE PAGE_SIZE +#define DUMP_BLOCK_SHIFT PAGE_SHIFT + +/* Dump ioctls */ +#define BLKADDDUMPDEVICE 0xdf00 /* Add a dump device */ +#define BLKREMOVEDUMPDEVICE 0xdf01 /* Delete a dump device */ + +int diskdump_register_hook(void (*dump_func)(struct pt_regs *, void *)); +void diskdump_unregister_hook(void); + +/* + * The handler that adapter driver provides for the common module of + * dump + */ +struct disk_dump_partition; +struct disk_dump_device; + +struct disk_dump_type { + void *(*probe)(struct block_device *); + int (*add_device)(struct disk_dump_device *); + void (*remove_device)(struct disk_dump_device *); + struct module *owner; + struct list_head list; +}; + +struct disk_dump_device_ops { + int (*sanity_check)(struct disk_dump_device *); + int (*quiesce)(struct disk_dump_device *); + int (*shutdown)(struct disk_dump_device *); + int (*rw_block)(struct disk_dump_partition *, int rw, unsigned long block_nr, void *buf, int len); +}; + +/* The data structure for a dump device */ +struct disk_dump_device { + struct list_head list; + struct disk_dump_device_ops ops; + struct disk_dump_type *dump_type; + void *device; + unsigned int max_blocks; + struct list_head partitions; +}; + +/* The data structure for a dump partition */ +struct disk_dump_partition { + struct list_head list; + struct disk_dump_device *device; + struct vfsmount *vfsmount; + struct dentry *dentry; + unsigned long start_sect; + unsigned long nr_sects; +}; + + +int register_disk_dump_type(struct disk_dump_type *); +int unregister_disk_dump_type(struct disk_dump_type *); + +void diskdump_update(void); + +/* + * Architecture-independent dump header + */ + +/* The signature which is written in each block in the dump partition */ +#define DISK_DUMP_SIGNATURE "DISKDUMP" +#define DISK_DUMP_HEADER_VERSION 1 + +#define DUMP_PARTITION_SIGNATURE "diskdump" + +#define DUMP_HEADER_COMPLETED 0 +#define DUMP_HEADER_INCOMPLETED 1 + +struct disk_dump_header { + char signature[8]; /* = "DISKDUMP" */ + int header_version; /* Dump header version */ + struct new_utsname utsname; /* copy of system_utsname */ + struct timespec timestamp; /* Time stamp */ + unsigned int status; /* Above flags */ + int block_size; /* Size of a block in byte */ + int sub_hdr_size; /* Size of arch dependent + header in blocks */ + unsigned int bitmap_blocks; /* Size of Memory bitmap in + block */ + unsigned int max_mapnr; /* = max_mapnr */ + unsigned int total_ram_blocks;/* Size of Memory in block */ + unsigned int device_blocks; /* Number of total blocks in + * the dump device */ + unsigned int written_blocks; /* Number of written blocks */ + unsigned int current_cpu; /* CPU# which handles dump */ + int nr_cpus; /* Number of CPUs */ + struct task_struct *tasks[NR_CPUS]; +}; + +/* + * Calculate the check sum of the whole module + */ +#define get_crc_module() \ +({ \ + struct module *module = &__this_module; \ + crc32_le(0, (char *)(module->module_core), \ + ((unsigned long)module - (unsigned long)(module->module_core))); \ +}) + +/* Calculate the checksum of the whole module */ +#define set_crc_modules() \ +({ \ + module_crc = 0; \ + module_crc = get_crc_module(); \ +}) + +/* + * Compare the checksum value that is stored in module_crc to the check + * sum of current whole module. Must be called with holding disk_dump_lock. + * Return TRUE if they are the same, else return FALSE + * + */ +#define check_crc_module() \ +({ \ + uint32_t orig_crc, cur_crc; \ + \ + orig_crc = module_crc; module_crc = 0; \ + cur_crc = get_crc_module(); \ + module_crc = orig_crc; \ + orig_crc == cur_crc; \ +}) + + +#endif /* _LINUX_DISKDUMP_H */ diff -Nur linux-2.6.7.org/include/linux/interrupt.h linux-2.6.7/include/linux/interrupt.h --- linux-2.6.7.org/include/linux/interrupt.h 2004-06-22 10:27:34.000000000 +0900 +++ linux-2.6.7/include/linux/interrupt.h 2004-06-22 22:26:39.000000000 +0900 @@ -246,4 +246,8 @@ extern int probe_irq_off(unsigned long); /* returns 0 or negative on failure */ extern unsigned int probe_irq_mask(unsigned long); /* returns mask of ISA interrupts */ + +extern void dump_clear_tasklet(void); +extern void dump_run_tasklet(void); + #endif diff -Nur linux-2.6.7.org/include/linux/kernel.h linux-2.6.7/include/linux/kernel.h --- linux-2.6.7.org/include/linux/kernel.h 2004-06-22 10:27:34.000000000 +0900 +++ linux-2.6.7/include/linux/kernel.h 2004-06-22 22:26:39.000000000 +0900 @@ -111,6 +111,11 @@ extern int panic_on_oops; extern int tainted; extern const char *print_tainted(void); +struct pt_regs; +extern void try_crashdump(struct pt_regs *); +extern void (*diskdump_func) (struct pt_regs *regs, void *platform_arg); +extern int diskdump_mode; +#define crashdump_mode() unlikely(diskdump_mode) /* Values used for system_state */ extern enum system_states { @@ -139,6 +144,12 @@ #define pr_info(fmt,arg...) \ printk(KERN_INFO fmt,##arg) +#define pr_err(fmt,arg...) \ + printk(KERN_ERR fmt,##arg) + +#define pr_warn(fmt,arg...) \ + printk(KERN_WARNING fmt,##arg) + /* * Display an IP address in readable format. */ diff -Nur linux-2.6.7.org/include/linux/timer.h linux-2.6.7/include/linux/timer.h --- linux-2.6.7.org/include/linux/timer.h 2004-06-22 10:27:31.000000000 +0900 +++ linux-2.6.7/include/linux/timer.h 2004-06-22 22:26:39.000000000 +0900 @@ -99,4 +99,7 @@ extern void run_local_timers(void); extern void it_real_fn(unsigned long); +extern void dump_clear_timers(void); +extern void dump_run_timers(void); + #endif diff -Nur linux-2.6.7.org/include/linux/workqueue.h linux-2.6.7/include/linux/workqueue.h --- linux-2.6.7.org/include/linux/workqueue.h 2004-06-22 10:27:35.000000000 +0900 +++ linux-2.6.7/include/linux/workqueue.h 2004-06-22 22:26:39.000000000 +0900 @@ -84,4 +84,7 @@ return ret; } +extern void dump_clear_workqueue(void); +extern void dump_run_workqueue(void); + #endif diff -Nur linux-2.6.7.org/kernel/panic.c linux-2.6.7/kernel/panic.c --- linux-2.6.7.org/kernel/panic.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/panic.c 2004-06-22 22:26:39.000000000 +0900 @@ -23,8 +23,10 @@ int panic_timeout; int panic_on_oops; int tainted; +int diskdump_mode = 0; EXPORT_SYMBOL(panic_timeout); +EXPORT_SYMBOL_GPL(diskdump_mode); struct notifier_block *panic_notifier_list; @@ -60,6 +62,8 @@ vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); printk(KERN_EMERG "Kernel panic: %s\n",buf); + if (diskdump_func) + BUG(); if (in_interrupt()) printk(KERN_EMERG "In interrupt handler - not syncing\n"); else if (!current->pid) @@ -134,3 +138,49 @@ snprintf(buf, sizeof(buf), "Not tainted"); return(buf); } + +/* + * Dump stuff. + */ +void (*diskdump_func) (struct pt_regs *regs, void *platform_arg) = NULL; +EXPORT_SYMBOL_GPL(diskdump_func); + +int diskdump_register_hook(void (*dump_func) (struct pt_regs *, void *)) +{ + if (diskdump_func) + return -EEXIST; + + diskdump_func = dump_func; + + return 0; +} + +EXPORT_SYMBOL_GPL(diskdump_register_hook); + +void diskdump_unregister_hook(void) +{ + diskdump_func = NULL; +} + +EXPORT_SYMBOL_GPL(diskdump_unregister_hook); + +/* + * Try crashdump. Diskdump is first, netdump is second. + * We clear diskdump_func before call of diskdump_func, so + * If double panic would occur in diskdump, netdump can handle + * it. + */ +#include <asm/diskdump.h> +void try_crashdump(struct pt_regs *regs) +{ + void (*func)(struct pt_regs *, void *); + + if (diskdump_func) { + func = diskdump_func; + diskdump_func = NULL; + platform_start_diskdump(func, regs); + } + if (panic_on_oops) + panic("Fatal exception"); +} + diff -Nur linux-2.6.7.org/kernel/softirq.c linux-2.6.7/kernel/softirq.c --- linux-2.6.7.org/kernel/softirq.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/softirq.c 2004-06-22 22:26:39.000000000 +0900 @@ -314,6 +314,38 @@ EXPORT_SYMBOL(tasklet_kill); +struct tasklet_head saved_tasklet; + +void dump_clear_tasklet(void) +{ + saved_tasklet.list = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = NULL; +} + +EXPORT_SYMBOL(dump_clear_tasklet); + +void dump_run_tasklet(void) +{ + struct tasklet_struct *list; + + list = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = NULL; + + while (list) { + struct tasklet_struct *t = list; + list = list->next; + + if (!atomic_read(&t->count) && + (test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))) + t->func(t->data); + + t->next = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = t; + } +} + +EXPORT_SYMBOL(dump_run_tasklet); + void __init softirq_init(void) { open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); diff -Nur linux-2.6.7.org/kernel/timer.c linux-2.6.7/kernel/timer.c --- linux-2.6.7.org/kernel/timer.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/timer.c 2004-06-22 22:26:39.000000000 +0900 @@ -31,6 +31,7 @@ #include <linux/time.h> #include <linux/jiffies.h> #include <linux/cpu.h> +#include <linux/delay.h> #include <asm/uaccess.h> #include <asm/unistd.h> @@ -424,7 +425,6 @@ { struct timer_list *timer; - spin_lock_irq(&base->lock); while (time_after_eq(jiffies, base->timer_jiffies)) { struct list_head work_list = LIST_HEAD_INIT(work_list); struct list_head *head = &work_list; @@ -460,6 +460,12 @@ } } set_running_timer(base, NULL); +} + +static inline void _run_timers(tvec_base_t *base) +{ + spin_lock_irq(&base->lock); + __run_timers(base); spin_unlock_irq(&base->lock); } @@ -909,7 +915,7 @@ tvec_base_t *base = &__get_cpu_var(tvec_bases); if (time_after_eq(jiffies, base->timer_jiffies)) - __run_timers(base); + _run_timers(base); } /* @@ -1105,6 +1111,12 @@ struct timer_list timer; unsigned long expire; + if (unlikely(crashdump_mode())) { + mdelay(timeout); + set_current_state(TASK_RUNNING); + return timeout; + } + switch (timeout) { case MAX_SCHEDULE_TIMEOUT: @@ -1308,7 +1320,7 @@ return 0; } -static void __devinit init_timers_cpu(int cpu) +static void /* __devinit */ init_timers_cpu(int cpu) { int j; tvec_base_t *base; @@ -1327,6 +1339,27 @@ base->timer_jiffies = jiffies; } +static tvec_base_t saved_tvec_base; + +void dump_clear_timers(void) +{ + tvec_base_t *base = &per_cpu(tvec_bases, smp_processor_id()); + + memcpy(&saved_tvec_base, base, sizeof(saved_tvec_base)); + init_timers_cpu(smp_processor_id()); +} + +EXPORT_SYMBOL(dump_clear_timers); + +void dump_run_timers(void) +{ + tvec_base_t *base = &__get_cpu_var(tvec_bases); + + __run_timers(base); +} + +EXPORT_SYMBOL(dump_run_timers); + #ifdef CONFIG_HOTPLUG_CPU static int migrate_timer_list(tvec_base_t *new_base, struct list_head *head) { diff -Nur linux-2.6.7.org/kernel/workqueue.c linux-2.6.7/kernel/workqueue.c --- linux-2.6.7.org/kernel/workqueue.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/workqueue.c 2004-06-22 22:26:39.000000000 +0900 @@ -424,6 +424,37 @@ } +struct cpu_workqueue_struct saved_cwq; + +void dump_clear_workqueue(void) +{ + int cpu = smp_processor_id(); + struct cpu_workqueue_struct *cwq = keventd_wq->cpu_wq + cpu; + + memcpy(&saved_cwq, cwq, sizeof(saved_cwq)); + spin_lock_init(&cwq->lock); + INIT_LIST_HEAD(&cwq->worklist); + init_waitqueue_head(&cwq->more_work); + init_waitqueue_head(&cwq->work_done); +} + +void dump_run_workqueue(void) +{ + struct cpu_workqueue_struct *cwq; + + cwq = keventd_wq->cpu_wq + smp_processor_id(); + while (!list_empty(&cwq->worklist)) { + struct work_struct *work = list_entry(cwq->worklist.next, + struct work_struct, entry); + void (*f) (void *) = work->func; + void *data = work->data; + + list_del_init(cwq->worklist.next); + clear_bit(0, &work->pending); + f(data); + } +} + #ifdef CONFIG_HOTPLUG_CPU /* Take the work from this (downed) CPU. */ static void take_over_work(struct workqueue_struct *wq, unsigned int cpu) @@ -507,3 +538,6 @@ EXPORT_SYMBOL(schedule_delayed_work); EXPORT_SYMBOL(flush_scheduled_work); +EXPORT_SYMBOL(dump_clear_workqueue); +EXPORT_SYMBOL(dump_run_workqueue); + diff -Nur linux-2.6.7.org/mm/bootmem.c linux-2.6.7/mm/bootmem.c --- linux-2.6.7.org/mm/bootmem.c 2004-06-22 10:28:05.000000000 +0900 +++ linux-2.6.7/mm/bootmem.c 2004-06-22 22:26:39.000000000 +0900 @@ -16,6 +16,7 @@ #include <linux/init.h> #include <linux/bootmem.h> #include <linux/mmzone.h> +#include <linux/module.h> #include <asm/dma.h> #include <asm/io.h> @@ -26,6 +27,7 @@ unsigned long max_low_pfn; unsigned long min_low_pfn; unsigned long max_pfn; +EXPORT_SYMBOL(max_pfn); /* return the number of _pages_ that will be allocated for the boot bitmap */ unsigned long __init bootmem_bootmap_pages (unsigned long pages) ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 0/4][Diskdump]Update patches
@ 2004-07-07 8:26 Takao Indoh
2004-07-07 8:30 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh
0 siblings, 1 reply; 9+ messages in thread
From: Takao Indoh @ 2004-07-07 8:26 UTC (permalink / raw)
To: linux-kernel
Hi!
I release new diskdump patches.
- Support sysfs interface instead of proc
- Fix some bugs
Source code can be downloaded from
http://sourceforge.net/projects/lkdump
The main change from previous version is replacing proc interface with
sysfs.
If you want to add a new dump device /dev/sda3,
# echo 3 > /sys/block/sda/device/dump
Like this, you need write the partition number into the appropriate
sysfs entry. You can also do the same thing using diskdumpctl command,
which is included in the diskdumputils.
# diskdumpctl /dev/sda3
If you want to remove the dump device /dev/sda3,
# echo -3 > /sys/block/sda/device/dump
or
# diskdumpctl -u /dev/sda1
Here is a part of patch of sysfs interface.
I added a new attribute "dump" to the /sys/block/sdX/device. The handler
of show/store operation calls a function which is registered via
sdev_dump_handler_register(). When the scsi_dump module is installed,
sysfs handler of scsi_dump is registered using
sdev_dump_handler_register().
Please feel free to comment!
diff -Nur linux-2.6.7.org/drivers/scsi/scsi_sysfs.c linux-2.6.7/drivers/scsi/scsi_sysfs.c
--- linux-2.6.7.org/drivers/scsi/scsi_sysfs.c 2004-06-22 10:27:50.000000000 +0900
+++ linux-2.6.7/drivers/scsi/scsi_sysfs.c 2004-07-07 17:01:26.146353152 +0900
@@ -375,6 +375,66 @@
DEVICE_ATTR(state, S_IRUGO | S_IWUSR, show_state_field, store_state_field);
+static DECLARE_MUTEX(sdev_dump_mutex);
+
+static struct device_attribute sdev_dump_attr = {
+ .show = NULL,
+ .store = NULL,
+};
+
+int sdev_dump_handler_register(struct device_attribute* attr)
+{
+ if (sdev_dump_attr.show || sdev_dump_attr.store)
+ return -EEXIST;
+
+ down(&sdev_dump_mutex);
+ sdev_dump_attr = *attr;
+ up(&sdev_dump_mutex);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(sdev_dump_handler_register);
+
+void sdev_dump_handler_unregister(void)
+{
+ down(&sdev_dump_mutex);
+ sdev_dump_attr.show = NULL;
+ sdev_dump_attr.store = NULL;
+ up(&sdev_dump_mutex);
+}
+
+EXPORT_SYMBOL(sdev_dump_handler_unregister);
+
+static ssize_t
+sdev_store_dump(struct device *dev, const char *buf, size_t count)
+{
+ ssize_t ret = count;
+
+ down(&sdev_dump_mutex);
+ if (sdev_dump_attr.store)
+ ret = sdev_dump_attr.store(dev, buf, count);
+ up(&sdev_dump_mutex);
+
+ return ret;
+}
+
+static ssize_t
+sdev_show_dump(struct device *dev, char *buf)
+{
+ ssize_t ret;
+
+ down(&sdev_dump_mutex);
+ if (sdev_dump_attr.show)
+ ret = sdev_dump_attr.show(dev, buf);
+ else
+ ret = snprintf(buf, 20, "handler not found\n");
+ up(&sdev_dump_mutex);
+
+ return ret;
+}
+
+DEVICE_ATTR(dump, S_IRUGO | S_IWUSR, sdev_show_dump, sdev_store_dump);
/* Default template for device attributes. May NOT be modified */
static struct device_attribute *scsi_sysfs_sdev_attrs[] = {
@@ -389,6 +449,7 @@
&dev_attr_delete,
&dev_attr_state,
&dev_attr_timeout,
+ &dev_attr_dump,
NULL
};
Best Regards,
Takao Indoh
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 1/4][Diskdump]Update patches 2004-07-07 8:26 [PATCH 0/4][Diskdump]Update patches Takao Indoh @ 2004-07-07 8:30 ` Takao Indoh 0 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-07-07 8:30 UTC (permalink / raw) To: linux-kernel This is a patch for diskdump common layer. diff -Nur linux-2.6.7.org/arch/i386/kernel/nmi.c linux-2.6.7/arch/i386/kernel/nmi.c --- linux-2.6.7.org/arch/i386/kernel/nmi.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/nmi.c 2004-07-07 14:16:22.000000000 +0900 @@ -524,3 +524,4 @@ EXPORT_SYMBOL(release_lapic_nmi); EXPORT_SYMBOL(disable_timer_nmi_watchdog); EXPORT_SYMBOL(enable_timer_nmi_watchdog); +EXPORT_SYMBOL_GPL(touch_nmi_watchdog); diff -Nur linux-2.6.7.org/arch/i386/kernel/reboot.c linux-2.6.7/arch/i386/kernel/reboot.c --- linux-2.6.7.org/arch/i386/kernel/reboot.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/reboot.c 2004-07-07 14:16:22.000000000 +0900 @@ -252,7 +252,8 @@ * Stop all CPUs and turn off local APICs and the IO-APIC, so * other OSs see a clean IRQ state. */ - smp_send_stop(); + if (!crashdump_mode()) + smp_send_stop(); #elif defined(CONFIG_X86_LOCAL_APIC) if (cpu_has_apic) { local_irq_disable(); diff -Nur linux-2.6.7.org/arch/i386/kernel/smp.c linux-2.6.7/arch/i386/kernel/smp.c --- linux-2.6.7.org/arch/i386/kernel/smp.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/smp.c 2004-07-07 14:16:22.000000000 +0900 @@ -520,7 +520,7 @@ return 0; /* Can deadlock when called with interrupts disabled */ - WARN_ON(irqs_disabled()); + WARN_ON(irqs_disabled() && !crashdump_mode()); data.func = func; data.info = info; diff -Nur linux-2.6.7.org/arch/i386/kernel/traps.c linux-2.6.7/arch/i386/kernel/traps.c --- linux-2.6.7.org/arch/i386/kernel/traps.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/traps.c 2004-07-07 14:16:22.000000000 +0900 @@ -294,6 +294,7 @@ } spinlock_t die_lock = SPIN_LOCK_UNLOCKED; +static int die_owner = -1; void die(const char * str, struct pt_regs * regs, long err) { @@ -301,7 +302,13 @@ int nl = 0; console_verbose(); - spin_lock_irq(&die_lock); + local_irq_disable(); + if (!spin_trylock(&die_lock)) { + if (smp_processor_id() != die_owner) + spin_lock(&die_lock); + /* allow recursive die to fall through */ + } + die_owner = smp_processor_id(); bust_spinlocks(1); handle_BUG(regs); printk(KERN_ALERT "%s: %04lx [#%d]\n", str, err & 0xffff, ++die_counter); @@ -320,7 +327,9 @@ if (nl) printk("\n"); show_registers(regs); + try_crashdump(regs); bust_spinlocks(0); + die_owner = -1; spin_unlock_irq(&die_lock); if (in_interrupt()) panic("Fatal exception in interrupt"); diff -Nur linux-2.6.7.org/arch/i386/mm/init.c linux-2.6.7/arch/i386/mm/init.c --- linux-2.6.7.org/arch/i386/mm/init.c 2004-06-22 10:27:17.000000000 +0900 +++ linux-2.6.7/arch/i386/mm/init.c 2004-07-07 14:16:22.000000000 +0900 @@ -168,7 +168,7 @@ extern int is_available_memory(efi_memory_desc_t *); -static inline int page_is_ram(unsigned long pagenr) +int page_is_ram(unsigned long pagenr) { int i; unsigned long addr, end; @@ -205,6 +205,7 @@ } return 0; } +EXPORT_SYMBOL(page_is_ram); #ifdef CONFIG_HIGHMEM pte_t *kmap_pte; diff -Nur linux-2.6.7.org/drivers/block/Kconfig linux-2.6.7/drivers/block/Kconfig --- linux-2.6.7.org/drivers/block/Kconfig 2004-06-22 10:27:42.000000000 +0900 +++ linux-2.6.7/drivers/block/Kconfig 2004-07-07 14:16:22.000000000 +0900 @@ -347,6 +347,11 @@ your machine, or if you want to have a raid or loopback device bigger than 2TB. Otherwise say N. +config DISKDUMP + tristate "Disk dump support" + ---help--- + Disk dump support. + source "drivers/s390/block/Kconfig" endmenu diff -Nur linux-2.6.7.org/drivers/block/Makefile linux-2.6.7/drivers/block/Makefile --- linux-2.6.7.org/drivers/block/Makefile 2004-06-22 10:27:43.000000000 +0900 +++ linux-2.6.7/drivers/block/Makefile 2004-07-07 14:16:22.000000000 +0900 @@ -43,3 +43,4 @@ obj-$(CONFIG_VIODASD) += viodasd.o obj-$(CONFIG_BLK_DEV_CARMEL) += carmel.o +obj-$(CONFIG_DISKDUMP) += diskdump.o diff -Nur linux-2.6.7.org/drivers/block/diskdump.c linux-2.6.7/drivers/block/diskdump.c --- linux-2.6.7.org/drivers/block/diskdump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/drivers/block/diskdump.c 2004-07-07 14:16:22.000000000 +0900 @@ -0,0 +1,851 @@ +/* + * linux/drivers/block/diskdump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Copyright (C) 2002 Red Hat, Inc. + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + * Some codes were derived from netdump and copyright belongs to + * Red Hat, Inc. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/file.h> +#include <linux/reboot.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/highmem.h> +#include <linux/utsname.h> +#include <linux/console.h> +#include <linux/smp_lock.h> +#include <linux/nmi.h> +#include <linux/genhd.h> +#include <linux/crc32.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/diskdump.h> +#include <asm/diskdump.h> + +#define Dbg(x, ...) pr_debug("disk_dump: " x "\n", ## __VA_ARGS__) +#define Err(x, ...) pr_err ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Warn(x, ...) pr_warn ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Info(x, ...) pr_info ("disk_dump: " x "\n", ## __VA_ARGS__) + +#define ROUNDUP(x, y) (((x) + ((y)-1))/(y)) + +/* 512byte sectors to blocks */ +#define SECTOR_BLOCK(s) ((s) >> (DUMP_BLOCK_SHIFT - 9)) + +static unsigned int fallback_on_err = 1; +static unsigned int allow_risky_dumps = 1; +static unsigned int block_order = 2; +static int sample_rate = 8; +module_param(fallback_on_err, uint, 0); +module_param(allow_risky_dumps, uint, 0); +module_param(block_order, uint, 0); +module_param(sample_rate, int, 0); + +static unsigned long timestamp_1sec; +static uint32_t module_crc; +static char *scratch; +static struct disk_dump_header dump_header; +static struct disk_dump_sub_header dump_sub_header; + +/* Registered dump devices */ +static LIST_HEAD(disk_dump_devices); + +static DECLARE_MUTEX(disk_dump_mutex); + +static unsigned int header_blocks; /* The size of all headers */ +static unsigned int bitmap_blocks; /* The size of bitmap header */ +static unsigned int total_ram_blocks; /* The size of memory */ +static unsigned int total_blocks; /* The sum of above */ + +struct notifier_block *disk_dump_notifier_list; +EXPORT_SYMBOL(disk_dump_notifier_list); + +unsigned long volatile diskdump_base_jiffies; + +extern int panic_timeout; +extern unsigned long max_pfn; + +#if CONFIG_SMP +static void freeze_cpu(void *dummy) +{ + unsigned int cpu = smp_processor_id(); + + dump_header.tasks[cpu] = current; + + platform_freeze_cpu(); +} +#endif + +static int lapse = 0; /* 200msec unit */ + +static inline unsigned long eta(unsigned long nr, unsigned long maxnr) +{ + unsigned long long eta; + + eta = ((maxnr << 8) / nr) * (unsigned long long)lapse; + + return (unsigned long)(eta >> 8) - lapse; +} + +static inline void print_status(unsigned int nr, unsigned int maxnr) +{ + static char *spinner = "/|\\-"; + static unsigned long long prev_timestamp = 0; + unsigned long long timestamp; + + platform_timestamp(timestamp); + + if (timestamp - prev_timestamp > (timestamp_1sec/5)) { + prev_timestamp = timestamp; + lapse++; + printk("%u/%u %lu ETA %c \r", + nr, maxnr, eta(nr, maxnr) / 5, spinner[lapse & 3]); + } +} + +static inline void clear_status(int nr, int maxnr) +{ + printk(" \r"); + lapse = 0; +} + +/* + * Checking the signature on a block. The format is as follows. + * + * 1st word = 'disk' + * 2nd word = 'dump' + * 3rd word = block number + * 4th word = ((block number + 7) * 11) & 0xffffffff + * 5th word = ((4th word + 7)* 11) & 0xffffffff + * .. + * + * Return 1 if the signature is correct, else return 0 + */ +static int check_block_signature(void *buf, unsigned int block_nr) +{ + int word_nr = PAGE_SIZE / sizeof(int); + int *words = buf; + unsigned int val; + int i; + + if (memcmp(buf, DUMP_PARTITION_SIGNATURE, sizeof(*words))) + return 0; + + val = block_nr; + for (i = 2; i < word_nr; i++) { + if (words[i] != val) + return 0; + val = (val + 7) * 11; + } + + return 1; +} + +/* + * Read one block into the dump partition + */ +static int read_blocks(struct disk_dump_partition *dump_part, unsigned int nr, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + local_irq_disable(); + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, READ, nr, buf, len); + if (ret < 0) { + Err("read error on block %u", nr); + return ret; + } + return 0; +} + +static int write_blocks(struct disk_dump_partition *dump_part, unsigned int offs, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + local_irq_disable(); + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, WRITE, offs, buf, len); + if (ret < 0) { + Err("write error on block %u", offs); + return ret; + } + return 0; +} + +/* + * Initialize the common header + */ + +/* + * Write the common header + */ +static int write_header(struct disk_dump_partition *dump_part) +{ + memset(scratch, '\0', PAGE_SIZE); + memcpy(scratch, &dump_header, sizeof(dump_header)); + + return write_blocks(dump_part, 1, scratch, 1); +} + +/* + * Check the signaures in all blocks of the dump partition + * Return 1 if the signature is correct, else return 0 + */ +static int check_dump_partition(struct disk_dump_partition *dump_part, unsigned int partition_size) +{ + unsigned int blk; + int ret; + unsigned int chunk_blks, skips; + int i; + + if (sample_rate < 0) /* No check */ + return 1; + + /* + * If the device has limitations of transfer size, use it. + */ + chunk_blks = 1 << block_order; + if (dump_part->device->max_blocks) + chunk_blks = min(chunk_blks, dump_part->device->max_blocks); + skips = chunk_blks << sample_rate; + + lapse = 0; + for (blk = 0; blk < partition_size; blk += skips) { + unsigned int len; +redo: + len = min(chunk_blks, partition_size - blk); + if ((ret = read_blocks(dump_part, blk, scratch, len)) < 0) + return 0; + print_status(blk + 1, partition_size); + for (i = 0; i < len; i++) + if (!check_block_signature(scratch + i * DUMP_BLOCK_SIZE, blk + i)) { + Err("bad signature in block %u", blk + i); + return 0; + } + } + /* Check the end of the dump partition */ + if (blk - skips + chunk_blks < partition_size) { + blk = partition_size - chunk_blks; + goto redo; + } + clear_status(blk, partition_size); + return 1; +} + +/* + * Write memory bitmap after location of dump headers. + */ +#define IDX2PAGENR(nr, byte, bit) (((nr) * PAGE_SIZE + (byte)) * 8 + (bit)) +static int write_bitmap(struct disk_dump_partition *dump_part, unsigned int bitmap_offset, unsigned int bitmap_blocks) +{ + unsigned int nr; + int bit, byte; + int ret = 0; + unsigned char val; + + for (nr = 0; nr < bitmap_blocks; nr++) { + for (byte = 0; byte < PAGE_SIZE; byte++) { + val = 0; + for (bit = 0; bit < 8; bit++) + if (page_is_ram(IDX2PAGENR(nr, byte, bit))) + val |= (1 << bit); + scratch[byte] = (char)val; + } + if ((ret = write_blocks(dump_part, bitmap_offset + nr, scratch, 1)) < 0) { + Err("I/O error %d on block %u", ret, bitmap_offset + nr); + break; + } + } + return ret; +} + +/* + * Write whole memory to dump partition. + * Return value is the number of writen blocks. + */ +static int write_memory(struct disk_dump_partition *dump_part, int offset, unsigned int max_blocks_written, unsigned int *blocks_written) +{ + char *kaddr; + unsigned int blocks = 0; + struct page *page; + unsigned int nr; + int ret = 0; + int blk_in_chunk = 0; + + for (nr = 0; nr < max_pfn; nr++) { + print_status(blocks, max_blocks_written); + + if (!page_is_ram(nr)) + continue; + + if (blocks >= max_blocks_written) { + Warn("dump device is too small. %lu pages were not saved", max_pfn - blocks); + goto out; + } + page = pfn_to_page(nr); + kaddr = (char *)kmap_atomic(page, KM_DISKDUMP); + /* + * need to copy because adapter drivers use virt_to_bus() + */ + memcpy(scratch + blk_in_chunk * PAGE_SIZE, kaddr, PAGE_SIZE); + blk_in_chunk++; + blocks++; + kunmap_atomic(kaddr, KM_DISKDUMP); + + if (blk_in_chunk >= (1 << block_order)) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) { + Err("I/O error %d on block %u", ret, offset); + break; + } + offset += blk_in_chunk; + blk_in_chunk = 0; + } + } + if (ret >= 0 && blk_in_chunk > 0) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) + Err("I/O error %d on block %u", ret, offset); + } + +out: + clear_status(nr, max_blocks_written); + + *blocks_written = blocks; + return ret; +} + +/* + * Select most suitable dump device. sanity_check() returns the state + * of each dump device. 0 means OK, negative value means NG, and + * positive value means it maybe work. select_dump_partition() first + * try to select a sane device and if it has no sane device and + * allow_risky_dumps is set, it select one from maybe OK devices. + * + * XXX We cannot handle multiple partitions yet. + */ +static struct disk_dump_partition *select_dump_partition(void) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + int sanity; + int strict_check = 1; + +redo: + /* + * Select a sane polling driver. + */ + list_for_each_entry(dump_device, &disk_dump_devices, list) { + sanity = 0; + if (dump_device->ops.sanity_check) + sanity = dump_device->ops.sanity_check(dump_device); + if (sanity < 0 || (sanity > 0 && strict_check)) + continue; + list_for_each_entry(dump_part, &dump_device->partitions, list) + return dump_part; + } + if (allow_risky_dumps && strict_check) { + strict_check = 0; + goto redo; + } + return NULL; +} + +static void disk_dump(struct pt_regs *regs, void *platform_arg) +{ + unsigned long flags; + int ret = -EIO; + struct pt_regs myregs; + unsigned int max_written_blocks, written_blocks; + int i; + struct disk_dump_device *dump_device = NULL; + struct disk_dump_partition *dump_part = NULL; + + /* Inhibit interrupt and stop other CPUs */ + local_irq_save(flags); + preempt_disable(); + + /* + * Check the checksum of myself + */ + if (down_trylock(&disk_dump_mutex)) { + Err("down_trylock(disk_dump_mutex) failed."); + goto done; + } + + if (!check_crc_module()) { + Err("checksum error. diskdump common module may be compromised."); + goto done; + } + + diskdump_mode = 1; + + Dbg("notify dump start."); + notifier_call_chain(&disk_dump_notifier_list, 0, NULL); + + dump_header.tasks[smp_processor_id()] = current; +#if CONFIG_SMP + smp_call_function(freeze_cpu, NULL, 1, 0); + mdelay(3000); + printk("CPU frozen: "); + for (i = 0; i < NR_CPUS; i++) { + if (dump_header.tasks[i] != NULL) + printk("#%d", i); + + } + printk("\n"); + printk("CPU#%d is executing diskdump.\n", smp_processor_id()); +#else + mdelay(1000); +#endif + + /* + * Setup timer/tasklet + */ + dump_clear_timers(); + dump_clear_tasklet(); + dump_clear_workqueue(); + + /* Save original jiffies value */ + diskdump_base_jiffies = jiffies; + + diskdump_setup_timestamp(); + + platform_fix_regs(); + + if (list_empty(&disk_dump_devices)) { + Err("adapter driver is not registered."); + goto done; + } + + printk("start dumping\n"); + + if (!(dump_part = select_dump_partition())) { + Err("No sane dump device found"); + goto done; + } + dump_device = dump_part->device; + + /* + * Stop ongoing I/O with polling driver and make the shift to I/O mode + * for dump + */ + Dbg("do quiesce"); + if (dump_device->ops.quiesce) + if ((ret = dump_device->ops.quiesce(dump_device)) < 0) { + Err("quiesce failed. error %d", ret); + goto done; + } + + if (SECTOR_BLOCK(dump_part->nr_sects) < header_blocks + bitmap_blocks) { + Warn("dump partition is too small. Aborted"); + goto done; + } + + /* Check dump partition */ + printk("check dump partition...\n"); + if (!check_dump_partition(dump_part, total_blocks)) { + Err("check partition failed."); + goto done; + } + + /* + * Write the common header + */ + memcpy(dump_header.signature, DISK_DUMP_SIGNATURE, sizeof(dump_header.signature)); + dump_header.utsname = system_utsname; + dump_header.timestamp = xtime; + dump_header.status = DUMP_HEADER_INCOMPLETED; + dump_header.block_size = PAGE_SIZE; + dump_header.sub_hdr_size = size_of_sub_header(); + dump_header.bitmap_blocks = bitmap_blocks; + dump_header.max_mapnr = max_pfn; + dump_header.total_ram_blocks = total_ram_blocks; + dump_header.device_blocks = SECTOR_BLOCK(dump_part->nr_sects); + dump_header.current_cpu = smp_processor_id(); + dump_header.nr_cpus = num_online_cpus(); + dump_header.written_blocks = 2; + + write_header(dump_part); + + /* + * Write the architecture dependent header + */ + Dbg("write sub header"); + if ((ret = write_sub_header()) < 0) { + Err("writing sub header failed. error %d", ret); + goto done; + } + + Dbg("writing memory bitmaps.."); + if ((ret = write_bitmap(dump_part, header_blocks, bitmap_blocks)) < 0) + goto done; + + max_written_blocks = total_ram_blocks; + if (dump_header.device_blocks < total_blocks) { + Warn("dump partition is too small. actual blocks %u. expected blocks %u. whole memory will not be saved", + dump_header.device_blocks, total_blocks); + max_written_blocks -= (total_blocks - dump_header.device_blocks); + } + + dump_header.written_blocks += dump_header.sub_hdr_size; + dump_header.written_blocks += dump_header.bitmap_blocks; + write_header(dump_part); + + printk("dumping memory..\n"); + if ((ret = write_memory(dump_part, header_blocks + bitmap_blocks, + max_written_blocks, &written_blocks)) < 0) + goto done; + + /* + * Set the number of block that is written into and write it + * into partition again. + */ + dump_header.written_blocks += written_blocks; + dump_header.status = DUMP_HEADER_COMPLETED; + write_header(dump_part); + + ret = 0; + +done: + Dbg("do adapter shutdown."); + if (dump_device && dump_device->ops.shutdown) + if (dump_device->ops.shutdown(dump_device)) + Err("adapter shutdown failed."); + + /* + * If diskdump failed and fallback_on_err is set, + * We just return and leave panic to netdump. + */ + if (fallback_on_err && ret != 0) + return; + + Dbg("notify panic."); + notifier_call_chain(&panic_notifier_list, 0, NULL); + + /* Resotre original jiffies. */ + jiffies = diskdump_base_jiffies; + + if (panic_timeout > 0) { + int i; + + printk(KERN_EMERG "Rebooting in %d second%s..", + panic_timeout, "s" + (panic_timeout == 1)); + for (i = 0; i < panic_timeout; i++) { + touch_nmi_watchdog(); + mdelay(1000); + } + printk("\n"); + machine_restart(NULL); + } + printk(KERN_EMERG "halt\n"); + for (;;) { + touch_nmi_watchdog(); + machine_halt(); + mdelay(1000); + } +} + +static struct disk_dump_partition *find_dump_partition(struct block_device *bdev) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + list_for_each_entry(dump_part, &dump_device->partitions, list) + if (dump_part->bdev == bdev) + return dump_part; + return NULL; +} + +static struct disk_dump_device *find_dump_device(struct disk_dump_device *device) +{ + struct disk_dump_device *dump_device; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + if (device->device == dump_device->device) + return dump_device; + return NULL; +} + +/* + * Add dump partition structure corresponding to file to the dump device + * structure. + */ +static int add_dump_partition(struct disk_dump_device *dump_device, struct block_device *bdev) +{ + struct disk_dump_partition *dump_part; + char buffer[BDEVNAME_SIZE]; + + if (!(dump_part = kmalloc(sizeof(*dump_part), GFP_KERNEL))) + return -ENOMEM; + + dump_part->device = dump_device; + dump_part->bdev = bdev; + + if (!bdev || !bdev->bd_part) + return -EINVAL; + dump_part->nr_sects = bdev->bd_part->nr_sects; + dump_part->start_sect = bdev->bd_part->start_sect; + + if (SECTOR_BLOCK(dump_part->nr_sects) < total_blocks) + Warn("%s is too small to save whole system memory\n", + bdevname(bdev, buffer)); + + list_add(&dump_part->list, &dump_device->partitions); + + return 0; +} + +/* + * Add dump device and partition. + * Must be called with disk_dump_mutex held. + */ +static int add_dump(struct disk_dump_device *device, struct block_device *bdev) +{ + int ret; + struct disk_dump_device *dump_device; + + if ((ret = blkdev_get(bdev, FMODE_READ, 0)) < 0) { + kfree(device); + return ret; + } + + /* Check whether this block device is already registered */ + if (find_dump_partition(bdev)) { + kfree(device); + blkdev_put(bdev); + return -EEXIST; + } + + /* Check whether this device is already registered */ + dump_device = find_dump_device(device); + if (dump_device == NULL) { + dump_device = device; + list_add(&dump_device->list, &disk_dump_devices); + } + else + kfree(device); + + ret = add_dump_partition(dump_device, bdev); + if (ret < 0 && list_empty(&dump_device->list)) { + list_del(&dump_device->list); + kfree(dump_device); + blkdev_put(bdev); + } + + return ret; +} + +/* + * Remove dump partition corresponding to bdev. + * Must be called with disk_dump_mutex held. + */ +static int remove_dump(struct block_device *bdev) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + + if (!(dump_part = find_dump_partition(bdev))) { + bdput(bdev); + return -ENOENT; + } + + blkdev_put(bdev); + dump_device = dump_part->device; + list_del(&dump_part->list); + kfree(dump_part); + + if (list_empty(&dump_device->partitions)) { + list_del(&dump_device->list); + kfree(dump_device); + } + + return 0; +} + +int register_disk_dump_device(struct disk_dump_device *device, struct block_device *bdev) +{ + int ret; + + down(&disk_dump_mutex); + ret = add_dump(device, bdev); + set_crc_modules(); + up(&disk_dump_mutex); + + return ret; +} + +EXPORT_SYMBOL(register_disk_dump_device); + +int unregister_disk_dump_device(struct block_device *bdev) +{ + int ret; + + down(&disk_dump_mutex); + ret = remove_dump(bdev); + set_crc_modules(); + up(&disk_dump_mutex); + + return ret; +} + +EXPORT_SYMBOL(unregister_disk_dump_device); + +static inline int find_disk_dump_device(struct block_device *bdev) +{ + int ret; + + down(&disk_dump_mutex); + ret = (find_dump_partition(bdev) != NULL); + up(&disk_dump_mutex); + + return ret; +} + +int print_disk_dump_device(struct device *dev, char *buf) +{ + int part, result, tmp; + struct gendisk *disk; + struct block_device *bdev; + int len = 0, maxlen = 1024; + char* p = buf; + char name[BDEVNAME_SIZE]; + + disk = device_to_gendisk(dev); + if (!disk || !disk->part) + return snprintf(buf, 20, "Error\n"); + + for (part = 0; part < disk->minors - 1; part++) { + bdev = bdget_disk(disk, part); + result = find_disk_dump_device(bdev); + if (result) { + tmp = sprintf(p, "%s\n", bdevname(bdev, name)); + len += tmp; + p += tmp; + } + bdput(bdev); + if(len >= maxlen) + break; + } + + return len; +} + +EXPORT_SYMBOL_GPL(print_disk_dump_device); + +static void compute_total_blocks(void) +{ + unsigned int nr; + + /* + * the number of block of the common header and the header + * that is depend on the architecture + * + * block 0: dump partition header + * block 1: dump header + * block 2: dump subheader + * block 3..n: memory bitmap + * block (n + 1)...: saved memory + * + * We never overwrite block 0 + */ + header_blocks = 2 + size_of_sub_header(); + + total_ram_blocks = 0; + for (nr = 0; nr < max_pfn; nr++) { + if (page_is_ram(nr)) + total_ram_blocks++; + } + + bitmap_blocks = ROUNDUP(max_pfn, 8 * PAGE_SIZE); + + /* + * The necessary size of area for dump is: + * 1 block for common header + * m blocks for architecture dependent header + * n blocks for memory bitmap + * and whole memory + */ + total_blocks = header_blocks + bitmap_blocks + total_ram_blocks; + + Info("total blocks required: %u (header %u + bitmap %u + memory %u)", + total_blocks, header_blocks, bitmap_blocks, total_ram_blocks); +} + +static int init_diskdump(void) +{ + unsigned long long t0; + unsigned long long t1; + struct page *page; + + if (!platform_supports_diskdump) { + Err("platform does not support diskdump."); + return -1; + } + + /* Allocate one block that is used temporally */ + do { + page = alloc_pages(GFP_KERNEL, block_order); + if (page != NULL) + break; + } while (--block_order >= 0); + if (!page) { + Err("alloc_pages failed."); + return -1; + } + scratch = page_address(page); + Info("Maximum block size: %lu", PAGE_SIZE << block_order); + + if (diskdump_register_hook(disk_dump)) { + Err("failed to register hooks."); + return -1; + } + + compute_total_blocks(); + + platform_timestamp(t0); + mdelay(1); + platform_timestamp(t1); + timestamp_1sec = (unsigned long)(t1 - t0) * 1000; + + return 0; +} + +static void cleanup_diskdump(void) +{ + Info("shut down."); + diskdump_unregister_hook(); + free_pages((unsigned long)scratch, block_order); +} + +module_init(init_diskdump); +module_exit(cleanup_diskdump); + +MODULE_LICENSE("GPL"); + diff -Nur linux-2.6.7.org/drivers/char/sysrq.c linux-2.6.7/drivers/char/sysrq.c --- linux-2.6.7.org/drivers/char/sysrq.c 2004-06-22 10:27:55.000000000 +0900 +++ linux-2.6.7/drivers/char/sysrq.c 2004-07-07 14:16:22.000000000 +0900 @@ -107,6 +107,19 @@ .action_msg = "Resetting", }; +/* crash sysrq handler */ +static void sysrq_handle_crash(int key, struct pt_regs *pt_regs, + struct tty_struct *tty) +{ + *( (char *) 0) = 0; +} + +static struct sysrq_key_op sysrq_crash_op = { + .handler = sysrq_handle_crash, + .help_msg = "Crash", + .action_msg = "Crashing the kernel by request", +}; + static void sysrq_handle_sync(int key, struct pt_regs *pt_regs, struct tty_struct *tty) { @@ -235,7 +248,7 @@ it is handled specially on the sparc and will never arrive */ /* b */ &sysrq_reboot_op, -/* c */ NULL, +/* c */ &sysrq_crash_op, /* d */ NULL, /* e */ &sysrq_term_op, /* f */ NULL, diff -Nur linux-2.6.7.org/include/asm-i386/diskdump.h linux-2.6.7/include/asm-i386/diskdump.h --- linux-2.6.7.org/include/asm-i386/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/include/asm-i386/diskdump.h 2004-07-07 14:16:22.000000000 +0900 @@ -0,0 +1,82 @@ +#ifndef _ASM_I386_DISKDUMP_H +#define _ASM_I386_DISKDUMP_H + +/* + * linux/include/asm-i386/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * Copyright (c) 2003 Red Hat, Inc. All rights reserved. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifdef __KERNEL__ + +#include <linux/elf.h> + +extern int page_is_ram(unsigned long); +const static int platform_supports_diskdump = 1; + +#define platform_fix_regs() \ +{ \ + unsigned long esp; \ + unsigned short ss; \ + esp = (unsigned long) ((char *)regs + sizeof (struct pt_regs)); \ + ss = __KERNEL_DS; \ + if (regs->xcs & 3) { \ + esp = regs->esp; \ + ss = regs->xss & 0xffff; \ + } \ + myregs = *regs; \ + myregs.esp = esp; \ + myregs.xss = (myregs.xss & 0xffff0000) | ss; \ +} + +struct disk_dump_sub_header { + elf_gregset_t elf_regs; +}; + +#define platform_timestamp(x) rdtscll(x) + +#define size_of_sub_header() ((sizeof(struct disk_dump_sub_header) + PAGE_SIZE - 1) / DUMP_BLOCK_SIZE) + +#define write_sub_header() \ +({ \ + int ret; \ + \ + ELF_CORE_COPY_REGS(dump_sub_header.elf_regs, (&myregs)); \ + clear_page(scratch); \ + memcpy(scratch, &dump_sub_header, sizeof(dump_sub_header)); \ + \ + if ((ret = write_blocks(dump_part, 2, scratch, 1)) >= 0) \ + ret = 1; /* size of sub header in page */; \ + ret; \ +}) + +#define platform_freeze_cpu() \ +{ \ + for (;;) local_irq_disable(); \ +} + +#define platform_start_diskdump(func, regs) \ +{ \ + func(regs, NULL); \ +} + +#endif /* __KERNEL__ */ + +#endif /* _ASM_I386_DISKDUMP_H */ diff -Nur linux-2.6.7.org/include/asm-i386/kmap_types.h linux-2.6.7/include/asm-i386/kmap_types.h --- linux-2.6.7.org/include/asm-i386/kmap_types.h 2004-06-22 10:27:28.000000000 +0900 +++ linux-2.6.7/include/asm-i386/kmap_types.h 2004-07-07 14:16:22.000000000 +0900 @@ -23,7 +23,8 @@ D(10) KM_IRQ1, D(11) KM_SOFTIRQ0, D(12) KM_SOFTIRQ1, -D(13) KM_TYPE_NR +D(13) KM_DISKDUMP, +D(14) KM_TYPE_NR }; #undef D diff -Nur linux-2.6.7.org/include/linux/diskdump.h linux-2.6.7/include/linux/diskdump.h --- linux-2.6.7.org/include/linux/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/include/linux/diskdump.h 2004-07-07 14:16:22.000000000 +0900 @@ -0,0 +1,165 @@ +#ifndef _LINUX_DISKDUMP_H +#define _LINUX_DISKDUMP_H + +/* + * linux/include/linux/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/list.h> +#include <linux/mount.h> +#include <linux/dcache.h> +#include <linux/blkdev.h> +#include <linux/utsname.h> + +/* The minimum Dump I/O unit. Must be the same of PAGE_SIZE */ +#define DUMP_BLOCK_SIZE PAGE_SIZE +#define DUMP_BLOCK_SHIFT PAGE_SHIFT + +/* Dump ioctls */ +#define BLKADDDUMPDEVICE 0xdf00 /* Add a dump device */ +#define BLKREMOVEDUMPDEVICE 0xdf01 /* Delete a dump device */ + +int diskdump_register_hook(void (*dump_func)(struct pt_regs *, void *)); +void diskdump_unregister_hook(void); + +/* + * The handler that adapter driver provides for the common module of + * dump + */ +struct disk_dump_partition; +struct disk_dump_device; + +struct disk_dump_device_ops { + int (*sanity_check)(struct disk_dump_device *); + int (*quiesce)(struct disk_dump_device *); + int (*shutdown)(struct disk_dump_device *); + int (*rw_block)(struct disk_dump_partition *, int rw, unsigned long block_nr, void *buf, int len); +}; + +/* The data structure for a dump device */ +struct disk_dump_device { + struct list_head list; + struct disk_dump_device_ops ops; + void *device; + unsigned int max_blocks; + struct list_head partitions; +}; + +/* The data structure for a dump partition */ +struct disk_dump_partition { + struct list_head list; + struct disk_dump_device *device; + struct block_device *bdev; + unsigned long start_sect; + unsigned long nr_sects; +}; + +int register_disk_dump_device(struct disk_dump_device *, struct block_device *); +int unregister_disk_dump_device(struct block_device *); +int print_disk_dump_device(struct device *dev, char *buf); + +void diskdump_update(void); +void diskdump_setup_timestamp(void); + +#define device_to_gendisk(dev) \ +({ \ + struct dentry *d; \ + struct qstr qstr; \ + \ + /* trace symlink to "block" */ \ + qstr.name = "block"; \ + qstr.len = strlen(qstr.name); \ + qstr.hash = full_name_hash(qstr.name, qstr.len); \ + d = d_lookup(dev->kobj.dentry, &qstr); \ + (!d || !d->d_fsdata) ? \ + NULL : \ + container_of(d->d_fsdata, struct gendisk, kobj); \ +}) + + +/* + * Architecture-independent dump header + */ + +/* The signature which is written in each block in the dump partition */ +#define DISK_DUMP_SIGNATURE "DISKDUMP" +#define DISK_DUMP_HEADER_VERSION 1 + +#define DUMP_PARTITION_SIGNATURE "diskdump" + +#define DUMP_HEADER_COMPLETED 0 +#define DUMP_HEADER_INCOMPLETED 1 + +struct disk_dump_header { + char signature[8]; /* = "DISKDUMP" */ + int header_version; /* Dump header version */ + struct new_utsname utsname; /* copy of system_utsname */ + struct timespec timestamp; /* Time stamp */ + unsigned int status; /* Above flags */ + int block_size; /* Size of a block in byte */ + int sub_hdr_size; /* Size of arch dependent + header in blocks */ + unsigned int bitmap_blocks; /* Size of Memory bitmap in + block */ + unsigned int max_mapnr; /* = max_mapnr */ + unsigned int total_ram_blocks;/* Size of Memory in block */ + unsigned int device_blocks; /* Number of total blocks in + * the dump device */ + unsigned int written_blocks; /* Number of written blocks */ + unsigned int current_cpu; /* CPU# which handles dump */ + int nr_cpus; /* Number of CPUs */ + struct task_struct *tasks[NR_CPUS]; +}; + +/* + * Calculate the check sum of the whole module + */ +#define get_crc_module() \ +({ \ + struct module *module = &__this_module; \ + crc32_le(0, (char *)(module->module_core), \ + ((unsigned long)module - (unsigned long)(module->module_core))); \ +}) + +/* Calculate the checksum of the whole module */ +#define set_crc_modules() \ +({ \ + module_crc = 0; \ + module_crc = get_crc_module(); \ +}) + +/* + * Compare the checksum value that is stored in module_crc to the check + * sum of current whole module. Must be called with holding disk_dump_lock. + * Return TRUE if they are the same, else return FALSE + * + */ +#define check_crc_module() \ +({ \ + uint32_t orig_crc, cur_crc; \ + \ + orig_crc = module_crc; module_crc = 0; \ + cur_crc = get_crc_module(); \ + module_crc = orig_crc; \ + orig_crc == cur_crc; \ +}) + + +#endif /* _LINUX_DISKDUMP_H */ diff -Nur linux-2.6.7.org/include/linux/interrupt.h linux-2.6.7/include/linux/interrupt.h --- linux-2.6.7.org/include/linux/interrupt.h 2004-06-22 10:27:34.000000000 +0900 +++ linux-2.6.7/include/linux/interrupt.h 2004-07-07 14:16:22.000000000 +0900 @@ -246,4 +246,8 @@ extern int probe_irq_off(unsigned long); /* returns 0 or negative on failure */ extern unsigned int probe_irq_mask(unsigned long); /* returns mask of ISA interrupts */ + +extern void dump_clear_tasklet(void); +extern void dump_run_tasklet(void); + #endif diff -Nur linux-2.6.7.org/include/linux/kernel.h linux-2.6.7/include/linux/kernel.h --- linux-2.6.7.org/include/linux/kernel.h 2004-06-22 10:27:34.000000000 +0900 +++ linux-2.6.7/include/linux/kernel.h 2004-07-07 14:16:22.000000000 +0900 @@ -111,6 +111,11 @@ extern int panic_on_oops; extern int tainted; extern const char *print_tainted(void); +struct pt_regs; +extern void try_crashdump(struct pt_regs *); +extern void (*diskdump_func) (struct pt_regs *regs, void *platform_arg); +extern int diskdump_mode; +#define crashdump_mode() unlikely(diskdump_mode) /* Values used for system_state */ extern enum system_states { @@ -139,6 +144,12 @@ #define pr_info(fmt,arg...) \ printk(KERN_INFO fmt,##arg) +#define pr_err(fmt,arg...) \ + printk(KERN_ERR fmt,##arg) + +#define pr_warn(fmt,arg...) \ + printk(KERN_WARNING fmt,##arg) + /* * Display an IP address in readable format. */ diff -Nur linux-2.6.7.org/include/linux/timer.h linux-2.6.7/include/linux/timer.h --- linux-2.6.7.org/include/linux/timer.h 2004-06-22 10:27:31.000000000 +0900 +++ linux-2.6.7/include/linux/timer.h 2004-07-07 14:16:22.000000000 +0900 @@ -99,4 +99,7 @@ extern void run_local_timers(void); extern void it_real_fn(unsigned long); +extern void dump_clear_timers(void); +extern void dump_run_timers(void); + #endif diff -Nur linux-2.6.7.org/include/linux/workqueue.h linux-2.6.7/include/linux/workqueue.h --- linux-2.6.7.org/include/linux/workqueue.h 2004-06-22 10:27:35.000000000 +0900 +++ linux-2.6.7/include/linux/workqueue.h 2004-07-07 14:16:22.000000000 +0900 @@ -84,4 +84,7 @@ return ret; } +extern void dump_clear_workqueue(void); +extern void dump_run_workqueue(void); + #endif diff -Nur linux-2.6.7.org/kernel/dump.c linux-2.6.7/kernel/dump.c --- linux-2.6.7.org/kernel/dump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/kernel/dump.c 2004-07-07 14:16:22.000000000 +0900 @@ -0,0 +1,121 @@ +/* + * linux/kernel/dump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/nmi.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/genhd.h> +#include <linux/diskdump.h> +#include <asm/diskdump.h> + +int diskdump_mode = 0; +EXPORT_SYMBOL_GPL(diskdump_mode); + +void (*diskdump_func) (struct pt_regs *regs, void *platform_arg) = NULL; +EXPORT_SYMBOL_GPL(diskdump_func); + +static unsigned long long timestamp_base; +static unsigned long timestamp_hz; + +/* + * run timer/tasklet/workqueue during dump + */ +void diskdump_setup_timestamp(void) +{ + unsigned long long t; + + platform_timestamp(timestamp_base); + udelay(1000000/HZ); + platform_timestamp(t); + timestamp_hz = (unsigned long)(t - timestamp_base); + diskdump_update(); +} + +EXPORT_SYMBOL_GPL(diskdump_setup_timestamp); + +void diskdump_update(void) +{ + unsigned long long t; + + touch_nmi_watchdog(); + + /* update jiffies */ + platform_timestamp(t); + while (t > timestamp_base + timestamp_hz) { + timestamp_base += timestamp_hz; + jiffies++; + platform_timestamp(t); + } + + dump_run_timers(); + dump_run_tasklet(); + dump_run_workqueue(); +} + +EXPORT_SYMBOL_GPL(diskdump_update); + +/* + * register/unregister hook + */ +int diskdump_register_hook(void (*dump_func) (struct pt_regs *, void *)) +{ + if (diskdump_func) + return -EEXIST; + + diskdump_func = dump_func; + + return 0; +} + +EXPORT_SYMBOL_GPL(diskdump_register_hook); + +void diskdump_unregister_hook(void) +{ + diskdump_func = NULL; +} + +EXPORT_SYMBOL_GPL(diskdump_unregister_hook); + +/* + * Try crashdump. Diskdump is first, netdump is second. + * We clear diskdump_func before call of diskdump_func, so + * If double panic would occur in diskdump, netdump can handle + * it. + */ +void try_crashdump(struct pt_regs *regs) +{ + void (*func)(struct pt_regs *, void *); + + if (diskdump_func) { + func = diskdump_func; + diskdump_func = NULL; + platform_start_diskdump(func, regs); + } + if (panic_on_oops) + panic("Fatal exception"); +} + diff -Nur linux-2.6.7.org/kernel/panic.c linux-2.6.7/kernel/panic.c --- linux-2.6.7.org/kernel/panic.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/panic.c 2004-07-07 14:16:22.000000000 +0900 @@ -60,6 +60,8 @@ vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); printk(KERN_EMERG "Kernel panic: %s\n",buf); + if (diskdump_func) + BUG(); if (in_interrupt()) printk(KERN_EMERG "In interrupt handler - not syncing\n"); else if (!current->pid) diff -Nur linux-2.6.7.org/kernel/softirq.c linux-2.6.7/kernel/softirq.c --- linux-2.6.7.org/kernel/softirq.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/softirq.c 2004-07-07 14:16:22.000000000 +0900 @@ -314,6 +314,38 @@ EXPORT_SYMBOL(tasklet_kill); +struct tasklet_head saved_tasklet; + +void dump_clear_tasklet(void) +{ + saved_tasklet.list = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = NULL; +} + +EXPORT_SYMBOL(dump_clear_tasklet); + +void dump_run_tasklet(void) +{ + struct tasklet_struct *list; + + list = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = NULL; + + while (list) { + struct tasklet_struct *t = list; + list = list->next; + + if (!atomic_read(&t->count) && + (test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))) + t->func(t->data); + + t->next = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = t; + } +} + +EXPORT_SYMBOL(dump_run_tasklet); + void __init softirq_init(void) { open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); diff -Nur linux-2.6.7.org/kernel/timer.c linux-2.6.7/kernel/timer.c --- linux-2.6.7.org/kernel/timer.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/timer.c 2004-07-07 14:16:22.000000000 +0900 @@ -31,6 +31,7 @@ #include <linux/time.h> #include <linux/jiffies.h> #include <linux/cpu.h> +#include <linux/delay.h> #include <asm/uaccess.h> #include <asm/unistd.h> @@ -423,8 +424,9 @@ static inline void __run_timers(tvec_base_t *base) { struct timer_list *timer; + unsigned long flags; - spin_lock_irq(&base->lock); + spin_lock_irqsave(&base->lock, flags); while (time_after_eq(jiffies, base->timer_jiffies)) { struct list_head work_list = LIST_HEAD_INIT(work_list); struct list_head *head = &work_list; @@ -453,14 +455,14 @@ set_running_timer(base, timer); smp_wmb(); timer->base = NULL; - spin_unlock_irq(&base->lock); + spin_unlock_irqrestore(&base->lock, flags); fn(data); spin_lock_irq(&base->lock); goto repeat; } } set_running_timer(base, NULL); - spin_unlock_irq(&base->lock); + spin_unlock_irqrestore(&base->lock, flags); } #ifdef CONFIG_NO_IDLE_HZ @@ -1105,6 +1107,12 @@ struct timer_list timer; unsigned long expire; + if (crashdump_mode()) { + mdelay(timeout); + set_current_state(TASK_RUNNING); + return timeout; + } + switch (timeout) { case MAX_SCHEDULE_TIMEOUT: @@ -1308,7 +1316,7 @@ return 0; } -static void __devinit init_timers_cpu(int cpu) +static void /* __devinit */ init_timers_cpu(int cpu) { int j; tvec_base_t *base; @@ -1327,6 +1335,27 @@ base->timer_jiffies = jiffies; } +static tvec_base_t saved_tvec_base; + +void dump_clear_timers(void) +{ + tvec_base_t *base = &per_cpu(tvec_bases, smp_processor_id()); + + memcpy(&saved_tvec_base, base, sizeof(saved_tvec_base)); + init_timers_cpu(smp_processor_id()); +} + +EXPORT_SYMBOL(dump_clear_timers); + +void dump_run_timers(void) +{ + tvec_base_t *base = &__get_cpu_var(tvec_bases); + + __run_timers(base); +} + +EXPORT_SYMBOL(dump_run_timers); + #ifdef CONFIG_HOTPLUG_CPU static int migrate_timer_list(tvec_base_t *new_base, struct list_head *head) { diff -Nur linux-2.6.7.org/kernel/workqueue.c linux-2.6.7/kernel/workqueue.c --- linux-2.6.7.org/kernel/workqueue.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/workqueue.c 2004-07-07 14:16:22.000000000 +0900 @@ -424,6 +424,37 @@ } +struct cpu_workqueue_struct saved_cwq; + +void dump_clear_workqueue(void) +{ + int cpu = smp_processor_id(); + struct cpu_workqueue_struct *cwq = keventd_wq->cpu_wq + cpu; + + memcpy(&saved_cwq, cwq, sizeof(saved_cwq)); + spin_lock_init(&cwq->lock); + INIT_LIST_HEAD(&cwq->worklist); + init_waitqueue_head(&cwq->more_work); + init_waitqueue_head(&cwq->work_done); +} + +void dump_run_workqueue(void) +{ + struct cpu_workqueue_struct *cwq; + + cwq = keventd_wq->cpu_wq + smp_processor_id(); + while (!list_empty(&cwq->worklist)) { + struct work_struct *work = list_entry(cwq->worklist.next, + struct work_struct, entry); + void (*f) (void *) = work->func; + void *data = work->data; + + list_del_init(cwq->worklist.next); + clear_bit(0, &work->pending); + f(data); + } +} + #ifdef CONFIG_HOTPLUG_CPU /* Take the work from this (downed) CPU. */ static void take_over_work(struct workqueue_struct *wq, unsigned int cpu) @@ -507,3 +538,6 @@ EXPORT_SYMBOL(schedule_delayed_work); EXPORT_SYMBOL(flush_scheduled_work); +EXPORT_SYMBOL(dump_clear_workqueue); +EXPORT_SYMBOL(dump_run_workqueue); + diff -Nur linux-2.6.7.org/mm/bootmem.c linux-2.6.7/mm/bootmem.c --- linux-2.6.7.org/mm/bootmem.c 2004-06-22 10:28:05.000000000 +0900 +++ linux-2.6.7/mm/bootmem.c 2004-07-07 14:16:22.000000000 +0900 @@ -16,6 +16,7 @@ #include <linux/init.h> #include <linux/bootmem.h> #include <linux/mmzone.h> +#include <linux/module.h> #include <asm/dma.h> #include <asm/io.h> @@ -26,6 +27,7 @@ unsigned long max_low_pfn; unsigned long min_low_pfn; unsigned long max_pfn; +EXPORT_SYMBOL(max_pfn); /* return the number of _pages_ that will be allocated for the boot bitmap */ unsigned long __init bootmem_bootmap_pages (unsigned long pages) ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 0/4][Diskdump]Update patches
@ 2004-07-09 7:15 Takao Indoh
2004-07-09 7:19 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh
0 siblings, 1 reply; 9+ messages in thread
From: Takao Indoh @ 2004-07-09 7:15 UTC (permalink / raw)
To: linux-kernel; +Cc: arjanv, hch
Hi all,
I improved the patches which I posted on 7th Jul.
- Improve sysfs interface code
- Bug fix related to eh_timeout of scsi_cmd
Source code can be downloaded from
http://sourceforge.net/projects/lkdump
On Mon, 21 Jun 2004 10:01:29 +0200, Arjan van de Ven wrote:
>On Mon, Jun 21, 2004 at 04:59:52PM +0900, Takao Indoh wrote:
>> Hi,
>>
>> Now I am fixing diskdump according to comments by you and Christoph.
>>
>> On Fri, 11 Jun 2004 13:50:45 +0200, Arjan van de Ven wrote:
>>
>> >> +#ifdef CONFIG_PROC_FS
>> >> +static int proc_ioctl(struct inode *inode, struct file *file, unsigned
>> >> int cmd, unsigned long param)
>> >
>> >
>> >ehhh this looks evil
>>
>> Do you mean I should use not ioctl but the following style?
>>
>> echo "add /dev/hda1" > /proc/diskdump
>> echo "delete /dev/hda1" > /proc/diskdump
>
>well no since /dev/hda is pointless; major/minor pairs maybe.
>But why in /proc???? it sounds like a sysfs job to me, where you probably
>want to represent a dump relationship with a symlink, and use "rm" to remove
>an entry..
As I wrote in the previous mail, I replaced proc interface with
sysfs.
If you want to add a new dump device /dev/sda3,
# echo 3 > /sys/block/sda/device/dump
If you want to remove the dump device /dev/sda3,
# echo -3 > /sys/block/sda/device/dump
I added a new attribute "dump" to the /sys/block/sdX/device.
diff -Nur linux-2.6.7.org/drivers/scsi/scsi_sysfs.c linux-2.6.7/drivers/scsi/scsi_sysfs.c
--- linux-2.6.7.org/drivers/scsi/scsi_sysfs.c 2004-06-22 10:27:50.000000000 +0900
+++ linux-2.6.7/drivers/scsi/scsi_sysfs.c 2004-07-09 11:36:41.451983216 +0900
@@ -11,6 +11,7 @@
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/device.h>
+#include <linux/diskdump.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_transport.h>
@@ -375,6 +376,7 @@
DEVICE_ATTR(state, S_IRUGO | S_IWUSR, show_state_field, store_state_field);
+DEVICE_ATTR(dump, S_IRUGO | S_IWUSR, diskdump_sysfs_show, diskdump_sysfs_store);
/* Default template for device attributes. May NOT be modified */
static struct device_attribute *scsi_sysfs_sdev_attrs[] = {
@@ -389,6 +391,7 @@
&dev_attr_delete,
&dev_attr_state,
&dev_attr_timeout,
+ &dev_attr_dump,
NULL
};
diskdump_sysfs_show/store is the sysfs handler of diskdump. These
functions passes struct device to the diskdump module.
Diskdump changes "struct device" to "struct scsi_device" like this:
+static void *scsi_dump_probe(struct device *dev)
+{
+ struct scsi_device *sdev;
+
+ if ((dev->bus == NULL) || (dev->bus->name == NULL) ||
+ strncmp(dev->bus->name, "scsi", 4))
+ return NULL;
+
+ sdev = to_scsi_device(dev);
+ if (!sdev->host->hostt->dump_poll)
+ return NULL;
+
+ return sdev;
+}
I used device->bus->name to confirm device is scsi device or not.
Please comment.
Best Regards,
Takao Indoh
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 1/4][Diskdump]Update patches 2004-07-09 7:15 [PATCH 0/4][Diskdump]Update patches Takao Indoh @ 2004-07-09 7:19 ` Takao Indoh 0 siblings, 0 replies; 9+ messages in thread From: Takao Indoh @ 2004-07-09 7:19 UTC (permalink / raw) To: linux-kernel This is a patch for diskdump common layer. diff -Nur linux-2.6.7.org/arch/i386/kernel/nmi.c linux-2.6.7/arch/i386/kernel/nmi.c --- linux-2.6.7.org/arch/i386/kernel/nmi.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/nmi.c 2004-07-09 15:35:35.254904504 +0900 @@ -524,3 +524,4 @@ EXPORT_SYMBOL(release_lapic_nmi); EXPORT_SYMBOL(disable_timer_nmi_watchdog); EXPORT_SYMBOL(enable_timer_nmi_watchdog); +EXPORT_SYMBOL_GPL(touch_nmi_watchdog); diff -Nur linux-2.6.7.org/arch/i386/kernel/reboot.c linux-2.6.7/arch/i386/kernel/reboot.c --- linux-2.6.7.org/arch/i386/kernel/reboot.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/reboot.c 2004-07-09 15:35:35.255904352 +0900 @@ -252,7 +252,8 @@ * Stop all CPUs and turn off local APICs and the IO-APIC, so * other OSs see a clean IRQ state. */ - smp_send_stop(); + if (!crashdump_mode()) + smp_send_stop(); #elif defined(CONFIG_X86_LOCAL_APIC) if (cpu_has_apic) { local_irq_disable(); diff -Nur linux-2.6.7.org/arch/i386/kernel/smp.c linux-2.6.7/arch/i386/kernel/smp.c --- linux-2.6.7.org/arch/i386/kernel/smp.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/smp.c 2004-07-09 15:35:35.255904352 +0900 @@ -520,7 +520,7 @@ return 0; /* Can deadlock when called with interrupts disabled */ - WARN_ON(irqs_disabled()); + WARN_ON(irqs_disabled() && !crashdump_mode()); data.func = func; data.info = info; diff -Nur linux-2.6.7.org/arch/i386/kernel/traps.c linux-2.6.7/arch/i386/kernel/traps.c --- linux-2.6.7.org/arch/i386/kernel/traps.c 2004-06-22 10:27:16.000000000 +0900 +++ linux-2.6.7/arch/i386/kernel/traps.c 2004-07-09 15:35:35.256904200 +0900 @@ -294,6 +294,7 @@ } spinlock_t die_lock = SPIN_LOCK_UNLOCKED; +static int die_owner = -1; void die(const char * str, struct pt_regs * regs, long err) { @@ -301,7 +302,13 @@ int nl = 0; console_verbose(); - spin_lock_irq(&die_lock); + local_irq_disable(); + if (!spin_trylock(&die_lock)) { + if (smp_processor_id() != die_owner) + spin_lock(&die_lock); + /* allow recursive die to fall through */ + } + die_owner = smp_processor_id(); bust_spinlocks(1); handle_BUG(regs); printk(KERN_ALERT "%s: %04lx [#%d]\n", str, err & 0xffff, ++die_counter); @@ -320,7 +327,9 @@ if (nl) printk("\n"); show_registers(regs); + try_crashdump(regs); bust_spinlocks(0); + die_owner = -1; spin_unlock_irq(&die_lock); if (in_interrupt()) panic("Fatal exception in interrupt"); diff -Nur linux-2.6.7.org/arch/i386/mm/init.c linux-2.6.7/arch/i386/mm/init.c --- linux-2.6.7.org/arch/i386/mm/init.c 2004-06-22 10:27:17.000000000 +0900 +++ linux-2.6.7/arch/i386/mm/init.c 2004-07-09 15:35:35.257904048 +0900 @@ -168,7 +168,7 @@ extern int is_available_memory(efi_memory_desc_t *); -static inline int page_is_ram(unsigned long pagenr) +int page_is_ram(unsigned long pagenr) { int i; unsigned long addr, end; @@ -205,6 +205,7 @@ } return 0; } +EXPORT_SYMBOL(page_is_ram); #ifdef CONFIG_HIGHMEM pte_t *kmap_pte; diff -Nur linux-2.6.7.org/drivers/block/Kconfig linux-2.6.7/drivers/block/Kconfig --- linux-2.6.7.org/drivers/block/Kconfig 2004-06-22 10:27:42.000000000 +0900 +++ linux-2.6.7/drivers/block/Kconfig 2004-07-09 15:35:35.257904048 +0900 @@ -347,6 +347,11 @@ your machine, or if you want to have a raid or loopback device bigger than 2TB. Otherwise say N. +config DISKDUMP + tristate "Disk dump support" + ---help--- + Disk dump support. + source "drivers/s390/block/Kconfig" endmenu diff -Nur linux-2.6.7.org/drivers/block/Makefile linux-2.6.7/drivers/block/Makefile --- linux-2.6.7.org/drivers/block/Makefile 2004-06-22 10:27:43.000000000 +0900 +++ linux-2.6.7/drivers/block/Makefile 2004-07-09 15:35:35.258903896 +0900 @@ -43,3 +43,4 @@ obj-$(CONFIG_VIODASD) += viodasd.o obj-$(CONFIG_BLK_DEV_CARMEL) += carmel.o +obj-$(CONFIG_DISKDUMP) += diskdump.o diff -Nur linux-2.6.7.org/drivers/block/diskdump.c linux-2.6.7/drivers/block/diskdump.c --- linux-2.6.7.org/drivers/block/diskdump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/drivers/block/diskdump.c 2004-07-09 15:35:35.260903592 +0900 @@ -0,0 +1,895 @@ +/* + * linux/drivers/block/diskdump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Copyright (C) 2002 Red Hat, Inc. + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + * Some codes were derived from netdump and copyright belongs to + * Red Hat, Inc. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/file.h> +#include <linux/reboot.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/highmem.h> +#include <linux/utsname.h> +#include <linux/console.h> +#include <linux/smp_lock.h> +#include <linux/nmi.h> +#include <linux/genhd.h> +#include <linux/crc32.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/diskdump.h> +#include <asm/diskdump.h> + +#define Dbg(x, ...) pr_debug("disk_dump: " x "\n", ## __VA_ARGS__) +#define Err(x, ...) pr_err ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Warn(x, ...) pr_warn ("disk_dump: " x "\n", ## __VA_ARGS__) +#define Info(x, ...) pr_info ("disk_dump: " x "\n", ## __VA_ARGS__) + +#define ROUNDUP(x, y) (((x) + ((y)-1))/(y)) + +/* 512byte sectors to blocks */ +#define SECTOR_BLOCK(s) ((s) >> (DUMP_BLOCK_SHIFT - 9)) + +static unsigned int fallback_on_err = 1; +static unsigned int allow_risky_dumps = 1; +static unsigned int block_order = 2; +static int sample_rate = 8; +module_param(fallback_on_err, uint, 0); +module_param(allow_risky_dumps, uint, 0); +module_param(block_order, uint, 0); +module_param(sample_rate, int, 0); + +static unsigned long timestamp_1sec; +static uint32_t module_crc; +static char *scratch; +static struct disk_dump_header dump_header; +static struct disk_dump_sub_header dump_sub_header; + +/* Registered dump devices */ +static LIST_HEAD(disk_dump_devices); + +/* Registered dump types, e.g. SCSI, ... */ +static LIST_HEAD(disk_dump_types); + +static DECLARE_MUTEX(disk_dump_mutex); + +static unsigned int header_blocks; /* The size of all headers */ +static unsigned int bitmap_blocks; /* The size of bitmap header */ +static unsigned int total_ram_blocks; /* The size of memory */ +static unsigned int total_blocks; /* The sum of above */ + +struct notifier_block *disk_dump_notifier_list; +EXPORT_SYMBOL(disk_dump_notifier_list); + +unsigned long volatile diskdump_base_jiffies; + +extern int panic_timeout; +extern unsigned long max_pfn; + +#if CONFIG_SMP +static void freeze_cpu(void *dummy) +{ + unsigned int cpu = smp_processor_id(); + + dump_header.tasks[cpu] = current; + + platform_freeze_cpu(); +} +#endif + +static int lapse = 0; /* 200msec unit */ + +static inline unsigned long eta(unsigned long nr, unsigned long maxnr) +{ + unsigned long long eta; + + eta = ((maxnr << 8) / nr) * (unsigned long long)lapse; + + return (unsigned long)(eta >> 8) - lapse; +} + +static inline void print_status(unsigned int nr, unsigned int maxnr) +{ + static char *spinner = "/|\\-"; + static unsigned long long prev_timestamp = 0; + unsigned long long timestamp; + + platform_timestamp(timestamp); + + if (timestamp - prev_timestamp > (timestamp_1sec/5)) { + prev_timestamp = timestamp; + lapse++; + printk("%u/%u %lu ETA %c \r", + nr, maxnr, eta(nr, maxnr) / 5, spinner[lapse & 3]); + } +} + +static inline void clear_status(int nr, int maxnr) +{ + printk(" \r"); + lapse = 0; +} + +/* + * Checking the signature on a block. The format is as follows. + * + * 1st word = 'disk' + * 2nd word = 'dump' + * 3rd word = block number + * 4th word = ((block number + 7) * 11) & 0xffffffff + * 5th word = ((4th word + 7)* 11) & 0xffffffff + * .. + * + * Return 1 if the signature is correct, else return 0 + */ +static int check_block_signature(void *buf, unsigned int block_nr) +{ + int word_nr = PAGE_SIZE / sizeof(int); + int *words = buf; + unsigned int val; + int i; + + if (memcmp(buf, DUMP_PARTITION_SIGNATURE, sizeof(*words))) + return 0; + + val = block_nr; + for (i = 2; i < word_nr; i++) { + if (words[i] != val) + return 0; + val = (val + 7) * 11; + } + + return 1; +} + +/* + * Read one block into the dump partition + */ +static int read_blocks(struct disk_dump_partition *dump_part, unsigned int nr, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + local_irq_disable(); + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, READ, nr, buf, len); + if (ret < 0) { + Err("read error on block %u", nr); + return ret; + } + return 0; +} + +static int write_blocks(struct disk_dump_partition *dump_part, unsigned int offs, char *buf, int len) +{ + struct disk_dump_device *device = dump_part->device; + int ret; + + local_irq_disable(); + touch_nmi_watchdog(); + ret = device->ops.rw_block(dump_part, WRITE, offs, buf, len); + if (ret < 0) { + Err("write error on block %u", offs); + return ret; + } + return 0; +} + +/* + * Initialize the common header + */ + +/* + * Write the common header + */ +static int write_header(struct disk_dump_partition *dump_part) +{ + memset(scratch, '\0', PAGE_SIZE); + memcpy(scratch, &dump_header, sizeof(dump_header)); + + return write_blocks(dump_part, 1, scratch, 1); +} + +/* + * Check the signaures in all blocks of the dump partition + * Return 1 if the signature is correct, else return 0 + */ +static int check_dump_partition(struct disk_dump_partition *dump_part, unsigned int partition_size) +{ + unsigned int blk; + int ret; + unsigned int chunk_blks, skips; + int i; + + if (sample_rate < 0) /* No check */ + return 1; + + /* + * If the device has limitations of transfer size, use it. + */ + chunk_blks = 1 << block_order; + if (dump_part->device->max_blocks) + chunk_blks = min(chunk_blks, dump_part->device->max_blocks); + skips = chunk_blks << sample_rate; + + lapse = 0; + for (blk = 0; blk < partition_size; blk += skips) { + unsigned int len; +redo: + len = min(chunk_blks, partition_size - blk); + if ((ret = read_blocks(dump_part, blk, scratch, len)) < 0) + return 0; + print_status(blk + 1, partition_size); + for (i = 0; i < len; i++) + if (!check_block_signature(scratch + i * DUMP_BLOCK_SIZE, blk + i)) { + Err("bad signature in block %u", blk + i); + return 0; + } + } + /* Check the end of the dump partition */ + if (blk - skips + chunk_blks < partition_size) { + blk = partition_size - chunk_blks; + goto redo; + } + clear_status(blk, partition_size); + return 1; +} + +/* + * Write memory bitmap after location of dump headers. + */ +#define IDX2PAGENR(nr, byte, bit) (((nr) * PAGE_SIZE + (byte)) * 8 + (bit)) +static int write_bitmap(struct disk_dump_partition *dump_part, unsigned int bitmap_offset, unsigned int bitmap_blocks) +{ + unsigned int nr; + int bit, byte; + int ret = 0; + unsigned char val; + + for (nr = 0; nr < bitmap_blocks; nr++) { + for (byte = 0; byte < PAGE_SIZE; byte++) { + val = 0; + for (bit = 0; bit < 8; bit++) + if (page_is_ram(IDX2PAGENR(nr, byte, bit))) + val |= (1 << bit); + scratch[byte] = (char)val; + } + if ((ret = write_blocks(dump_part, bitmap_offset + nr, scratch, 1)) < 0) { + Err("I/O error %d on block %u", ret, bitmap_offset + nr); + break; + } + } + return ret; +} + +/* + * Write whole memory to dump partition. + * Return value is the number of writen blocks. + */ +static int write_memory(struct disk_dump_partition *dump_part, int offset, unsigned int max_blocks_written, unsigned int *blocks_written) +{ + char *kaddr; + unsigned int blocks = 0; + struct page *page; + unsigned int nr; + int ret = 0; + int blk_in_chunk = 0; + + for (nr = 0; nr < max_pfn; nr++) { + print_status(blocks, max_blocks_written); + + if (!page_is_ram(nr)) + continue; + + if (blocks >= max_blocks_written) { + Warn("dump device is too small. %lu pages were not saved", max_pfn - blocks); + goto out; + } + page = pfn_to_page(nr); + kaddr = (char *)kmap_atomic(page, KM_DISKDUMP); + /* + * need to copy because adapter drivers use virt_to_bus() + */ + memcpy(scratch + blk_in_chunk * PAGE_SIZE, kaddr, PAGE_SIZE); + blk_in_chunk++; + blocks++; + kunmap_atomic(kaddr, KM_DISKDUMP); + + if (blk_in_chunk >= (1 << block_order)) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) { + Err("I/O error %d on block %u", ret, offset); + break; + } + offset += blk_in_chunk; + blk_in_chunk = 0; + } + } + if (ret >= 0 && blk_in_chunk > 0) { + ret = write_blocks(dump_part, offset, scratch, blk_in_chunk); + if (ret < 0) + Err("I/O error %d on block %u", ret, offset); + } + +out: + clear_status(nr, max_blocks_written); + + *blocks_written = blocks; + return ret; +} + +/* + * Select most suitable dump device. sanity_check() returns the state + * of each dump device. 0 means OK, negative value means NG, and + * positive value means it maybe work. select_dump_partition() first + * try to select a sane device and if it has no sane device and + * allow_risky_dumps is set, it select one from maybe OK devices. + * + * XXX We cannot handle multiple partitions yet. + */ +static struct disk_dump_partition *select_dump_partition(void) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + int sanity; + int strict_check = 1; + +redo: + /* + * Select a sane polling driver. + */ + list_for_each_entry(dump_device, &disk_dump_devices, list) { + sanity = 0; + if (dump_device->ops.sanity_check) + sanity = dump_device->ops.sanity_check(dump_device); + if (sanity < 0 || (sanity > 0 && strict_check)) + continue; + list_for_each_entry(dump_part, &dump_device->partitions, list) + return dump_part; + } + if (allow_risky_dumps && strict_check) { + strict_check = 0; + goto redo; + } + return NULL; +} + +static void disk_dump(struct pt_regs *regs, void *platform_arg) +{ + unsigned long flags; + int ret = -EIO; + struct pt_regs myregs; + unsigned int max_written_blocks, written_blocks; + int i; + struct disk_dump_device *dump_device = NULL; + struct disk_dump_partition *dump_part = NULL; + + /* Inhibit interrupt and stop other CPUs */ + local_irq_save(flags); + preempt_disable(); + + /* + * Check the checksum of myself + */ + if (down_trylock(&disk_dump_mutex)) { + Err("down_trylock(disk_dump_mutex) failed."); + goto done; + } + + if (!check_crc_module()) { + Err("checksum error. diskdump common module may be compromised."); + goto done; + } + + diskdump_mode = 1; + + Dbg("notify dump start."); + notifier_call_chain(&disk_dump_notifier_list, 0, NULL); + + dump_header.tasks[smp_processor_id()] = current; +#if CONFIG_SMP + smp_call_function(freeze_cpu, NULL, 1, 0); + mdelay(3000); + printk("CPU frozen: "); + for (i = 0; i < NR_CPUS; i++) { + if (dump_header.tasks[i] != NULL) + printk("#%d", i); + + } + printk("\n"); + printk("CPU#%d is executing diskdump.\n", smp_processor_id()); +#else + mdelay(1000); +#endif + + /* + * Setup timer/tasklet + */ + dump_clear_timers(); + dump_clear_tasklet(); + dump_clear_workqueue(); + + /* Save original jiffies value */ + diskdump_base_jiffies = jiffies; + + diskdump_setup_timestamp(); + + platform_fix_regs(); + + if (list_empty(&disk_dump_devices)) { + Err("adapter driver is not registered."); + goto done; + } + + printk("start dumping\n"); + + if (!(dump_part = select_dump_partition())) { + Err("No sane dump device found"); + goto done; + } + dump_device = dump_part->device; + + /* + * Stop ongoing I/O with polling driver and make the shift to I/O mode + * for dump + */ + Dbg("do quiesce"); + if (dump_device->ops.quiesce) + if ((ret = dump_device->ops.quiesce(dump_device)) < 0) { + Err("quiesce failed. error %d", ret); + goto done; + } + + if (SECTOR_BLOCK(dump_part->nr_sects) < header_blocks + bitmap_blocks) { + Warn("dump partition is too small. Aborted"); + goto done; + } + + /* Check dump partition */ + printk("check dump partition...\n"); + if (!check_dump_partition(dump_part, total_blocks)) { + Err("check partition failed."); + goto done; + } + + /* + * Write the common header + */ + memcpy(dump_header.signature, DISK_DUMP_SIGNATURE, sizeof(dump_header.signature)); + dump_header.utsname = system_utsname; + dump_header.timestamp = xtime; + dump_header.status = DUMP_HEADER_INCOMPLETED; + dump_header.block_size = PAGE_SIZE; + dump_header.sub_hdr_size = size_of_sub_header(); + dump_header.bitmap_blocks = bitmap_blocks; + dump_header.max_mapnr = max_pfn; + dump_header.total_ram_blocks = total_ram_blocks; + dump_header.device_blocks = SECTOR_BLOCK(dump_part->nr_sects); + dump_header.current_cpu = smp_processor_id(); + dump_header.nr_cpus = num_online_cpus(); + dump_header.written_blocks = 2; + + write_header(dump_part); + + /* + * Write the architecture dependent header + */ + Dbg("write sub header"); + if ((ret = write_sub_header()) < 0) { + Err("writing sub header failed. error %d", ret); + goto done; + } + + Dbg("writing memory bitmaps.."); + if ((ret = write_bitmap(dump_part, header_blocks, bitmap_blocks)) < 0) + goto done; + + max_written_blocks = total_ram_blocks; + if (dump_header.device_blocks < total_blocks) { + Warn("dump partition is too small. actual blocks %u. expected blocks %u. whole memory will not be saved", + dump_header.device_blocks, total_blocks); + max_written_blocks -= (total_blocks - dump_header.device_blocks); + } + + dump_header.written_blocks += dump_header.sub_hdr_size; + dump_header.written_blocks += dump_header.bitmap_blocks; + write_header(dump_part); + + printk("dumping memory..\n"); + if ((ret = write_memory(dump_part, header_blocks + bitmap_blocks, + max_written_blocks, &written_blocks)) < 0) + goto done; + + /* + * Set the number of block that is written into and write it + * into partition again. + */ + dump_header.written_blocks += written_blocks; + dump_header.status = DUMP_HEADER_COMPLETED; + write_header(dump_part); + + ret = 0; + +done: + Dbg("do adapter shutdown."); + if (dump_device && dump_device->ops.shutdown) + if (dump_device->ops.shutdown(dump_device)) + Err("adapter shutdown failed."); + + /* + * If diskdump failed and fallback_on_err is set, + * We just return and leave panic to netdump. + */ + if (fallback_on_err && ret != 0) + return; + + Dbg("notify panic."); + notifier_call_chain(&panic_notifier_list, 0, NULL); + + /* Resotre original jiffies. */ + jiffies = diskdump_base_jiffies; + + if (panic_timeout > 0) { + int i; + + printk(KERN_EMERG "Rebooting in %d second%s..", + panic_timeout, "s" + (panic_timeout == 1)); + for (i = 0; i < panic_timeout; i++) { + touch_nmi_watchdog(); + mdelay(1000); + } + printk("\n"); + machine_restart(NULL); + } + printk(KERN_EMERG "halt\n"); + for (;;) { + touch_nmi_watchdog(); + machine_halt(); + mdelay(1000); + } +} + +static struct disk_dump_partition *find_dump_partition(struct block_device *bdev) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + list_for_each_entry(dump_part, &dump_device->partitions, list) + if (dump_part->bdev == bdev) + return dump_part; + return NULL; +} + +static struct disk_dump_device *find_dump_device(struct disk_dump_device *device) +{ + struct disk_dump_device *dump_device; + + list_for_each_entry(dump_device, &disk_dump_devices, list) + if (device->device == dump_device->device) + return dump_device; + return NULL; +} + +static void *find_real_device(struct device *dev, struct disk_dump_type **_dump_type) +{ + void *real_device; + struct disk_dump_type *dump_type; + + list_for_each_entry(dump_type, &disk_dump_types, list) + if ((real_device = dump_type->probe(dev)) != NULL) { + *_dump_type = dump_type; + return real_device; + } + return NULL; +} + +/* + * Add dump partition structure corresponding to file to the dump device + * structure. + */ +static int add_dump_partition(struct disk_dump_device *dump_device, struct block_device *bdev) +{ + struct disk_dump_partition *dump_part; + char buffer[BDEVNAME_SIZE]; + + if (!(dump_part = kmalloc(sizeof(*dump_part), GFP_KERNEL))) + return -ENOMEM; + + dump_part->device = dump_device; + dump_part->bdev = bdev; + + if (!bdev || !bdev->bd_part) + return -EINVAL; + dump_part->nr_sects = bdev->bd_part->nr_sects; + dump_part->start_sect = bdev->bd_part->start_sect; + + if (SECTOR_BLOCK(dump_part->nr_sects) < total_blocks) + Warn("%s is too small to save whole system memory\n", + bdevname(bdev, buffer)); + + list_add(&dump_part->list, &dump_device->partitions); + + return 0; +} + +/* + * Add dump device and partition. + * Must be called with disk_dump_mutex held. + */ +static int add_dump(struct device *dev, struct block_device *bdev) +{ + struct disk_dump_type *dump_type = NULL; + struct disk_dump_device *dump_device; + void *real_device; + int ret; + + if ((ret = blkdev_get(bdev, FMODE_READ, 0)) < 0) + return ret; + + /* Check whether this block device is already registered */ + if (find_dump_partition(bdev)) { + blkdev_put(bdev); + return -EEXIST; + } + + /* find dump_type and real device for this inode */ + if (!(real_device = find_real_device(dev, &dump_type))) { + blkdev_put(bdev); + return -ENXIO; + } + + /* Check whether this device is already registered */ + dump_device = find_dump_device(real_device); + if (dump_device == NULL) { + /* real_device is not registered. create new dump_device */ + if (!(dump_device = kmalloc(sizeof(*dump_device), GFP_KERNEL))) { + blkdev_put(bdev); + return -ENOMEM; + } + + memset(dump_device, 0, sizeof(*dump_device)); + INIT_LIST_HEAD(&dump_device->partitions); + + dump_device->dump_type = dump_type; + dump_device->device = real_device; + if ((ret = dump_type->add_device(dump_device)) < 0) { + kfree(dump_device); + blkdev_put(bdev); + return ret; + } + if (!try_module_get(dump_type->owner)) + return -EINVAL; + list_add(&dump_device->list, &disk_dump_devices); + } + + ret = add_dump_partition(dump_device, bdev); + if (ret < 0 && list_empty(&dump_device->list)) { + dump_type->remove_device(dump_device); + module_put(dump_type->owner); + list_del(&dump_device->list); + kfree(dump_device); + } + if (ret < 0) + blkdev_put(bdev); + + return ret; +} + +/* + * Remove dump partition corresponding to bdev. + * Must be called with disk_dump_mutex held. + */ +static int remove_dump(struct block_device *bdev) +{ + struct disk_dump_device *dump_device; + struct disk_dump_partition *dump_part; + struct disk_dump_type *dump_type; + + if (!(dump_part = find_dump_partition(bdev))) { + bdput(bdev); + return -ENOENT; + } + + blkdev_put(bdev); + dump_device = dump_part->device; + list_del(&dump_part->list); + kfree(dump_part); + + if (list_empty(&dump_device->partitions)) { + dump_type = dump_device->dump_type; + dump_type->remove_device(dump_device); + module_put(dump_type->owner); + list_del(&dump_device->list); + kfree(dump_device); + } + + return 0; +} + +int register_disk_dump_device(struct device *dev, struct block_device *bdev) +{ + int ret; + + down(&disk_dump_mutex); + ret = add_dump(dev, bdev); + set_crc_modules(); + up(&disk_dump_mutex); + + return ret; +} + +int unregister_disk_dump_device(struct block_device *bdev) +{ + int ret; + + down(&disk_dump_mutex); + ret = remove_dump(bdev); + set_crc_modules(); + up(&disk_dump_mutex); + + return ret; +} + +int find_disk_dump_device(struct block_device *bdev) +{ + int ret; + + down(&disk_dump_mutex); + ret = (find_dump_partition(bdev) != NULL); + up(&disk_dump_mutex); + + return ret; +} + +int register_disk_dump_type(struct disk_dump_type *dump_type) +{ + down(&disk_dump_mutex); + list_add(&dump_type->list, &disk_dump_types); + set_crc_modules(); + up(&disk_dump_mutex); + + return 0; +} + +EXPORT_SYMBOL(register_disk_dump_type); + +int unregister_disk_dump_type(struct disk_dump_type *dump_type) +{ + down(&disk_dump_mutex); + list_del(&dump_type->list); + set_crc_modules(); + up(&disk_dump_mutex); + + return 0; +} + +EXPORT_SYMBOL(unregister_disk_dump_type); + +static void compute_total_blocks(void) +{ + unsigned int nr; + + /* + * the number of block of the common header and the header + * that is depend on the architecture + * + * block 0: dump partition header + * block 1: dump header + * block 2: dump subheader + * block 3..n: memory bitmap + * block (n + 1)...: saved memory + * + * We never overwrite block 0 + */ + header_blocks = 2 + size_of_sub_header(); + + total_ram_blocks = 0; + for (nr = 0; nr < max_pfn; nr++) { + if (page_is_ram(nr)) + total_ram_blocks++; + } + + bitmap_blocks = ROUNDUP(max_pfn, 8 * PAGE_SIZE); + + /* + * The necessary size of area for dump is: + * 1 block for common header + * m blocks for architecture dependent header + * n blocks for memory bitmap + * and whole memory + */ + total_blocks = header_blocks + bitmap_blocks + total_ram_blocks; + + Info("total blocks required: %u (header %u + bitmap %u + memory %u)", + total_blocks, header_blocks, bitmap_blocks, total_ram_blocks); +} + +struct disk_dump_ops dump_ops = { + .add_dump = register_disk_dump_device, + .remove_dump = unregister_disk_dump_device, + .find_dump = find_disk_dump_device, +}; + +static int init_diskdump(void) +{ + unsigned long long t0; + unsigned long long t1; + struct page *page; + + if (!platform_supports_diskdump) { + Err("platform does not support diskdump."); + return -1; + } + + /* Allocate one block that is used temporally */ + do { + page = alloc_pages(GFP_KERNEL, block_order); + if (page != NULL) + break; + } while (--block_order >= 0); + if (!page) { + Err("alloc_pages failed."); + return -1; + } + scratch = page_address(page); + Info("Maximum block size: %lu", PAGE_SIZE << block_order); + + if (diskdump_register_hook(disk_dump)) { + Err("failed to register hooks."); + return -1; + } + + if (diskdump_register_ops(&dump_ops)) { + Err("failed to register ops."); + return -1; + } + + compute_total_blocks(); + + platform_timestamp(t0); + mdelay(1); + platform_timestamp(t1); + timestamp_1sec = (unsigned long)(t1 - t0) * 1000; + + return 0; +} + +static void cleanup_diskdump(void) +{ + Info("shut down."); + diskdump_unregister_hook(); + diskdump_unregister_ops(); + free_pages((unsigned long)scratch, block_order); +} + +module_init(init_diskdump); +module_exit(cleanup_diskdump); + +MODULE_LICENSE("GPL"); + diff -Nur linux-2.6.7.org/drivers/char/sysrq.c linux-2.6.7/drivers/char/sysrq.c --- linux-2.6.7.org/drivers/char/sysrq.c 2004-06-22 10:27:55.000000000 +0900 +++ linux-2.6.7/drivers/char/sysrq.c 2004-07-09 15:35:35.260903592 +0900 @@ -107,6 +107,19 @@ .action_msg = "Resetting", }; +/* crash sysrq handler */ +static void sysrq_handle_crash(int key, struct pt_regs *pt_regs, + struct tty_struct *tty) +{ + *( (char *) 0) = 0; +} + +static struct sysrq_key_op sysrq_crash_op = { + .handler = sysrq_handle_crash, + .help_msg = "Crash", + .action_msg = "Crashing the kernel by request", +}; + static void sysrq_handle_sync(int key, struct pt_regs *pt_regs, struct tty_struct *tty) { @@ -235,7 +248,7 @@ it is handled specially on the sparc and will never arrive */ /* b */ &sysrq_reboot_op, -/* c */ NULL, +/* c */ &sysrq_crash_op, /* d */ NULL, /* e */ &sysrq_term_op, /* f */ NULL, diff -Nur linux-2.6.7.org/include/asm-i386/diskdump.h linux-2.6.7/include/asm-i386/diskdump.h --- linux-2.6.7.org/include/asm-i386/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/include/asm-i386/diskdump.h 2004-07-09 15:35:35.274901464 +0900 @@ -0,0 +1,82 @@ +#ifndef _ASM_I386_DISKDUMP_H +#define _ASM_I386_DISKDUMP_H + +/* + * linux/include/asm-i386/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * Copyright (c) 2003 Red Hat, Inc. All rights reserved. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifdef __KERNEL__ + +#include <linux/elf.h> + +extern int page_is_ram(unsigned long); +const static int platform_supports_diskdump = 1; + +#define platform_fix_regs() \ +{ \ + unsigned long esp; \ + unsigned short ss; \ + esp = (unsigned long) ((char *)regs + sizeof (struct pt_regs)); \ + ss = __KERNEL_DS; \ + if (regs->xcs & 3) { \ + esp = regs->esp; \ + ss = regs->xss & 0xffff; \ + } \ + myregs = *regs; \ + myregs.esp = esp; \ + myregs.xss = (myregs.xss & 0xffff0000) | ss; \ +} + +struct disk_dump_sub_header { + elf_gregset_t elf_regs; +}; + +#define platform_timestamp(x) rdtscll(x) + +#define size_of_sub_header() ((sizeof(struct disk_dump_sub_header) + PAGE_SIZE - 1) / DUMP_BLOCK_SIZE) + +#define write_sub_header() \ +({ \ + int ret; \ + \ + ELF_CORE_COPY_REGS(dump_sub_header.elf_regs, (&myregs)); \ + clear_page(scratch); \ + memcpy(scratch, &dump_sub_header, sizeof(dump_sub_header)); \ + \ + if ((ret = write_blocks(dump_part, 2, scratch, 1)) >= 0) \ + ret = 1; /* size of sub header in page */; \ + ret; \ +}) + +#define platform_freeze_cpu() \ +{ \ + for (;;) local_irq_disable(); \ +} + +#define platform_start_diskdump(func, regs) \ +{ \ + func(regs, NULL); \ +} + +#endif /* __KERNEL__ */ + +#endif /* _ASM_I386_DISKDUMP_H */ diff -Nur linux-2.6.7.org/include/asm-i386/kmap_types.h linux-2.6.7/include/asm-i386/kmap_types.h --- linux-2.6.7.org/include/asm-i386/kmap_types.h 2004-06-22 10:27:28.000000000 +0900 +++ linux-2.6.7/include/asm-i386/kmap_types.h 2004-07-09 15:35:35.275901312 +0900 @@ -23,7 +23,8 @@ D(10) KM_IRQ1, D(11) KM_SOFTIRQ0, D(12) KM_SOFTIRQ1, -D(13) KM_TYPE_NR +D(13) KM_DISKDUMP, +D(14) KM_TYPE_NR }; #undef D diff -Nur linux-2.6.7.org/include/linux/diskdump.h linux-2.6.7/include/linux/diskdump.h --- linux-2.6.7.org/include/linux/diskdump.h 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/include/linux/diskdump.h 2004-07-09 15:35:35.276901160 +0900 @@ -0,0 +1,172 @@ +#ifndef _LINUX_DISKDUMP_H +#define _LINUX_DISKDUMP_H + +/* + * linux/include/linux/diskdump.h + * + * Copyright (c) 2004 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/list.h> +#include <linux/blkdev.h> +#include <linux/utsname.h> +#include <linux/device.h> + +/* The minimum Dump I/O unit. Must be the same of PAGE_SIZE */ +#define DUMP_BLOCK_SIZE PAGE_SIZE +#define DUMP_BLOCK_SHIFT PAGE_SHIFT + +int diskdump_register_hook(void (*dump_func)(struct pt_regs *, void *)); +void diskdump_unregister_hook(void); + +/* + * The handler of diskdump module + */ +struct disk_dump_ops { + int (*add_dump)(struct device *, struct block_device *); + int (*remove_dump)(struct block_device *); + int (*find_dump)(struct block_device *); +}; + +int diskdump_register_ops(struct disk_dump_ops* op); +void diskdump_unregister_ops(void); + + +/* + * The handler that adapter driver provides for the common module of + * dump + */ +struct disk_dump_partition; +struct disk_dump_device; + +struct disk_dump_type { + void *(*probe)(struct device *); + int (*add_device)(struct disk_dump_device *); + void (*remove_device)(struct disk_dump_device *); + struct module *owner; + struct list_head list; +}; + +struct disk_dump_device_ops { + int (*sanity_check)(struct disk_dump_device *); + int (*quiesce)(struct disk_dump_device *); + int (*shutdown)(struct disk_dump_device *); + int (*rw_block)(struct disk_dump_partition *, int rw, unsigned long block_nr, void *buf, int len); +}; + +/* The data structure for a dump device */ +struct disk_dump_device { + struct list_head list; + struct disk_dump_device_ops ops; + struct disk_dump_type *dump_type; + void *device; + unsigned int max_blocks; + struct list_head partitions; +}; + +/* The data structure for a dump partition */ +struct disk_dump_partition { + struct list_head list; + struct disk_dump_device *device; + struct block_device *bdev; + unsigned long start_sect; + unsigned long nr_sects; +}; + +int register_disk_dump_type(struct disk_dump_type *); +int unregister_disk_dump_type(struct disk_dump_type *); + + +/* + * sysfs interface + */ +ssize_t diskdump_sysfs_store(struct device *dev, const char *buf, size_t count); +ssize_t diskdump_sysfs_show(struct device *dev, char *buf); + +void diskdump_update(void); +void diskdump_setup_timestamp(void); + +/* + * Architecture-independent dump header + */ + +/* The signature which is written in each block in the dump partition */ +#define DISK_DUMP_SIGNATURE "DISKDUMP" +#define DISK_DUMP_HEADER_VERSION 1 + +#define DUMP_PARTITION_SIGNATURE "diskdump" + +#define DUMP_HEADER_COMPLETED 0 +#define DUMP_HEADER_INCOMPLETED 1 + +struct disk_dump_header { + char signature[8]; /* = "DISKDUMP" */ + int header_version; /* Dump header version */ + struct new_utsname utsname; /* copy of system_utsname */ + struct timespec timestamp; /* Time stamp */ + unsigned int status; /* Above flags */ + int block_size; /* Size of a block in byte */ + int sub_hdr_size; /* Size of arch dependent + header in blocks */ + unsigned int bitmap_blocks; /* Size of Memory bitmap in + block */ + unsigned int max_mapnr; /* = max_mapnr */ + unsigned int total_ram_blocks;/* Size of Memory in block */ + unsigned int device_blocks; /* Number of total blocks in + * the dump device */ + unsigned int written_blocks; /* Number of written blocks */ + unsigned int current_cpu; /* CPU# which handles dump */ + int nr_cpus; /* Number of CPUs */ + struct task_struct *tasks[NR_CPUS]; +}; + +/* + * Calculate the check sum of the whole module + */ +#define get_crc_module() \ +({ \ + struct module *module = &__this_module; \ + crc32_le(0, (char *)(module->module_core), \ + ((unsigned long)module - (unsigned long)(module->module_core))); \ +}) + +/* Calculate the checksum of the whole module */ +#define set_crc_modules() \ +({ \ + module_crc = 0; \ + module_crc = get_crc_module(); \ +}) + +/* + * Compare the checksum value that is stored in module_crc to the check + * sum of current whole module. Must be called with holding disk_dump_lock. + * Return TRUE if they are the same, else return FALSE + * + */ +#define check_crc_module() \ +({ \ + uint32_t orig_crc, cur_crc; \ + \ + orig_crc = module_crc; module_crc = 0; \ + cur_crc = get_crc_module(); \ + module_crc = orig_crc; \ + orig_crc == cur_crc; \ +}) + + +#endif /* _LINUX_DISKDUMP_H */ diff -Nur linux-2.6.7.org/include/linux/interrupt.h linux-2.6.7/include/linux/interrupt.h --- linux-2.6.7.org/include/linux/interrupt.h 2004-06-22 10:27:34.000000000 +0900 +++ linux-2.6.7/include/linux/interrupt.h 2004-07-09 15:35:35.276901160 +0900 @@ -246,4 +246,8 @@ extern int probe_irq_off(unsigned long); /* returns 0 or negative on failure */ extern unsigned int probe_irq_mask(unsigned long); /* returns mask of ISA interrupts */ + +extern void dump_clear_tasklet(void); +extern void dump_run_tasklet(void); + #endif diff -Nur linux-2.6.7.org/include/linux/kernel.h linux-2.6.7/include/linux/kernel.h --- linux-2.6.7.org/include/linux/kernel.h 2004-06-22 10:27:34.000000000 +0900 +++ linux-2.6.7/include/linux/kernel.h 2004-07-09 15:35:35.276901160 +0900 @@ -111,6 +111,11 @@ extern int panic_on_oops; extern int tainted; extern const char *print_tainted(void); +struct pt_regs; +extern void try_crashdump(struct pt_regs *); +extern void (*diskdump_func) (struct pt_regs *regs, void *platform_arg); +extern int diskdump_mode; +#define crashdump_mode() unlikely(diskdump_mode) /* Values used for system_state */ extern enum system_states { @@ -139,6 +144,12 @@ #define pr_info(fmt,arg...) \ printk(KERN_INFO fmt,##arg) +#define pr_err(fmt,arg...) \ + printk(KERN_ERR fmt,##arg) + +#define pr_warn(fmt,arg...) \ + printk(KERN_WARNING fmt,##arg) + /* * Display an IP address in readable format. */ diff -Nur linux-2.6.7.org/include/linux/timer.h linux-2.6.7/include/linux/timer.h --- linux-2.6.7.org/include/linux/timer.h 2004-06-22 10:27:31.000000000 +0900 +++ linux-2.6.7/include/linux/timer.h 2004-07-09 15:35:35.277901008 +0900 @@ -99,4 +99,7 @@ extern void run_local_timers(void); extern void it_real_fn(unsigned long); +extern void dump_clear_timers(void); +extern void dump_run_timers(void); + #endif diff -Nur linux-2.6.7.org/include/linux/workqueue.h linux-2.6.7/include/linux/workqueue.h --- linux-2.6.7.org/include/linux/workqueue.h 2004-06-22 10:27:35.000000000 +0900 +++ linux-2.6.7/include/linux/workqueue.h 2004-07-09 15:35:35.277901008 +0900 @@ -84,4 +84,7 @@ return ret; } +extern void dump_clear_workqueue(void); +extern void dump_run_workqueue(void); + #endif diff -Nur linux-2.6.7.org/kernel/Makefile linux-2.6.7/kernel/Makefile --- linux-2.6.7.org/kernel/Makefile 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/Makefile 2004-07-09 15:35:35.278900856 +0900 @@ -7,7 +7,7 @@ sysctl.o capability.o ptrace.o timer.o user.o \ signal.o sys.o kmod.o workqueue.o pid.o \ rcupdate.o intermodule.o extable.o params.o posix-timers.o \ - kthread.o + kthread.o dump.o obj-$(CONFIG_FUTEX) += futex.o obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o diff -Nur linux-2.6.7.org/kernel/dump.c linux-2.6.7/kernel/dump.c --- linux-2.6.7.org/kernel/dump.c 1970-01-01 09:00:00.000000000 +0900 +++ linux-2.6.7/kernel/dump.c 2004-07-09 15:35:35.279900704 +0900 @@ -0,0 +1,246 @@ +/* + * linux/kernel/dump.c + * + * Copyright (C) 2004 FUJITSU LIMITED + * Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com) + * + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/nmi.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/genhd.h> +#include <linux/diskdump.h> +#include <asm/diskdump.h> + +static DECLARE_MUTEX(dump_ops_mutex); +struct disk_dump_ops* dump_ops = NULL; + +int diskdump_mode = 0; +EXPORT_SYMBOL_GPL(diskdump_mode); + +void (*diskdump_func) (struct pt_regs *regs, void *platform_arg) = NULL; +EXPORT_SYMBOL_GPL(diskdump_func); + +static unsigned long long timestamp_base; +static unsigned long timestamp_hz; + + +/* + * register/unregister diskdump operations + */ +int diskdump_register_ops(struct disk_dump_ops* op) +{ + down(&dump_ops_mutex); + if (dump_ops) { + up(&dump_ops_mutex); + return -EEXIST; + } + dump_ops = op; + up(&dump_ops_mutex); + + return 0; +} + +EXPORT_SYMBOL_GPL(diskdump_register_ops); + +void diskdump_unregister_ops(void) +{ + down(&dump_ops_mutex); + dump_ops = NULL; + up(&dump_ops_mutex); +} + +EXPORT_SYMBOL_GPL(diskdump_unregister_ops); + + +/* + * sysfs interface + */ +static struct gendisk *device_to_gendisk(struct device *dev) +{ + struct dentry *d; + struct qstr qstr; + + /* trace symlink to "block" */ + qstr.name = "block"; + qstr.len = strlen(qstr.name); + qstr.hash = full_name_hash(qstr.name, qstr.len); + d = d_lookup(dev->kobj.dentry, &qstr); + if (!d || !d->d_fsdata) + return NULL; + else + return container_of(d->d_fsdata, struct gendisk, kobj); +} + +ssize_t diskdump_sysfs_store(struct device *dev, const char *buf, size_t count) +{ + struct gendisk *disk; + struct block_device *bdev; + int part, remove = 0; + + if (!dump_ops) + return count; + + /* get partition number */ + sscanf (buf, "%d\n", &part); + if (part < 0) { + part = -part; + remove = 1; + } + + /* get block device */ + if (!(disk = device_to_gendisk(dev)) || + !(bdev = bdget_disk(disk, part))) + return count; + + /* add/remove device */ + down(&dump_ops_mutex); + if (!remove) + dump_ops->add_dump(dev, bdev); + else + dump_ops->remove_dump(bdev); + up(&dump_ops_mutex); + + return count; +} + +EXPORT_SYMBOL_GPL(diskdump_sysfs_store); + +ssize_t diskdump_sysfs_show(struct device *dev, char *buf) +{ + int part, result, tmp; + struct gendisk *disk; + struct block_device *bdev; + int len = 0, maxlen = 1024; + char* p = buf; + char name[BDEVNAME_SIZE]; + + if (!dump_ops) + return 0; + + /* get gendisk */ + disk = device_to_gendisk(dev); + if (!disk || !disk->part) + return 0; + + /* print device */ + down(&dump_ops_mutex); + for (part = 0; part < disk->minors - 1; part++) { + bdev = bdget_disk(disk, part); + result = dump_ops->find_dump(bdev); + bdput(bdev); + if (result) { + tmp = sprintf(p, "%s\n", bdevname(bdev, name)); + len += tmp; + p += tmp; + if(len >= maxlen) + break; + } + } + up(&dump_ops_mutex); + + return len; +} + +EXPORT_SYMBOL_GPL(diskdump_sysfs_show); + +/* + * run timer/tasklet/workqueue during dump + */ +void diskdump_setup_timestamp(void) +{ + unsigned long long t; + + platform_timestamp(timestamp_base); + udelay(1000000/HZ); + platform_timestamp(t); + timestamp_hz = (unsigned long)(t - timestamp_base); + diskdump_update(); +} + +EXPORT_SYMBOL_GPL(diskdump_setup_timestamp); + +void diskdump_update(void) +{ + unsigned long long t; + + touch_nmi_watchdog(); + + /* update jiffies */ + platform_timestamp(t); + while (t > timestamp_base + timestamp_hz) { + timestamp_base += timestamp_hz; + jiffies++; + platform_timestamp(t); + } + + dump_run_timers(); + dump_run_tasklet(); + dump_run_workqueue(); +} + +EXPORT_SYMBOL_GPL(diskdump_update); + + +/* + * register/unregister hook + */ +int diskdump_register_hook(void (*dump_func) (struct pt_regs *, void *)) +{ + if (diskdump_func) + return -EEXIST; + + diskdump_func = dump_func; + + return 0; +} + +EXPORT_SYMBOL_GPL(diskdump_register_hook); + +void diskdump_unregister_hook(void) +{ + diskdump_func = NULL; +} + +EXPORT_SYMBOL_GPL(diskdump_unregister_hook); + + +/* + * Try crashdump. Diskdump is first, netdump is second. + * We clear diskdump_func before call of diskdump_func, so + * If double panic would occur in diskdump, netdump can handle + * it. + */ +void try_crashdump(struct pt_regs *regs) +{ + void (*func)(struct pt_regs *, void *); + + if (diskdump_func) { + func = diskdump_func; + diskdump_func = NULL; + platform_start_diskdump(func, regs); + } + if (panic_on_oops) + panic("Fatal exception"); +} + diff -Nur linux-2.6.7.org/kernel/panic.c linux-2.6.7/kernel/panic.c --- linux-2.6.7.org/kernel/panic.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/panic.c 2004-07-09 15:35:35.279900704 +0900 @@ -60,6 +60,8 @@ vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); printk(KERN_EMERG "Kernel panic: %s\n",buf); + if (diskdump_func) + BUG(); if (in_interrupt()) printk(KERN_EMERG "In interrupt handler - not syncing\n"); else if (!current->pid) diff -Nur linux-2.6.7.org/kernel/softirq.c linux-2.6.7/kernel/softirq.c --- linux-2.6.7.org/kernel/softirq.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/softirq.c 2004-07-09 15:35:35.280900552 +0900 @@ -314,6 +314,38 @@ EXPORT_SYMBOL(tasklet_kill); +struct tasklet_head saved_tasklet; + +void dump_clear_tasklet(void) +{ + saved_tasklet.list = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = NULL; +} + +EXPORT_SYMBOL(dump_clear_tasklet); + +void dump_run_tasklet(void) +{ + struct tasklet_struct *list; + + list = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = NULL; + + while (list) { + struct tasklet_struct *t = list; + list = list->next; + + if (!atomic_read(&t->count) && + (test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))) + t->func(t->data); + + t->next = __get_cpu_var(tasklet_vec).list; + __get_cpu_var(tasklet_vec).list = t; + } +} + +EXPORT_SYMBOL(dump_run_tasklet); + void __init softirq_init(void) { open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); diff -Nur linux-2.6.7.org/kernel/timer.c linux-2.6.7/kernel/timer.c --- linux-2.6.7.org/kernel/timer.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/timer.c 2004-07-09 15:35:35.281900400 +0900 @@ -31,6 +31,7 @@ #include <linux/time.h> #include <linux/jiffies.h> #include <linux/cpu.h> +#include <linux/delay.h> #include <asm/uaccess.h> #include <asm/unistd.h> @@ -423,8 +424,9 @@ static inline void __run_timers(tvec_base_t *base) { struct timer_list *timer; + unsigned long flags; - spin_lock_irq(&base->lock); + spin_lock_irqsave(&base->lock, flags); while (time_after_eq(jiffies, base->timer_jiffies)) { struct list_head work_list = LIST_HEAD_INIT(work_list); struct list_head *head = &work_list; @@ -453,14 +455,14 @@ set_running_timer(base, timer); smp_wmb(); timer->base = NULL; - spin_unlock_irq(&base->lock); + spin_unlock_irqrestore(&base->lock, flags); fn(data); spin_lock_irq(&base->lock); goto repeat; } } set_running_timer(base, NULL); - spin_unlock_irq(&base->lock); + spin_unlock_irqrestore(&base->lock, flags); } #ifdef CONFIG_NO_IDLE_HZ @@ -1105,6 +1107,12 @@ struct timer_list timer; unsigned long expire; + if (crashdump_mode()) { + mdelay(timeout); + set_current_state(TASK_RUNNING); + return timeout; + } + switch (timeout) { case MAX_SCHEDULE_TIMEOUT: @@ -1308,7 +1316,7 @@ return 0; } -static void __devinit init_timers_cpu(int cpu) +static void /* __devinit */ init_timers_cpu(int cpu) { int j; tvec_base_t *base; @@ -1327,6 +1335,27 @@ base->timer_jiffies = jiffies; } +static tvec_base_t saved_tvec_base; + +void dump_clear_timers(void) +{ + tvec_base_t *base = &per_cpu(tvec_bases, smp_processor_id()); + + memcpy(&saved_tvec_base, base, sizeof(saved_tvec_base)); + init_timers_cpu(smp_processor_id()); +} + +EXPORT_SYMBOL(dump_clear_timers); + +void dump_run_timers(void) +{ + tvec_base_t *base = &__get_cpu_var(tvec_bases); + + __run_timers(base); +} + +EXPORT_SYMBOL(dump_run_timers); + #ifdef CONFIG_HOTPLUG_CPU static int migrate_timer_list(tvec_base_t *new_base, struct list_head *head) { diff -Nur linux-2.6.7.org/kernel/workqueue.c linux-2.6.7/kernel/workqueue.c --- linux-2.6.7.org/kernel/workqueue.c 2004-06-22 10:27:25.000000000 +0900 +++ linux-2.6.7/kernel/workqueue.c 2004-07-09 15:35:35.282900248 +0900 @@ -424,6 +424,37 @@ } +struct cpu_workqueue_struct saved_cwq; + +void dump_clear_workqueue(void) +{ + int cpu = smp_processor_id(); + struct cpu_workqueue_struct *cwq = keventd_wq->cpu_wq + cpu; + + memcpy(&saved_cwq, cwq, sizeof(saved_cwq)); + spin_lock_init(&cwq->lock); + INIT_LIST_HEAD(&cwq->worklist); + init_waitqueue_head(&cwq->more_work); + init_waitqueue_head(&cwq->work_done); +} + +void dump_run_workqueue(void) +{ + struct cpu_workqueue_struct *cwq; + + cwq = keventd_wq->cpu_wq + smp_processor_id(); + while (!list_empty(&cwq->worklist)) { + struct work_struct *work = list_entry(cwq->worklist.next, + struct work_struct, entry); + void (*f) (void *) = work->func; + void *data = work->data; + + list_del_init(cwq->worklist.next); + clear_bit(0, &work->pending); + f(data); + } +} + #ifdef CONFIG_HOTPLUG_CPU /* Take the work from this (downed) CPU. */ static void take_over_work(struct workqueue_struct *wq, unsigned int cpu) @@ -507,3 +538,6 @@ EXPORT_SYMBOL(schedule_delayed_work); EXPORT_SYMBOL(flush_scheduled_work); +EXPORT_SYMBOL(dump_clear_workqueue); +EXPORT_SYMBOL(dump_run_workqueue); + diff -Nur linux-2.6.7.org/mm/bootmem.c linux-2.6.7/mm/bootmem.c --- linux-2.6.7.org/mm/bootmem.c 2004-06-22 10:28:05.000000000 +0900 +++ linux-2.6.7/mm/bootmem.c 2004-07-09 15:35:35.282900248 +0900 @@ -16,6 +16,7 @@ #include <linux/init.h> #include <linux/bootmem.h> #include <linux/mmzone.h> +#include <linux/module.h> #include <asm/dma.h> #include <asm/io.h> @@ -26,6 +27,7 @@ unsigned long max_low_pfn; unsigned long min_low_pfn; unsigned long max_pfn; +EXPORT_SYMBOL(max_pfn); /* return the number of _pages_ that will be allocated for the boot bitmap */ unsigned long __init bootmem_bootmap_pages (unsigned long pages) ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2004-07-09 7:21 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2004-06-16 12:39 [PATCH 0/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:45 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:46 ` [PATCH 2/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:48 ` [PATCH 3/4][Diskdump]Update patches Takao Indoh 2004-06-16 12:50 ` [PATCH 4/4][Diskdump]Update patches Takao Indoh 2004-06-16 17:25 ` [PATCH 0/4][Diskdump]Update patches Jeff Garzik -- strict thread matches above, loose matches on Subject: below -- 2004-06-22 13:47 Takao Indoh 2004-06-22 13:59 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh 2004-07-07 8:26 [PATCH 0/4][Diskdump]Update patches Takao Indoh 2004-07-07 8:30 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh 2004-07-09 7:15 [PATCH 0/4][Diskdump]Update patches Takao Indoh 2004-07-09 7:19 ` [PATCH 1/4][Diskdump]Update patches Takao Indoh
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox