RHEL4/kernel/dump.c
<<
>>
Prefs
   1/*
   2 *  linux/kernel/dump.c
   3 *
   4 *  Copyright (C) 2004  FUJITSU LIMITED
   5 *  Written by Nobuhiro Tachino (ntachino@jp.fujitsu.com)
   6 *
   7 */
   8/*
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License as published by
  11 * the Free Software Foundation; either version 2, or (at your option)
  12 * any later version.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 *
  19 * You should have received a copy of the GNU General Public License
  20 * along with this program; if not, write to the Free Software
  21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22 *
  23 */
  24
  25#include <linux/kernel.h>
  26#include <linux/delay.h>
  27#include <linux/nmi.h>
  28#include <linux/timer.h>
  29#include <linux/interrupt.h>
  30#include <linux/workqueue.h>
  31#include <linux/genhd.h>
  32#include <linux/namei.h>
  33#include <linux/mount.h>
  34#include <linux/diskdump.h>
  35#include <asm/diskdump.h>
  36#include <asm/crashdump.h>
  37#include "../fs/sysfs/sysfs.h"
  38
  39static DECLARE_MUTEX(dump_ops_mutex);
  40struct disk_dump_ops* dump_ops = NULL;
  41
  42int diskdump_mode = 0;
  43EXPORT_SYMBOL_GPL(diskdump_mode);
  44
  45void (*diskdump_func) (struct pt_regs *regs) = NULL;
  46EXPORT_SYMBOL_GPL(diskdump_func);
  47
  48static unsigned long long timestamp_base;
  49static unsigned long timestamp_hz;
  50
  51
  52/*
  53 * register/unregister diskdump operations
  54 */
  55int diskdump_register_ops(struct disk_dump_ops* op)
  56{
  57        down(&dump_ops_mutex);
  58        if (dump_ops) {
  59                up(&dump_ops_mutex);
  60                return -EEXIST;
  61        }
  62        dump_ops = op;
  63        up(&dump_ops_mutex);
  64
  65        return 0;
  66}
  67
  68EXPORT_SYMBOL_GPL(diskdump_register_ops);
  69
  70void diskdump_unregister_ops(void)
  71{
  72        down(&dump_ops_mutex);
  73        dump_ops = NULL;
  74        up(&dump_ops_mutex);
  75}
  76
  77EXPORT_SYMBOL_GPL(diskdump_unregister_ops);
  78
  79
  80/*
  81 * sysfs interface
  82 */
  83static struct gendisk *device_to_gendisk(struct device *dev)
  84{
  85        struct nameidata nd;
  86        struct sysfs_dirent *sd;
  87        struct kobject *kobj;
  88        int rc;
  89
  90        /* trace symlink to "block" */
  91        nd.mnt = mntget(sysfs_mount);
  92        nd.dentry = dget(dev->kobj.dentry);
  93        nd.flags = LOOKUP_FOLLOW;
  94        nd.last_type = LAST_ROOT;
  95        nd.depth = 0;
  96        rc = link_path_walk("block", &nd);
  97        if (rc < 0) {
  98                if (rc == -ENOENT)
  99                        return NULL;
 100                goto err;
 101        }
 102        sd = nd.dentry->d_fsdata;
 103        kobj = sd ? kobject_get(sd->s_element) : NULL;
 104        path_release(&nd);
 105        if (!kobj)
 106                goto err;
 107        return container_of(kobj, struct gendisk, kobj);
 108
 109err:
 110        printk(KERN_WARNING "dump: device has no block attribute\n");
 111        return NULL;
 112}
 113
 114ssize_t diskdump_sysfs_store(struct device *dev, const char *buf, size_t count)
 115{
 116        struct gendisk *disk;
 117
 118        /* early cutoff */
 119        if (!dump_ops || !dump_ops->add_dump || !dump_ops->remove_dump)
 120                return count;
 121
 122        /* get disk */
 123        disk = device_to_gendisk(dev);
 124
 125        if (disk) {
 126                count = diskdump_sysfs_store_disk(disk, dev, buf, count);
 127                put_disk(disk);
 128        }
 129
 130        return count;
 131}
 132
 133EXPORT_SYMBOL_GPL(diskdump_sysfs_store);
 134
 135ssize_t diskdump_sysfs_store_disk(struct gendisk *disk, struct device *dev, const char *buf, size_t count)
 136{
 137        struct block_device *bdev;
 138        int part, remove = 0;
 139
 140        if (!dump_ops || !dump_ops->add_dump || !dump_ops->remove_dump)
 141                return count;
 142
 143        /* get partition number */
 144        if (sscanf (buf, "%d\n", &part) != 1)
 145                return -EINVAL;
 146
 147        if (part < 0) {
 148                part = -part;
 149                remove = 1;
 150        }
 151
 152        if (part >= disk->minors)
 153                return -EINVAL;
 154
 155        /* get block device */
 156        if (!(bdev = bdget_disk(disk, part)))
 157                return count;
 158
 159        /* add/remove device */
 160        down(&dump_ops_mutex);
 161        if (!remove)
 162                dump_ops->add_dump(dev, bdev);
 163        else
 164                dump_ops->remove_dump(bdev);
 165        up(&dump_ops_mutex);
 166
 167        return count;
 168}
 169
 170EXPORT_SYMBOL_GPL(diskdump_sysfs_store_disk);
 171
 172ssize_t diskdump_sysfs_show(struct device *dev, char *buf)
 173{
 174        struct gendisk *disk;
 175        ssize_t len;
 176
 177        /* early cutoff */
 178        if (!dump_ops || !dump_ops->find_dump)
 179                return 0;
 180
 181        /* get gendisk */
 182        disk = device_to_gendisk(dev);
 183        if (!disk)
 184                return 0;
 185
 186        len = diskdump_sysfs_show_disk(disk, buf);
 187
 188        put_disk(disk);
 189
 190        return len;
 191}
 192
 193EXPORT_SYMBOL_GPL(diskdump_sysfs_show);
 194
 195ssize_t diskdump_sysfs_show_disk(struct gendisk *disk, char *buf)
 196{
 197        struct block_device *bdev;
 198        int part, tmp, len = 0, maxlen = 1024;
 199        char* p = buf; 
 200        char name[BDEVNAME_SIZE];
 201
 202        if (!dump_ops || !dump_ops->find_dump)
 203                return 0;
 204
 205        if (!disk->part)
 206                return 0;
 207
 208        /* print device */
 209        down(&dump_ops_mutex);
 210        for (part = 0; part < disk->minors; part++) {
 211                bdev = bdget_disk(disk, part);
 212                if (dump_ops->find_dump(bdev)) {
 213                        tmp = sprintf(p, "%s\n", bdevname(bdev, name));
 214                        len += tmp;
 215                        p += tmp;
 216                }
 217                bdput(bdev);
 218                if(len >= maxlen)
 219                        break;
 220        }
 221        up(&dump_ops_mutex);
 222
 223        return len;
 224}
 225
 226EXPORT_SYMBOL_GPL(diskdump_sysfs_show_disk);
 227
 228/*
 229 * run timer/tasklet/workqueue during dump
 230 */
 231void diskdump_setup_timestamp(void)
 232{
 233        unsigned long long t;
 234
 235        platform_timestamp(timestamp_base);
 236        udelay(1000000/HZ);
 237        platform_timestamp(t);
 238        timestamp_hz = (unsigned long)(t - timestamp_base);
 239        diskdump_update();
 240}
 241
 242EXPORT_SYMBOL_GPL(diskdump_setup_timestamp);
 243
 244void diskdump_update(void)
 245{
 246        unsigned long long t;
 247
 248        touch_nmi_watchdog();
 249
 250        /* update jiffies */
 251        platform_timestamp(t);
 252        while (t > timestamp_base + timestamp_hz) {
 253                timestamp_base += timestamp_hz;
 254                jiffies++;
 255                platform_timestamp(t);
 256        }
 257
 258        dump_run_timers();
 259        dump_run_tasklet();
 260        dump_run_workqueue();
 261}
 262
 263EXPORT_SYMBOL_GPL(diskdump_update);
 264
 265
 266/*
 267 * register/unregister hook
 268 */
 269int diskdump_register_hook(void (*dump_func) (struct pt_regs *))
 270{
 271        if (diskdump_func)
 272                return -EEXIST;
 273
 274        diskdump_func = dump_func;
 275
 276        return 0;
 277}
 278
 279EXPORT_SYMBOL_GPL(diskdump_register_hook);
 280
 281void diskdump_unregister_hook(void)
 282{
 283        diskdump_func = NULL;
 284}
 285
 286EXPORT_SYMBOL_GPL(diskdump_unregister_hook);
 287
 288void (*netdump_func) (struct pt_regs *regs) = NULL;
 289int netdump_mode = 0;
 290EXPORT_SYMBOL_GPL(netdump_mode);
 291
 292/*
 293 * Try crashdump. Diskdump is first, netdump is second.
 294 * We clear diskdump_func before call of diskdump_func, so
 295 * If double panic would occur in diskdump, netdump can handle
 296 * it.
 297 */
 298void try_crashdump(struct pt_regs *regs)
 299{
 300        void (*func)(struct pt_regs *);
 301
 302        if (diskdump_func) {
 303                system_state = SYSTEM_DUMPING;
 304                func = diskdump_func;
 305                diskdump_func = NULL;
 306                func(regs);
 307        }
 308        if (netdump_func)
 309                netdump_func(regs);
 310}
 311
 312extern unsigned long max_pfn;
 313
 314int diskdump_mark_free_pages(void)
 315{
 316        struct zone *zone;
 317        unsigned long start_pfn, err_pfn, i, pfn;
 318        int order, free_page_cnt = 0;
 319        struct list_head *curr, *previous, *dlhead;
 320
 321        /*
 322         * This is not necessary if PG_nosave_free is cleared
 323         * while allocating new pages.
 324         */
 325        for (pfn = next_ram_page(ULONG_MAX); pfn < max_pfn; pfn = next_ram_page(pfn))
 326                if (pfn_valid(pfn))
 327                        ClearPageNosaveFree(pfn_to_page(pfn));
 328
 329        for_each_zone(zone) {
 330                if (!zone->spanned_pages)
 331                        continue;
 332
 333                for (order = MAX_ORDER - 1; order >= 0; --order) {
 334                        /*
 335                         * Emulate a list_for_each.
 336                         */
 337                        dlhead = &zone->free_area[order].free_list;
 338
 339                        for (previous = dlhead, curr = dlhead->next;
 340                             curr != dlhead;
 341                             previous=curr, curr = curr->next) {
 342
 343                                start_pfn = page_to_pfn(
 344                                        list_entry(curr, struct page, lru));
 345
 346                                if (!pfn_valid(start_pfn) ||
 347                                    (previous != curr->prev)) {
 348                                        err_pfn = start_pfn;
 349                                        goto mark_err;
 350                                }
 351
 352                                for (i = 0; i < (1<<order); i++) {
 353                                        pfn = start_pfn + i;
 354                                        if (!pfn_valid(pfn) ||
 355                                            TestSetPageNosaveFree(
 356                                                  pfn_to_page(pfn))) {
 357                                                err_pfn = pfn;
 358                                                goto mark_err;
 359                                        }
 360                                }
 361                                free_page_cnt += i;
 362                        }
 363                }
 364        }
 365        return free_page_cnt;
 366
 367mark_err:
 368        printk(KERN_WARNING "dump: Bad page. PFN %lu.", err_pfn);
 369        printk(KERN_WARNING "DUMP_LEVEL will be ignored. Free pages will be dumped.");
 370        return -1;
 371}
 372
 373EXPORT_SYMBOL_GPL(diskdump_mark_free_pages);
 374