diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 33 | ||||
-rw-r--r-- | drivers/misc/Makefile | 2 | ||||
-rw-r--r-- | drivers/misc/atmel-ssc.c | 2 | ||||
-rw-r--r-- | drivers/misc/atmel_pwm.c | 1 | ||||
-rw-r--r-- | drivers/misc/atmel_tclib.c | 161 | ||||
-rw-r--r-- | drivers/misc/enclosure.c | 118 | ||||
-rw-r--r-- | drivers/misc/hdpuftrs/hdpu_cpustate.c | 2 | ||||
-rw-r--r-- | drivers/misc/hdpuftrs/hdpu_nexus.c | 2 | ||||
-rw-r--r-- | drivers/misc/kgdbts.c | 1090 |
9 files changed, 1357 insertions, 54 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 962817e49fba..bb94ce78a6d0 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -22,6 +22,39 @@ config ATMEL_PWM purposes including software controlled power-efficent backlights on LCD displays, motor control, and waveform generation. +config ATMEL_TCLIB + bool "Atmel AT32/AT91 Timer/Counter Library" + depends on (AVR32 || ARCH_AT91) + help + Select this if you want a library to allocate the Timer/Counter + blocks found on many Atmel processors. This facilitates using + these blocks by different drivers despite processor differences. + +config ATMEL_TCB_CLKSRC + bool "TC Block Clocksource" + depends on ATMEL_TCLIB && GENERIC_TIME + default y + help + Select this to get a high precision clocksource based on a + TC block with a 5+ MHz base clock rate. Two timer channels + are combined to make a single 32-bit timer. + + When GENERIC_CLOCKEVENTS is defined, the third timer channel + may be used as a clock event device supporting oneshot mode + (delays of up to two seconds) based on the 32 KiHz clock. + +config ATMEL_TCB_CLKSRC_BLOCK + int + depends on ATMEL_TCB_CLKSRC + prompt "TC Block" if ARCH_AT91RM9200 || ARCH_AT91SAM9260 || CPU_AT32AP700X + default 0 + range 0 1 + help + Some chips provide more than one TC block, so you have the + choice of which one to use for the clock framework. The other + TC can be used for other purposes, such as PWM generation and + interval timing. + config IBM_ASM tristate "Device driver for IBM RSA service processor" depends on X86 && PCI && INPUT && EXPERIMENTAL diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 3b12f5da8562..4581b2533111 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o +obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o @@ -22,3 +23,4 @@ obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o +obj-$(CONFIG_KGDB_TESTS) += kgdbts.o diff --git a/drivers/misc/atmel-ssc.c b/drivers/misc/atmel-ssc.c index 058ccac700d0..e171650766ce 100644 --- a/drivers/misc/atmel-ssc.c +++ b/drivers/misc/atmel-ssc.c @@ -154,6 +154,7 @@ static struct platform_driver ssc_driver = { .remove = __devexit_p(ssc_remove), .driver = { .name = "ssc", + .owner = THIS_MODULE, }, }; @@ -172,3 +173,4 @@ module_exit(ssc_exit); MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); MODULE_DESCRIPTION("SSC driver for Atmel AVR32 and AT91"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ssc"); diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c index f8d3b9a76cbd..0d5ce03cdff2 100644 --- a/drivers/misc/atmel_pwm.c +++ b/drivers/misc/atmel_pwm.c @@ -407,3 +407,4 @@ module_exit(pwm_exit); MODULE_DESCRIPTION("Driver for AT32/AT91 PWM module"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel_pwm"); diff --git a/drivers/misc/atmel_tclib.c b/drivers/misc/atmel_tclib.c new file mode 100644 index 000000000000..05dc8a31f280 --- /dev/null +++ b/drivers/misc/atmel_tclib.c @@ -0,0 +1,161 @@ +#include <linux/atmel_tc.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> + +/* Number of bytes to reserve for the iomem resource */ +#define ATMEL_TC_IOMEM_SIZE 256 + + +/* + * This is a thin library to solve the problem of how to portably allocate + * one of the TC blocks. For simplicity, it doesn't currently expect to + * share individual timers between different drivers. + */ + +#if defined(CONFIG_AVR32) +/* AVR32 has these divide PBB */ +const u8 atmel_tc_divisors[5] = { 0, 4, 8, 16, 32, }; +EXPORT_SYMBOL(atmel_tc_divisors); + +#elif defined(CONFIG_ARCH_AT91) +/* AT91 has these divide MCK */ +const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, }; +EXPORT_SYMBOL(atmel_tc_divisors); + +#endif + +static DEFINE_SPINLOCK(tc_list_lock); +static LIST_HEAD(tc_list); + +/** + * atmel_tc_alloc - allocate a specified TC block + * @block: which block to allocate + * @name: name to be associated with the iomem resource + * + * Caller allocates a block. If it is available, a pointer to a + * pre-initialized struct atmel_tc is returned. The caller can access + * the registers directly through the "regs" field. + */ +struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name) +{ + struct atmel_tc *tc; + struct platform_device *pdev = NULL; + struct resource *r; + + spin_lock(&tc_list_lock); + list_for_each_entry(tc, &tc_list, node) { + if (tc->pdev->id == block) { + pdev = tc->pdev; + break; + } + } + + if (!pdev || tc->iomem) + goto fail; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + r = request_mem_region(r->start, ATMEL_TC_IOMEM_SIZE, name); + if (!r) + goto fail; + + tc->regs = ioremap(r->start, ATMEL_TC_IOMEM_SIZE); + if (!tc->regs) + goto fail_ioremap; + + tc->iomem = r; + +out: + spin_unlock(&tc_list_lock); + return tc; + +fail_ioremap: + release_resource(r); +fail: + tc = NULL; + goto out; +} +EXPORT_SYMBOL_GPL(atmel_tc_alloc); + +/** + * atmel_tc_free - release a specified TC block + * @tc: Timer/counter block that was returned by atmel_tc_alloc() + * + * This reverses the effect of atmel_tc_alloc(), unmapping the I/O + * registers, invalidating the resource returned by that routine and + * making the TC available to other drivers. + */ +void atmel_tc_free(struct atmel_tc *tc) +{ + spin_lock(&tc_list_lock); + if (tc->regs) { + iounmap(tc->regs); + release_resource(tc->iomem); + tc->regs = NULL; + tc->iomem = NULL; + } + spin_unlock(&tc_list_lock); +} +EXPORT_SYMBOL_GPL(atmel_tc_free); + +static int __init tc_probe(struct platform_device *pdev) +{ + struct atmel_tc *tc; + struct clk *clk; + int irq; + + if (!platform_get_resource(pdev, IORESOURCE_MEM, 0)) + return -EINVAL; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -EINVAL; + + tc = kzalloc(sizeof(struct atmel_tc), GFP_KERNEL); + if (!tc) + return -ENOMEM; + + tc->pdev = pdev; + + clk = clk_get(&pdev->dev, "t0_clk"); + if (IS_ERR(clk)) { + kfree(tc); + return -EINVAL; + } + + tc->clk[0] = clk; + tc->clk[1] = clk_get(&pdev->dev, "t1_clk"); + if (IS_ERR(tc->clk[1])) + tc->clk[1] = clk; + tc->clk[2] = clk_get(&pdev->dev, "t2_clk"); + if (IS_ERR(tc->clk[2])) + tc->clk[2] = clk; + + tc->irq[0] = irq; + tc->irq[1] = platform_get_irq(pdev, 1); + if (tc->irq[1] < 0) + tc->irq[1] = irq; + tc->irq[2] = platform_get_irq(pdev, 2); + if (tc->irq[2] < 0) + tc->irq[2] = irq; + + spin_lock(&tc_list_lock); + list_add_tail(&tc->node, &tc_list); + spin_unlock(&tc_list_lock); + + return 0; +} + +static struct platform_driver tc_driver = { + .driver.name = "atmel_tcb", +}; + +static int __init tc_init(void) +{ + return platform_driver_probe(&tc_driver, tc_probe); +} +arch_initcall(tc_init); diff --git a/drivers/misc/enclosure.c b/drivers/misc/enclosure.c index 6fcb0e96adf4..fafb57fed761 100644 --- a/drivers/misc/enclosure.c +++ b/drivers/misc/enclosure.c @@ -40,16 +40,16 @@ static struct class enclosure_component_class; * Looks through the list of registered enclosures to see * if it can find a match for a device. Returns NULL if no * enclosure is found. Obtains a reference to the enclosure class - * device which must be released with class_device_put(). + * device which must be released with device_put(). */ struct enclosure_device *enclosure_find(struct device *dev) { - struct enclosure_device *edev = NULL; + struct enclosure_device *edev; mutex_lock(&container_list_lock); list_for_each_entry(edev, &container_list, node) { - if (edev->cdev.dev == dev) { - class_device_get(&edev->cdev); + if (edev->edev.parent == dev) { + get_device(&edev->edev); mutex_unlock(&container_list_lock); return edev; } @@ -117,11 +117,11 @@ enclosure_register(struct device *dev, const char *name, int components, edev->components = components; - edev->cdev.class = &enclosure_class; - edev->cdev.dev = get_device(dev); + edev->edev.class = &enclosure_class; + edev->edev.parent = get_device(dev); edev->cb = cb; - snprintf(edev->cdev.class_id, BUS_ID_SIZE, "%s", name); - err = class_device_register(&edev->cdev); + snprintf(edev->edev.bus_id, BUS_ID_SIZE, "%s", name); + err = device_register(&edev->edev); if (err) goto err; @@ -135,7 +135,7 @@ enclosure_register(struct device *dev, const char *name, int components, return edev; err: - put_device(edev->cdev.dev); + put_device(edev->edev.parent); kfree(edev); return ERR_PTR(err); } @@ -158,27 +158,28 @@ void enclosure_unregister(struct enclosure_device *edev) for (i = 0; i < edev->components; i++) if (edev->component[i].number != -1) - class_device_unregister(&edev->component[i].cdev); + device_unregister(&edev->component[i].cdev); /* prevent any callbacks into service user */ edev->cb = &enclosure_null_callbacks; - class_device_unregister(&edev->cdev); + device_unregister(&edev->edev); } EXPORT_SYMBOL_GPL(enclosure_unregister); -static void enclosure_release(struct class_device *cdev) +static void enclosure_release(struct device *cdev) { struct enclosure_device *edev = to_enclosure_device(cdev); - put_device(cdev->dev); + put_device(cdev->parent); kfree(edev); } -static void enclosure_component_release(struct class_device *cdev) +static void enclosure_component_release(struct device *dev) { - if (cdev->dev) - put_device(cdev->dev); - class_device_put(cdev->parent); + struct enclosure_component *cdev = to_enclosure_component(dev); + + put_device(cdev->dev); + put_device(dev->parent); } /** @@ -201,7 +202,7 @@ enclosure_component_register(struct enclosure_device *edev, const char *name) { struct enclosure_component *ecomp; - struct class_device *cdev; + struct device *cdev; int err; if (number >= edev->components) @@ -215,14 +216,14 @@ enclosure_component_register(struct enclosure_device *edev, ecomp->type = type; ecomp->number = number; cdev = &ecomp->cdev; - cdev->parent = class_device_get(&edev->cdev); + cdev->parent = get_device(&edev->edev); cdev->class = &enclosure_component_class; if (name) - snprintf(cdev->class_id, BUS_ID_SIZE, "%s", name); + snprintf(cdev->bus_id, BUS_ID_SIZE, "%s", name); else - snprintf(cdev->class_id, BUS_ID_SIZE, "%u", number); + snprintf(cdev->bus_id, BUS_ID_SIZE, "%u", number); - err = class_device_register(cdev); + err = device_register(cdev); if (err) ERR_PTR(err); @@ -247,18 +248,17 @@ EXPORT_SYMBOL_GPL(enclosure_component_register); int enclosure_add_device(struct enclosure_device *edev, int component, struct device *dev) { - struct class_device *cdev; + struct enclosure_component *cdev; if (!edev || component >= edev->components) return -EINVAL; - cdev = &edev->component[component].cdev; + cdev = &edev->component[component]; - class_device_del(cdev); - if (cdev->dev) - put_device(cdev->dev); + device_del(&cdev->cdev); + put_device(cdev->dev); cdev->dev = get_device(dev); - return class_device_add(cdev); + return device_add(&cdev->cdev); } EXPORT_SYMBOL_GPL(enclosure_add_device); @@ -272,18 +272,17 @@ EXPORT_SYMBOL_GPL(enclosure_add_device); */ int enclosure_remove_device(struct enclosure_device *edev, int component) { - struct class_device *cdev; + struct enclosure_component *cdev; if (!edev || component >= edev->components) return -EINVAL; - cdev = &edev->component[component].cdev; + cdev = &edev->component[component]; - class_device_del(cdev); - if (cdev->dev) - put_device(cdev->dev); + device_del(&cdev->cdev); + put_device(cdev->dev); cdev->dev = NULL; - return class_device_add(cdev); + return device_add(&cdev->cdev); } EXPORT_SYMBOL_GPL(enclosure_remove_device); @@ -291,14 +290,16 @@ EXPORT_SYMBOL_GPL(enclosure_remove_device); * sysfs pieces below */ -static ssize_t enclosure_show_components(struct class_device *cdev, char *buf) +static ssize_t enclosure_show_components(struct device *cdev, + struct device_attribute *attr, + char *buf) { struct enclosure_device *edev = to_enclosure_device(cdev); return snprintf(buf, 40, "%d\n", edev->components); } -static struct class_device_attribute enclosure_attrs[] = { +static struct device_attribute enclosure_attrs[] = { __ATTR(components, S_IRUGO, enclosure_show_components, NULL), __ATTR_NULL }; @@ -306,8 +307,8 @@ static struct class_device_attribute enclosure_attrs[] = { static struct class enclosure_class = { .name = "enclosure", .owner = THIS_MODULE, - .release = enclosure_release, - .class_dev_attrs = enclosure_attrs, + .dev_release = enclosure_release, + .dev_attrs = enclosure_attrs, }; static const char *const enclosure_status [] = { @@ -326,7 +327,8 @@ static const char *const enclosure_type [] = { [ENCLOSURE_COMPONENT_ARRAY_DEVICE] = "array device", }; -static ssize_t get_component_fault(struct class_device *cdev, char *buf) +static ssize_t get_component_fault(struct device *cdev, + struct device_attribute *attr, char *buf) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -336,8 +338,9 @@ static ssize_t get_component_fault(struct class_device *cdev, char *buf) return snprintf(buf, 40, "%d\n", ecomp->fault); } -static ssize_t set_component_fault(struct class_device *cdev, const char *buf, - size_t count) +static ssize_t set_component_fault(struct device *cdev, + struct device_attribute *attr, + const char *buf, size_t count) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -348,7 +351,8 @@ static ssize_t set_component_fault(struct class_device *cdev, const char *buf, return count; } -static ssize_t get_component_status(struct class_device *cdev, char *buf) +static ssize_t get_component_status(struct device *cdev, + struct device_attribute *attr,char *buf) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -358,8 +362,9 @@ static ssize_t get_component_status(struct class_device *cdev, char *buf) return snprintf(buf, 40, "%s\n", enclosure_status[ecomp->status]); } -static ssize_t set_component_status(struct class_device *cdev, const char *buf, - size_t count) +static ssize_t set_component_status(struct device *cdev, + struct device_attribute *attr, + const char *buf, size_t count) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -380,7 +385,8 @@ static ssize_t set_component_status(struct class_device *cdev, const char *buf, return -EINVAL; } -static ssize_t get_component_active(struct class_device *cdev, char *buf) +static ssize_t get_component_active(struct device *cdev, + struct device_attribute *attr, char *buf) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -390,8 +396,9 @@ static ssize_t get_component_active(struct class_device *cdev, char *buf) return snprintf(buf, 40, "%d\n", ecomp->active); } -static ssize_t set_component_active(struct class_device *cdev, const char *buf, - size_t count) +static ssize_t set_component_active(struct device *cdev, + struct device_attribute *attr, + const char *buf, size_t count) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -402,7 +409,8 @@ static ssize_t set_component_active(struct class_device *cdev, const char *buf, return count; } -static ssize_t get_component_locate(struct class_device *cdev, char *buf) +static ssize_t get_component_locate(struct device *cdev, + struct device_attribute *attr, char *buf) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -412,8 +420,9 @@ static ssize_t get_component_locate(struct class_device *cdev, char *buf) return snprintf(buf, 40, "%d\n", ecomp->locate); } -static ssize_t set_component_locate(struct class_device *cdev, const char *buf, - size_t count) +static ssize_t set_component_locate(struct device *cdev, + struct device_attribute *attr, + const char *buf, size_t count) { struct enclosure_device *edev = to_enclosure_device(cdev->parent); struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -424,7 +433,8 @@ static ssize_t set_component_locate(struct class_device *cdev, const char *buf, return count; } -static ssize_t get_component_type(struct class_device *cdev, char *buf) +static ssize_t get_component_type(struct device *cdev, + struct device_attribute *attr, char *buf) { struct enclosure_component *ecomp = to_enclosure_component(cdev); @@ -432,7 +442,7 @@ static ssize_t get_component_type(struct class_device *cdev, char *buf) } -static struct class_device_attribute enclosure_component_attrs[] = { +static struct device_attribute enclosure_component_attrs[] = { __ATTR(fault, S_IRUGO | S_IWUSR, get_component_fault, set_component_fault), __ATTR(status, S_IRUGO | S_IWUSR, get_component_status, @@ -448,8 +458,8 @@ static struct class_device_attribute enclosure_component_attrs[] = { static struct class enclosure_component_class = { .name = "enclosure_component", .owner = THIS_MODULE, - .class_dev_attrs = enclosure_component_attrs, - .release = enclosure_component_release, + .dev_attrs = enclosure_component_attrs, + .dev_release = enclosure_component_release, }; static int __init enclosure_init(void) diff --git a/drivers/misc/hdpuftrs/hdpu_cpustate.c b/drivers/misc/hdpuftrs/hdpu_cpustate.c index aa8ce7abe922..302e92418bbe 100644 --- a/drivers/misc/hdpuftrs/hdpu_cpustate.c +++ b/drivers/misc/hdpuftrs/hdpu_cpustate.c @@ -164,6 +164,7 @@ static struct platform_driver hdpu_cpustate_driver = { .remove = hdpu_cpustate_remove, .driver = { .name = HDPU_CPUSTATE_NAME, + .owner = THIS_MODULE, }, }; @@ -248,3 +249,4 @@ module_exit(cpustate_exit); MODULE_AUTHOR("Brian Waite"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" HDPU_CPUSTATE_NAME); diff --git a/drivers/misc/hdpuftrs/hdpu_nexus.c b/drivers/misc/hdpuftrs/hdpu_nexus.c index 2887b2147980..2fa36f7a6eb3 100644 --- a/drivers/misc/hdpuftrs/hdpu_nexus.c +++ b/drivers/misc/hdpuftrs/hdpu_nexus.c @@ -55,6 +55,7 @@ static struct platform_driver hdpu_nexus_driver = { .remove = hdpu_nexus_remove, .driver = { .name = HDPU_NEXUS_NAME, + .owner = THIS_MODULE, }, }; @@ -151,3 +152,4 @@ module_exit(nexus_exit); MODULE_AUTHOR("Brian Waite"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" HDPU_NEXUS_NAME); diff --git a/drivers/misc/kgdbts.c b/drivers/misc/kgdbts.c new file mode 100644 index 000000000000..6d6286c4eeac --- /dev/null +++ b/drivers/misc/kgdbts.c @@ -0,0 +1,1090 @@ +/* + * kgdbts is a test suite for kgdb for the sole purpose of validating + * that key pieces of the kgdb internals are working properly such as + * HW/SW breakpoints, single stepping, and NMI. + * + * Created by: Jason Wessel <jason.wessel@windriver.com> + * + * Copyright (c) 2008 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* Information about the kgdb test suite. + * ------------------------------------- + * + * The kgdb test suite is designed as a KGDB I/O module which + * simulates the communications that a debugger would have with kgdb. + * The tests are broken up in to a line by line and referenced here as + * a "get" which is kgdb requesting input and "put" which is kgdb + * sending a response. + * + * The kgdb suite can be invoked from the kernel command line + * arguments system or executed dynamically at run time. The test + * suite uses the variable "kgdbts" to obtain the information about + * which tests to run and to configure the verbosity level. The + * following are the various characters you can use with the kgdbts= + * line: + * + * When using the "kgdbts=" you only choose one of the following core + * test types: + * A = Run all the core tests silently + * V1 = Run all the core tests with minimal output + * V2 = Run all the core tests in debug mode + * + * You can also specify optional tests: + * N## = Go to sleep with interrupts of for ## seconds + * to test the HW NMI watchdog + * F## = Break at do_fork for ## iterations + * S## = Break at sys_open for ## iterations + * + * NOTE: that the do_fork and sys_open tests are mutually exclusive. + * + * To invoke the kgdb test suite from boot you use a kernel start + * argument as follows: + * kgdbts=V1 kgdbwait + * Or if you wanted to perform the NMI test for 6 seconds and do_fork + * test for 100 forks, you could use: + * kgdbts=V1N6F100 kgdbwait + * + * The test suite can also be invoked at run time with: + * echo kgdbts=V1N6F100 > /sys/module/kgdbts/parameters/kgdbts + * Or as another example: + * echo kgdbts=V2 > /sys/module/kgdbts/parameters/kgdbts + * + * When developing a new kgdb arch specific implementation or + * using these tests for the purpose of regression testing, + * several invocations are required. + * + * 1) Boot with the test suite enabled by using the kernel arguments + * "kgdbts=V1F100 kgdbwait" + * ## If kgdb arch specific implementation has NMI use + * "kgdbts=V1N6F100 + * + * 2) After the system boot run the basic test. + * echo kgdbts=V1 > /sys/module/kgdbts/parameters/kgdbts + * + * 3) Run the concurrency tests. It is best to use n+1 + * while loops where n is the number of cpus you have + * in your system. The example below uses only two + * loops. + * + * ## This tests break points on sys_open + * while [ 1 ] ; do find / > /dev/null 2>&1 ; done & + * while [ 1 ] ; do find / > /dev/null 2>&1 ; done & + * echo kgdbts=V1S10000 > /sys/module/kgdbts/parameters/kgdbts + * fg # and hit control-c + * fg # and hit control-c + * ## This tests break points on do_fork + * while [ 1 ] ; do date > /dev/null ; done & + * while [ 1 ] ; do date > /dev/null ; done & + * echo kgdbts=V1F1000 > /sys/module/kgdbts/parameters/kgdbts + * fg # and hit control-c + * + */ + +#include <linux/kernel.h> +#include <linux/kgdb.h> +#include <linux/ctype.h> +#include <linux/uaccess.h> +#include <linux/syscalls.h> +#include <linux/nmi.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/delay.h> + +#define v1printk(a...) do { \ + if (verbose) \ + printk(KERN_INFO a); \ + } while (0) +#define v2printk(a...) do { \ + if (verbose > 1) \ + printk(KERN_INFO a); \ + touch_nmi_watchdog(); \ + } while (0) +#define eprintk(a...) do { \ + printk(KERN_ERR a); \ + WARN_ON(1); \ + } while (0) +#define MAX_CONFIG_LEN 40 + +static const char hexchars[] = "0123456789abcdef"; +static struct kgdb_io kgdbts_io_ops; +static char get_buf[BUFMAX]; +static int get_buf_cnt; +static char put_buf[BUFMAX]; +static int put_buf_cnt; +static char scratch_buf[BUFMAX]; +static int verbose; +static int repeat_test; +static int test_complete; +static int send_ack; +static int final_ack; +static int hw_break_val; +static int hw_break_val2; +#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) +static int arch_needs_sstep_emulation = 1; +#else +static int arch_needs_sstep_emulation; +#endif +static unsigned long sstep_addr; +static int sstep_state; + +/* Storage for the registers, in GDB format. */ +static unsigned long kgdbts_gdb_regs[(NUMREGBYTES + + sizeof(unsigned long) - 1) / + sizeof(unsigned long)]; +static struct pt_regs kgdbts_regs; + +/* -1 = init not run yet, 0 = unconfigured, 1 = configured. */ +static int configured = -1; + +#ifdef CONFIG_KGDB_TESTS_BOOT_STRING +static char config[MAX_CONFIG_LEN] = CONFIG_KGDB_TESTS_BOOT_STRING; +#else +static char config[MAX_CONFIG_LEN]; +#endif +static struct kparam_string kps = { + .string = config, + .maxlen = MAX_CONFIG_LEN, +}; + +static void fill_get_buf(char *buf); + +struct test_struct { + char *get; + char *put; + void (*get_handler)(char *); + int (*put_handler)(char *, char *); +}; + +struct test_state { + char *name; + struct test_struct *tst; + int idx; + int (*run_test) (int, int); + int (*validate_put) (char *); +}; + +static struct test_state ts; + +static int kgdbts_unreg_thread(void *ptr) +{ + /* Wait until the tests are complete and then ungresiter the I/O + * driver. + */ + while (!final_ack) + msleep_interruptible(1500); + + if (configured) + kgdb_unregister_io_module(&kgdbts_io_ops); + configured = 0; + + return 0; +} + +/* This is noinline such that it can be used for a single location to + * place a breakpoint + */ +static noinline void kgdbts_break_test(void) +{ + v2printk("kgdbts: breakpoint complete\n"); +} + +/* Lookup symbol info in the kernel */ +static unsigned long lookup_addr(char *arg) +{ + unsigned long addr = 0; + + if (!strcmp(arg, "kgdbts_break_test")) + addr = (unsigned long)kgdbts_break_test; + else if (!strcmp(arg, "sys_open")) + addr = (unsigned long)sys_open; + else if (!strcmp(arg, "do_fork")) + addr = (unsigned long)do_fork; + else if (!strcmp(arg, "hw_break_val")) + addr = (unsigned long)&hw_break_val; + return addr; +} + +static void break_helper(char *bp_type, char *arg, unsigned long vaddr) +{ + unsigned long addr; + + if (arg) + addr = lookup_addr(arg); + else + addr = vaddr; + + sprintf(scratch_buf, "%s,%lx,%i", bp_type, addr, + BREAK_INSTR_SIZE); + fill_get_buf(scratch_buf); +} + +static void sw_break(char *arg) +{ + break_helper("Z0", arg, 0); +} + +static void sw_rem_break(char *arg) +{ + break_helper("z0", arg, 0); +} + +static void hw_break(char *arg) +{ + break_helper("Z1", arg, 0); +} + +static void hw_rem_break(char *arg) +{ + break_helper("z1", arg, 0); +} + +static void hw_write_break(char *arg) +{ + break_helper("Z2", arg, 0); +} + +static void hw_rem_write_break(char *arg) +{ + break_helper("z2", arg, 0); +} + +static void hw_access_break(char *arg) +{ + break_helper("Z4", arg, 0); +} + +static void hw_rem_access_break(char *arg) +{ + break_helper("z4", arg, 0); +} + +static void hw_break_val_access(void) +{ + hw_break_val2 = hw_break_val; +} + +static void hw_break_val_write(void) +{ + hw_break_val++; +} + +static int check_and_rewind_pc(char *put_str, char *arg) +{ + unsigned long addr = lookup_addr(arg); + int offset = 0; + + kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs, + NUMREGBYTES); + gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs); + v2printk("Stopped at IP: %lx\n", instruction_pointer(&kgdbts_regs)); +#ifdef CONFIG_X86 + /* On x86 a breakpoint stop requires it to be decremented */ + if (addr + 1 == kgdbts_regs.ip) + offset = -1; +#endif + if (strcmp(arg, "silent") && + instruction_pointer(&kgdbts_regs) + offset != addr) { + eprintk("kgdbts: BP mismatch %lx expected %lx\n", + instruction_pointer(&kgdbts_regs) + offset, addr); + return 1; + } +#ifdef CONFIG_X86 + /* On x86 adjust the instruction pointer if needed */ + kgdbts_regs.ip += offset; +#endif + return 0; +} + +static int check_single_step(char *put_str, char *arg) +{ + unsigned long addr = lookup_addr(arg); + /* + * From an arch indepent point of view the instruction pointer + * should be on a different instruction + */ + kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs, + NUMREGBYTES); + gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs); + v2printk("Singlestep stopped at IP: %lx\n", + instruction_pointer(&kgdbts_regs)); + if (instruction_pointer(&kgdbts_regs) == addr) { + eprintk("kgdbts: SingleStep failed at %lx\n", + instruction_pointer(&kgdbts_regs)); + return 1; + } + + return 0; +} + +static void write_regs(char *arg) +{ + memset(scratch_buf, 0, sizeof(scratch_buf)); + scratch_buf[0] = 'G'; + pt_regs_to_gdb_regs(kgdbts_gdb_regs, &kgdbts_regs); + kgdb_mem2hex((char *)kgdbts_gdb_regs, &scratch_buf[1], NUMREGBYTES); + fill_get_buf(scratch_buf); +} + +static void skip_back_repeat_test(char *arg) +{ + int go_back = simple_strtol(arg, NULL, 10); + + repeat_test--; + if (repeat_test <= 0) + ts.idx++; + else + ts.idx -= go_back; + fill_get_buf(ts.tst[ts.idx].get); +} + +static int got_break(char *put_str, char *arg) +{ + test_complete = 1; + if (!strncmp(put_str+1, arg, 2)) { + if (!strncmp(arg, "T0", 2)) + test_complete = 2; + return 0; + } + return 1; +} + +static void emul_sstep_get(char *arg) +{ + if (!arch_needs_sstep_emulation) { + fill_get_buf(arg); + return; + } + switch (sstep_state) { + case 0: + v2printk("Emulate single step\n"); + /* Start by looking at the current PC */ + fill_get_buf("g"); + break; + case 1: + /* set breakpoint */ + break_helper("Z0", 0, sstep_addr); + break; + case 2: + /* Continue */ + fill_get_buf("c"); + break; + case 3: + /* Clear breakpoint */ + break_helper("z0", 0, sstep_addr); + break; + default: + eprintk("kgdbts: ERROR failed sstep get emulation\n"); + } + sstep_state++; +} + +static int emul_sstep_put(char *put_str, char *arg) +{ + if (!arch_needs_sstep_emulation) { + if (!strncmp(put_str+1, arg, 2)) + return 0; + return 1; + } + switch (sstep_state) { + case 1: + /* validate the "g" packet to get the IP */ + kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs, + NUMREGBYTES); + gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs); + v2printk("Stopped at IP: %lx\n", + instruction_pointer(&kgdbts_regs)); + /* Want to stop at IP + break instruction size by default */ + sstep_addr = instruction_pointer(&kgdbts_regs) + + BREAK_INSTR_SIZE; + break; + case 2: + if (strncmp(put_str, "$OK", 3)) { + eprintk("kgdbts: failed sstep break set\n"); + return 1; + } + break; + case 3: + if (strncmp(put_str, "$T0", 3)) { + eprintk("kgdbts: failed continue sstep\n"); + return 1; + } + break; + case 4: + if (strncmp(put_str, "$OK", 3)) { + eprintk("kgdbts: failed sstep break unset\n"); + return 1; + } + /* Single step is complete so continue on! */ + sstep_state = 0; + return 0; + default: + eprintk("kgdbts: ERROR failed sstep put emulation\n"); + } + + /* Continue on the same test line until emulation is complete */ + ts.idx--; + return 0; +} + +static int final_ack_set(char *put_str, char *arg) +{ + if (strncmp(put_str+1, arg, 2)) + return 1; + final_ack = 1; + return 0; +} +/* + * Test to plant a breakpoint and detach, which should clear out the + * breakpoint and restore the original instruction. + */ +static struct test_struct plant_and_detach_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "kgdbts_break_test", "OK", sw_break, }, /* set sw breakpoint */ + { "D", "OK" }, /* Detach */ + { "", "" }, +}; + +/* + * Simple test to write in a software breakpoint, check for the + * correct stop location and detach. + */ +static struct test_struct sw_breakpoint_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "kgdbts_break_test", "OK", sw_break, }, /* set sw breakpoint */ + { "c", "T0*", }, /* Continue */ + { "g", "kgdbts_break_test", 0, check_and_rewind_pc }, + { "write", "OK", write_regs }, + { "kgdbts_break_test", "OK", sw_rem_break }, /*remove breakpoint */ + { "D", "OK" }, /* Detach */ + { "D", "OK", 0, got_break }, /* If the test worked we made it here */ + { "", "" }, +}; + +/* + * Test a known bad memory read location to test the fault handler and + * read bytes 1-8 at the bad address + */ +static struct test_struct bad_read_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "m0,1", "E*" }, /* read 1 byte at address 1 */ + { "m0,2", "E*" }, /* read 1 byte at address 2 */ + { "m0,3", "E*" }, /* read 1 byte at address 3 */ + { "m0,4", "E*" }, /* read 1 byte at address 4 */ + { "m0,5", "E*" }, /* read 1 byte at address 5 */ + { "m0,6", "E*" }, /* read 1 byte at address 6 */ + { "m0,7", "E*" }, /* read 1 byte at address 7 */ + { "m0,8", "E*" }, /* read 1 byte at address 8 */ + { "D", "OK" }, /* Detach which removes all breakpoints and continues */ + { "", "" }, +}; + +/* + * Test for hitting a breakpoint, remove it, single step, plant it + * again and detach. + */ +static struct test_struct singlestep_break_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "kgdbts_break_test", "OK", sw_break, }, /* set sw breakpoint */ + { "c", "T0*", }, /* Continue */ + { "g", "kgdbts_break_test", 0, check_and_rewind_pc }, + { "write", "OK", write_regs }, /* Write registers */ + { "kgdbts_break_test", "OK", sw_rem_break }, /*remove breakpoint */ + { "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */ + { "g", "kgdbts_break_test", 0, check_single_step }, + { "kgdbts_break_test", "OK", sw_break, }, /* set sw breakpoint */ + { "c", "T0*", }, /* Continue */ + { "g", "kgdbts_break_test", 0, check_and_rewind_pc }, + { "write", "OK", write_regs }, /* Write registers */ + { "D", "OK" }, /* Remove all breakpoints and continues */ + { "", "" }, +}; + +/* + * Test for hitting a breakpoint at do_fork for what ever the number + * of iterations required by the variable repeat_test. + */ +static struct test_struct do_fork_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "do_fork", "OK", sw_break, }, /* set sw breakpoint */ + { "c", "T0*", }, /* Continue */ + { "g", "do_fork", 0, check_and_rewind_pc }, /* check location */ + { "write", "OK", write_regs }, /* Write registers */ + { "do_fork", "OK", sw_rem_break }, /*remove breakpoint */ + { "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */ + { "g", "do_fork", 0, check_single_step }, + { "do_fork", "OK", sw_break, }, /* set sw breakpoint */ + { "7", "T0*", skip_back_repeat_test }, /* Loop based on repeat_test */ + { "D", "OK", 0, final_ack_set }, /* detach and unregister I/O */ + { "", "" }, +}; + +/* Test for hitting a breakpoint at sys_open for what ever the number + * of iterations required by the variable repeat_test. + */ +static struct test_struct sys_open_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "sys_open", "OK", sw_break, }, /* set sw breakpoint */ + { "c", "T0*", }, /* Continue */ + { "g", "sys_open", 0, check_and_rewind_pc }, /* check location */ + { "write", "OK", write_regs }, /* Write registers */ + { "sys_open", "OK", sw_rem_break }, /*remove breakpoint */ + { "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */ + { "g", "sys_open", 0, check_single_step }, + { "sys_open", "OK", sw_break, }, /* set sw breakpoint */ + { "7", "T0*", skip_back_repeat_test }, /* Loop based on repeat_test */ + { "D", "OK", 0, final_ack_set }, /* detach and unregister I/O */ + { "", "" }, +}; + +/* + * Test for hitting a simple hw breakpoint + */ +static struct test_struct hw_breakpoint_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "kgdbts_break_test", "OK", hw_break, }, /* set hw breakpoint */ + { "c", "T0*", }, /* Continue */ + { "g", "kgdbts_break_test", 0, check_and_rewind_pc }, + { "write", "OK", write_regs }, + { "kgdbts_break_test", "OK", hw_rem_break }, /*remove breakpoint */ + { "D", "OK" }, /* Detach */ + { "D", "OK", 0, got_break }, /* If the test worked we made it here */ + { "", "" }, +}; + +/* + * Test for hitting a hw write breakpoint + */ +static struct test_struct hw_write_break_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "hw_break_val", "OK", hw_write_break, }, /* set hw breakpoint */ + { "c", "T0*", 0, got_break }, /* Continue */ + { "g", "silent", 0, check_and_rewind_pc }, + { "write", "OK", write_regs }, + { "hw_break_val", "OK", hw_rem_write_break }, /*remove breakpoint */ + { "D", "OK" }, /* Detach */ + { "D", "OK", 0, got_break }, /* If the test worked we made it here */ + { "", "" }, +}; + +/* + * Test for hitting a hw access breakpoint + */ +static struct test_struct hw_access_break_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "hw_break_val", "OK", hw_access_break, }, /* set hw breakpoint */ + { "c", "T0*", 0, got_break }, /* Continue */ + { "g", "silent", 0, check_and_rewind_pc }, + { "write", "OK", write_regs }, + { "hw_break_val", "OK", hw_rem_access_break }, /*remove breakpoint */ + { "D", "OK" }, /* Detach */ + { "D", "OK", 0, got_break }, /* If the test worked we made it here */ + { "", "" }, +}; + +/* + * Test for hitting a hw access breakpoint + */ +static struct test_struct nmi_sleep_test[] = { + { "?", "S0*" }, /* Clear break points */ + { "c", "T0*", 0, got_break }, /* Continue */ + { "D", "OK" }, /* Detach */ + { "D", "OK", 0, got_break }, /* If the test worked we made it here */ + { "", "" }, +}; + +static void fill_get_buf(char *buf) +{ + unsigned char checksum = 0; + int count = 0; + char ch; + + strcpy(get_buf, "$"); + strcat(get_buf, buf); + while ((ch = buf[count])) { + checksum += ch; + count++; + } + strcat(get_buf, "#"); + get_buf[count + 2] = hexchars[checksum >> 4]; + get_buf[count + 3] = hexchars[checksum & 0xf]; + get_buf[count + 4] = '\0'; + v2printk("get%i: %s\n", ts.idx, get_buf); +} + +static int validate_simple_test(char *put_str) +{ + char *chk_str; + + if (ts.tst[ts.idx].put_handler) + return ts.tst[ts.idx].put_handler(put_str, + ts.tst[ts.idx].put); + + chk_str = ts.tst[ts.idx].put; + if (*put_str == '$') + put_str++; + + while (*chk_str != '\0' && *put_str != '\0') { + /* If someone does a * to match the rest of the string, allow + * it, or stop if the recieved string is complete. + */ + if (*put_str == '#' || *chk_str == '*') + return 0; + if (*put_str != *chk_str) + return 1; + + chk_str++; + put_str++; + } + if (*chk_str == '\0' && (*put_str == '\0' || *put_str == '#')) + return 0; + + return 1; +} + +static int run_simple_test(int is_get_char, int chr) +{ + int ret = 0; + if (is_get_char) { + /* Send an ACK on the get if a prior put completed and set the + * send ack variable + */ + if (send_ack) { + send_ack = 0; + return '+'; + } + /* On the first get char, fill the transmit buffer and then + * take from the get_string. + */ + if (get_buf_cnt == 0) { + if (ts.tst[ts.idx].get_handler) + ts.tst[ts.idx].get_handler(ts.tst[ts.idx].get); + else + fill_get_buf(ts.tst[ts.idx].get); + } + + if (get_buf[get_buf_cnt] == '\0') { + eprintk("kgdbts: ERROR GET: EOB on '%s' at %i\n", + ts.name, ts.idx); + get_buf_cnt = 0; + fill_get_buf("D"); + } + ret = get_buf[get_buf_cnt]; + get_buf_cnt++; + return ret; + } + + /* This callback is a put char which is when kgdb sends data to + * this I/O module. + */ + if (ts.tst[ts.idx].get[0] == '\0' && + ts.tst[ts.idx].put[0] == '\0') { + eprintk("kgdbts: ERROR: beyond end of test on" + " '%s' line %i\n", ts.name, ts.idx); + return 0; + } + + if (put_buf_cnt >= BUFMAX) { + eprintk("kgdbts: ERROR: put buffer overflow on" + " '%s' line %i\n", ts.name, ts.idx); + put_buf_cnt = 0; + return 0; + } + /* Ignore everything until the first valid packet start '$' */ + if (put_buf_cnt == 0 && chr != '$') + return 0; + + put_buf[put_buf_cnt] = chr; + put_buf_cnt++; + + /* End of packet == #XX so look for the '#' */ + if (put_buf_cnt > 3 && put_buf[put_buf_cnt - 3] == '#') { + put_buf[put_buf_cnt] = '\0'; + v2printk("put%i: %s\n", ts.idx, put_buf); + /* Trigger check here */ + if (ts.validate_put && ts.validate_put(put_buf)) { + eprintk("kgdbts: ERROR PUT: end of test " + "buffer on '%s' line %i expected %s got %s\n", + ts.name, ts.idx, ts.tst[ts.idx].put, put_buf); + } + ts.idx++; + put_buf_cnt = 0; + get_buf_cnt = 0; + send_ack = 1; + } + return 0; +} + +static void init_simple_test(void) +{ + memset(&ts, 0, sizeof(ts)); + ts.run_test = run_simple_test; + ts.validate_put = validate_simple_test; +} + +static void run_plant_and_detach_test(int is_early) +{ + char before[BREAK_INSTR_SIZE]; + char after[BREAK_INSTR_SIZE]; + + probe_kernel_read(before, (char *)kgdbts_break_test, + BREAK_INSTR_SIZE); + init_simple_test(); + ts.tst = plant_and_detach_test; + ts.name = "plant_and_detach_test"; + /* Activate test with initial breakpoint */ + if (!is_early) + kgdb_breakpoint(); + probe_kernel_read(after, (char *)kgdbts_break_test, + BREAK_INSTR_SIZE); + if (memcmp(before, after, BREAK_INSTR_SIZE)) { + printk(KERN_CRIT "kgdbts: ERROR kgdb corrupted memory\n"); + panic("kgdb memory corruption"); + } + + /* complete the detach test */ + if (!is_early) + kgdbts_break_test(); +} + +static void run_breakpoint_test(int is_hw_breakpoint) +{ + test_complete = 0; + init_simple_test(); + if (is_hw_breakpoint) { + ts.tst = hw_breakpoint_test; + ts.name = "hw_breakpoint_test"; + } else { + ts.tst = sw_breakpoint_test; + ts.name = "sw_breakpoint_test"; + } + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); + /* run code with the break point in it */ + kgdbts_break_test(); + kgdb_breakpoint(); + + if (test_complete) + return; + + eprintk("kgdbts: ERROR %s test failed\n", ts.name); +} + +static void run_hw_break_test(int is_write_test) +{ + test_complete = 0; + init_simple_test(); + if (is_write_test) { + ts.tst = hw_write_break_test; + ts.name = "hw_write_break_test"; + } else { + ts.tst = hw_access_break_test; + ts.name = "hw_access_break_test"; + } + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); + hw_break_val_access(); + if (is_write_test) { + if (test_complete == 2) + eprintk("kgdbts: ERROR %s broke on access\n", + ts.name); + hw_break_val_write(); + } + kgdb_breakpoint(); + + if (test_complete == 1) + return; + + eprintk("kgdbts: ERROR %s test failed\n", ts.name); +} + +static void run_nmi_sleep_test(int nmi_sleep) +{ + unsigned long flags; + + init_simple_test(); + ts.tst = nmi_sleep_test; + ts.name = "nmi_sleep_test"; + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); + local_irq_save(flags); + mdelay(nmi_sleep*1000); + touch_nmi_watchdog(); + local_irq_restore(flags); + if (test_complete != 2) + eprintk("kgdbts: ERROR nmi_test did not hit nmi\n"); + kgdb_breakpoint(); + if (test_complete == 1) + return; + + eprintk("kgdbts: ERROR %s test failed\n", ts.name); +} + +static void run_bad_read_test(void) +{ + init_simple_test(); + ts.tst = bad_read_test; + ts.name = "bad_read_test"; + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); +} + +static void run_do_fork_test(void) +{ + init_simple_test(); + ts.tst = do_fork_test; + ts.name = "do_fork_test"; + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); +} + +static void run_sys_open_test(void) +{ + init_simple_test(); + ts.tst = sys_open_test; + ts.name = "sys_open_test"; + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); +} + +static void run_singlestep_break_test(void) +{ + init_simple_test(); + ts.tst = singlestep_break_test; + ts.name = "singlestep_breakpoint_test"; + /* Activate test with initial breakpoint */ + kgdb_breakpoint(); + kgdbts_break_test(); + kgdbts_break_test(); +} + +static void kgdbts_run_tests(void) +{ + char *ptr; + int fork_test = 0; + int sys_open_test = 0; + int nmi_sleep = 0; + + ptr = strstr(config, "F"); + if (ptr) + fork_test = simple_strtol(ptr+1, NULL, 10); + ptr = strstr(config, "S"); + if (ptr) + sys_open_test = simple_strtol(ptr+1, NULL, 10); + ptr = strstr(config, "N"); + if (ptr) + nmi_sleep = simple_strtol(ptr+1, NULL, 10); + + /* required internal KGDB tests */ + v1printk("kgdbts:RUN plant and detach test\n"); + run_plant_and_detach_test(0); + v1printk("kgdbts:RUN sw breakpoint test\n"); + run_breakpoint_test(0); + v1printk("kgdbts:RUN bad memory access test\n"); + run_bad_read_test(); + v1printk("kgdbts:RUN singlestep breakpoint test\n"); + run_singlestep_break_test(); + + /* ===Optional tests=== */ + + /* All HW break point tests */ + if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT) { + v1printk("kgdbts:RUN hw breakpoint test\n"); + run_breakpoint_test(1); + v1printk("kgdbts:RUN hw write breakpoint test\n"); + run_hw_break_test(1); + v1printk("kgdbts:RUN access write breakpoint test\n"); + run_hw_break_test(0); + } + + if (nmi_sleep) { + v1printk("kgdbts:RUN NMI sleep %i seconds test\n", nmi_sleep); + run_nmi_sleep_test(nmi_sleep); + } + + /* If the do_fork test is run it will be the last test that is + * executed because a kernel thread will be spawned at the very + * end to unregister the debug hooks. + */ + if (fork_test) { + repeat_test = fork_test; + printk(KERN_INFO "kgdbts:RUN do_fork for %i breakpoints\n", + repeat_test); + kthread_run(kgdbts_unreg_thread, 0, "kgdbts_unreg"); + run_do_fork_test(); + return; + } + + /* If the sys_open test is run it will be the last test that is + * executed because a kernel thread will be spawned at the very + * end to unregister the debug hooks. + */ + if (sys_open_test) { + repeat_test = sys_open_test; + printk(KERN_INFO "kgdbts:RUN sys_open for %i breakpoints\n", + repeat_test); + kthread_run(kgdbts_unreg_thread, 0, "kgdbts_unreg"); + run_sys_open_test(); + return; + } + /* Shutdown and unregister */ + kgdb_unregister_io_module(&kgdbts_io_ops); + configured = 0; +} + +static int kgdbts_option_setup(char *opt) +{ + if (strlen(opt) > MAX_CONFIG_LEN) { + printk(KERN_ERR "kgdbts: config string too long\n"); + return -ENOSPC; + } + strcpy(config, opt); + + verbose = 0; + if (strstr(config, "V1")) + verbose = 1; + if (strstr(config, "V2")) + verbose = 2; + + return 0; +} + +__setup("kgdbts=", kgdbts_option_setup); + +static int configure_kgdbts(void) +{ + int err = 0; + + if (!strlen(config) || isspace(config[0])) + goto noconfig; + err = kgdbts_option_setup(config); + if (err) + goto noconfig; + + final_ack = 0; + run_plant_and_detach_test(1); + + err = kgdb_register_io_module(&kgdbts_io_ops); + if (err) { + configured = 0; + return err; + } + configured = 1; + kgdbts_run_tests(); + + return err; + +noconfig: + config[0] = 0; + configured = 0; + + return err; +} + +static int __init init_kgdbts(void) +{ + /* Already configured? */ + if (configured == 1) + return 0; + + return configure_kgdbts(); +} + +static void cleanup_kgdbts(void) +{ + if (configured == 1) + kgdb_unregister_io_module(&kgdbts_io_ops); +} + +static int kgdbts_get_char(void) +{ + int val = 0; + + if (ts.run_test) + val = ts.run_test(1, 0); + + return val; +} + +static void kgdbts_put_char(u8 chr) +{ + if (ts.run_test) + ts.run_test(0, chr); +} + +static int param_set_kgdbts_var(const char *kmessage, struct kernel_param *kp) +{ + int len = strlen(kmessage); + + if (len >= MAX_CONFIG_LEN) { + printk(KERN_ERR "kgdbts: config string too long\n"); + return -ENOSPC; + } + + /* Only copy in the string if the init function has not run yet */ + if (configured < 0) { + strcpy(config, kmessage); + return 0; + } + + if (kgdb_connected) { + printk(KERN_ERR + "kgdbts: Cannot reconfigure while KGDB is connected.\n"); + + return -EBUSY; + } + + strcpy(config, kmessage); + /* Chop out \n char as a result of echo */ + if (config[len - 1] == '\n') + config[len - 1] = '\0'; + + if (configured == 1) + cleanup_kgdbts(); + + /* Go and configure with the new params. */ + return configure_kgdbts(); +} + +static void kgdbts_pre_exp_handler(void) +{ + /* Increment the module count when the debugger is active */ + if (!kgdb_connected) + try_module_get(THIS_MODULE); +} + +static void kgdbts_post_exp_handler(void) +{ + /* decrement the module count when the debugger detaches */ + if (!kgdb_connected) + module_put(THIS_MODULE); +} + +static struct kgdb_io kgdbts_io_ops = { + .name = "kgdbts", + .read_char = kgdbts_get_char, + .write_char = kgdbts_put_char, + .pre_exception = kgdbts_pre_exp_handler, + .post_exception = kgdbts_post_exp_handler, +}; + +module_init(init_kgdbts); +module_exit(cleanup_kgdbts); +module_param_call(kgdbts, param_set_kgdbts_var, param_get_string, &kps, 0644); +MODULE_PARM_DESC(kgdbts, "<A|V1|V2>[F#|S#][N#]"); +MODULE_DESCRIPTION("KGDB Test Suite"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wind River Systems, Inc."); + |