|  | /* | 
|  | * QTest testcase for Realtek 8139 NIC | 
|  | * | 
|  | * Copyright (c) 2013-2014 SUSE LINUX Products GmbH | 
|  | * | 
|  | * This work is licensed under the terms of the GNU GPL, version 2 or later. | 
|  | * See the COPYING file in the top-level directory. | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "libqtest-single.h" | 
|  | #include "libqos/pci-pc.h" | 
|  | #include "qemu/timer.h" | 
|  |  | 
|  | static int verbosity_level; | 
|  |  | 
|  | /* Tests only initialization so far. TODO: Replace with functional tests */ | 
|  | static void nop(void) | 
|  | { | 
|  | } | 
|  |  | 
|  | #define CLK 33333333 | 
|  |  | 
|  | static QPCIBus *pcibus; | 
|  | static QPCIDevice *pcidev; | 
|  | static QPCIBar dev_bar; | 
|  |  | 
|  | static void save_fn(QPCIDevice *dev, int devfn, void *data) | 
|  | { | 
|  | QPCIDevice **pdev = (QPCIDevice **) data; | 
|  |  | 
|  | *pdev = dev; | 
|  | } | 
|  |  | 
|  | static QPCIDevice *get_device(void) | 
|  | { | 
|  | QPCIDevice *dev; | 
|  |  | 
|  | pcibus = qpci_new_pc(global_qtest, NULL); | 
|  | qpci_device_foreach(pcibus, 0x10ec, 0x8139, save_fn, &dev); | 
|  | g_assert(dev != NULL); | 
|  |  | 
|  | return dev; | 
|  | } | 
|  |  | 
|  | #define PORT(name, len, val) \ | 
|  | static unsigned __attribute__((unused)) in_##name(void) \ | 
|  | { \ | 
|  | unsigned res = qpci_io_read##len(pcidev, dev_bar, (val));     \ | 
|  | if (verbosity_level >= 2) { \ | 
|  | g_test_message("*%s -> %x", #name, res); \ | 
|  | } \ | 
|  | return res; \ | 
|  | } \ | 
|  | static void out_##name(unsigned v) \ | 
|  | { \ | 
|  | if (verbosity_level >= 2) { \ | 
|  | g_test_message("%x -> *%s", v, #name); \ | 
|  | } \ | 
|  | qpci_io_write##len(pcidev, dev_bar, (val), v);        \ | 
|  | } | 
|  |  | 
|  | PORT(Timer, l, 0x48) | 
|  | PORT(IntrMask, w, 0x3c) | 
|  | PORT(IntrStatus, w, 0x3E) | 
|  | PORT(TimerInt, l, 0x54) | 
|  |  | 
|  | #define fatal(...) do { g_test_message(__VA_ARGS__); g_assert_not_reached(); } while (0) | 
|  |  | 
|  | static void test_timer(void) | 
|  | { | 
|  | const unsigned from = 0.95 * CLK; | 
|  | const unsigned to = 1.6 * CLK; | 
|  | unsigned prev, curr, next; | 
|  | unsigned cnt, diff; | 
|  |  | 
|  | out_IntrMask(0); | 
|  |  | 
|  | in_IntrStatus(); | 
|  | in_Timer(); | 
|  | in_Timer(); | 
|  |  | 
|  | /* Test 1. test counter continue and continue */ | 
|  | out_TimerInt(0); /* disable timer */ | 
|  | out_IntrStatus(0x4000); | 
|  | out_Timer(12345); /* reset timer to 0 */ | 
|  | curr = in_Timer(); | 
|  | if (curr > 0.1 * CLK) { | 
|  | fatal("time too big %u\n", curr); | 
|  | } | 
|  | for (cnt = 0; ; ) { | 
|  | clock_step(1 * NANOSECONDS_PER_SECOND); | 
|  | prev = curr; | 
|  | curr = in_Timer(); | 
|  |  | 
|  | /* test skip is in a specific range */ | 
|  | diff = (curr-prev) & 0xffffffffu; | 
|  | if (diff < from || diff > to) { | 
|  | fatal("Invalid diff %u (%u-%u)\n", diff, from, to); | 
|  | } | 
|  | if (curr < prev && ++cnt == 3) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Test 2. Check we didn't get an interrupt with TimerInt == 0 */ | 
|  | if (in_IntrStatus() & 0x4000) { | 
|  | fatal("got an interrupt\n"); | 
|  | } | 
|  |  | 
|  | /* Test 3. Setting TimerInt to 1 and Timer to 0 get interrupt */ | 
|  | out_TimerInt(1); | 
|  | out_Timer(0); | 
|  | clock_step(40); | 
|  | if ((in_IntrStatus() & 0x4000) == 0) { | 
|  | fatal("we should have an interrupt here!\n"); | 
|  | } | 
|  |  | 
|  | /* Test 3. Check acknowledge */ | 
|  | out_IntrStatus(0x4000); | 
|  | if (in_IntrStatus() & 0x4000) { | 
|  | fatal("got an interrupt\n"); | 
|  | } | 
|  |  | 
|  | /* Test. Status set after Timer reset */ | 
|  | out_Timer(0); | 
|  | out_TimerInt(0); | 
|  | out_IntrStatus(0x4000); | 
|  | curr = in_Timer(); | 
|  | out_TimerInt(curr + 0.5 * CLK); | 
|  | clock_step(1 * NANOSECONDS_PER_SECOND); | 
|  | out_Timer(0); | 
|  | if ((in_IntrStatus() & 0x4000) == 0) { | 
|  | fatal("we should have an interrupt here!\n"); | 
|  | } | 
|  |  | 
|  | /* Test. Status set after TimerInt reset */ | 
|  | out_Timer(0); | 
|  | out_TimerInt(0); | 
|  | out_IntrStatus(0x4000); | 
|  | curr = in_Timer(); | 
|  | out_TimerInt(curr + 0.5 * CLK); | 
|  | clock_step(1 * NANOSECONDS_PER_SECOND); | 
|  | out_TimerInt(0); | 
|  | if ((in_IntrStatus() & 0x4000) == 0) { | 
|  | fatal("we should have an interrupt here!\n"); | 
|  | } | 
|  |  | 
|  | /* Test 4. Increment TimerInt we should see an interrupt */ | 
|  | curr = in_Timer(); | 
|  | next = curr + 5.0 * CLK; | 
|  | out_TimerInt(next); | 
|  | for (cnt = 0; ; ) { | 
|  | clock_step(1 * NANOSECONDS_PER_SECOND); | 
|  | prev = curr; | 
|  | curr = in_Timer(); | 
|  | diff = (curr-prev) & 0xffffffffu; | 
|  | if (diff < from || diff > to) { | 
|  | fatal("Invalid diff %u (%u-%u)\n", diff, from, to); | 
|  | } | 
|  | if (cnt < 3 && curr > next) { | 
|  | if ((in_IntrStatus() & 0x4000) == 0) { | 
|  | fatal("we should have an interrupt here!\n"); | 
|  | } | 
|  | out_IntrStatus(0x4000); | 
|  | next = curr + 5.0 * CLK; | 
|  | out_TimerInt(next); | 
|  | if (++cnt == 3) { | 
|  | out_TimerInt(1); | 
|  | } | 
|  | /* Test 5. Second time we pass from 0 should see an interrupt */ | 
|  | } else if (cnt >= 3 && curr < prev) { | 
|  | /* here we should have an interrupt */ | 
|  | if ((in_IntrStatus() & 0x4000) == 0) { | 
|  | fatal("we should have an interrupt here!\n"); | 
|  | } | 
|  | out_IntrStatus(0x4000); | 
|  | if (++cnt == 5) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | g_test_message("Everythink is ok!"); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void test_init(void) | 
|  | { | 
|  | uint64_t barsize; | 
|  |  | 
|  | pcidev = get_device(); | 
|  |  | 
|  | dev_bar = qpci_iomap(pcidev, 0, &barsize); | 
|  |  | 
|  | qpci_device_enable(pcidev); | 
|  |  | 
|  | test_timer(); | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | int ret; | 
|  | char *v_env = getenv("V"); | 
|  |  | 
|  | if (v_env) { | 
|  | verbosity_level = atoi(v_env); | 
|  | } | 
|  |  | 
|  | g_test_init(&argc, &argv, NULL); | 
|  |  | 
|  | if (!qtest_has_device("rtl8139")) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | qtest_start("-device rtl8139"); | 
|  |  | 
|  | qtest_add_func("/rtl8139/nop", nop); | 
|  | qtest_add_func("/rtl8139/timer", test_init); | 
|  |  | 
|  | ret = g_test_run(); | 
|  |  | 
|  | qtest_end(); | 
|  |  | 
|  | return ret; | 
|  | } |