From 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 Mon Sep 17 00:00:00 2001 From: Yunhong Jiang Date: Tue, 4 Aug 2015 12:17:53 -0700 Subject: Add the rt linux 4.1.3-rt3 as base Import the rt linux 4.1.3-rt3 as OPNFV kvm base. It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and the base is: commit 0917f823c59692d751951bf5ea699a2d1e2f26a2 Author: Sebastian Andrzej Siewior Date: Sat Jul 25 12:13:34 2015 +0200 Prepare v4.1.3-rt3 Signed-off-by: Sebastian Andrzej Siewior We lose all the git history this way and it's not good. We should apply another opnfv project repo in future. Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423 Signed-off-by: Yunhong Jiang --- kernel/drivers/staging/unisys/visorutil/Kconfig | 9 + kernel/drivers/staging/unisys/visorutil/Makefile | 9 + .../drivers/staging/unisys/visorutil/charqueue.c | 127 +++++++++++++ .../drivers/staging/unisys/visorutil/charqueue.h | 37 ++++ .../drivers/staging/unisys/visorutil/memregion.h | 43 +++++ .../staging/unisys/visorutil/memregion_direct.c | 207 +++++++++++++++++++++ .../staging/unisys/visorutil/periodic_work.c | 204 ++++++++++++++++++++ .../staging/unisys/visorutil/visorkmodutils.c | 71 +++++++ 8 files changed, 707 insertions(+) create mode 100644 kernel/drivers/staging/unisys/visorutil/Kconfig create mode 100644 kernel/drivers/staging/unisys/visorutil/Makefile create mode 100644 kernel/drivers/staging/unisys/visorutil/charqueue.c create mode 100644 kernel/drivers/staging/unisys/visorutil/charqueue.h create mode 100644 kernel/drivers/staging/unisys/visorutil/memregion.h create mode 100644 kernel/drivers/staging/unisys/visorutil/memregion_direct.c create mode 100644 kernel/drivers/staging/unisys/visorutil/periodic_work.c create mode 100644 kernel/drivers/staging/unisys/visorutil/visorkmodutils.c (limited to 'kernel/drivers/staging/unisys/visorutil') diff --git a/kernel/drivers/staging/unisys/visorutil/Kconfig b/kernel/drivers/staging/unisys/visorutil/Kconfig new file mode 100644 index 000000000..be9c2cf89 --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/Kconfig @@ -0,0 +1,9 @@ +# +# Unisys timskmod configuration +# + +config UNISYS_VISORUTIL + tristate "Unisys visorutil driver" + ---help--- + If you say Y here, you will enable the Unisys visorutil driver. + diff --git a/kernel/drivers/staging/unisys/visorutil/Makefile b/kernel/drivers/staging/unisys/visorutil/Makefile new file mode 100644 index 000000000..d9ab5a36e --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for Unisys timskmod +# + +obj-$(CONFIG_UNISYS_VISORUTIL) += visorutil.o + +visorutil-y := charqueue.o periodic_work.o memregion_direct.o visorkmodutils.o + +ccflags-y += -Idrivers/staging/unisys/include diff --git a/kernel/drivers/staging/unisys/visorutil/charqueue.c b/kernel/drivers/staging/unisys/visorutil/charqueue.c new file mode 100644 index 000000000..c91752a2d --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/charqueue.c @@ -0,0 +1,127 @@ +/* charqueue.c + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * 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 of the License, 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +/* + * Simple character queue implementation for Linux kernel mode. + */ + +#include "charqueue.h" + +#define MYDRVNAME "charqueue" + +#define IS_EMPTY(charqueue) (charqueue->head == charqueue->tail) + +struct charqueue { + int alloc_size; + int nslots; + spinlock_t lock; /* read/write lock for this structure */ + int head, tail; + unsigned char buf[0]; +}; + +struct charqueue *visor_charqueue_create(ulong nslots) +{ + int alloc_size = sizeof(struct charqueue) + nslots + 1; + struct charqueue *cq; + + cq = kmalloc(alloc_size, GFP_KERNEL|__GFP_NORETRY); + if (cq == NULL) + return NULL; + cq->alloc_size = alloc_size; + cq->nslots = nslots; + cq->head = 0; + cq->tail = 0; + spin_lock_init(&cq->lock); + return cq; +} +EXPORT_SYMBOL_GPL(visor_charqueue_create); + +void visor_charqueue_enqueue(struct charqueue *charqueue, unsigned char c) +{ + int alloc_slots = charqueue->nslots+1; /* 1 slot is always empty */ + + spin_lock(&charqueue->lock); + charqueue->head = (charqueue->head+1) % alloc_slots; + if (charqueue->head == charqueue->tail) + /* overflow; overwrite the oldest entry */ + charqueue->tail = (charqueue->tail+1) % alloc_slots; + charqueue->buf[charqueue->head] = c; + spin_unlock(&charqueue->lock); +} +EXPORT_SYMBOL_GPL(visor_charqueue_enqueue); + +BOOL visor_charqueue_is_empty(struct charqueue *charqueue) +{ + BOOL b; + + spin_lock(&charqueue->lock); + b = IS_EMPTY(charqueue); + spin_unlock(&charqueue->lock); + return b; +} +EXPORT_SYMBOL_GPL(visor_charqueue_is_empty); + +static int charqueue_dequeue_1(struct charqueue *charqueue) +{ + int alloc_slots = charqueue->nslots + 1; /* 1 slot is always empty */ + + if (IS_EMPTY(charqueue)) + return -1; + charqueue->tail = (charqueue->tail+1) % alloc_slots; + return charqueue->buf[charqueue->tail]; +} + +int charqueue_dequeue(struct charqueue *charqueue) +{ + int rc; + + spin_lock(&charqueue->lock); + rc = charqueue_dequeue_1(charqueue); + spin_unlock(&charqueue->lock); + return rc; +} + +int visor_charqueue_dequeue_n(struct charqueue *charqueue, unsigned char *buf, + int n) +{ + int rc, counter = 0, c; + + spin_lock(&charqueue->lock); + for (;;) { + if (n <= 0) + break; /* no more buffer space */ + c = charqueue_dequeue_1(charqueue); + if (c < 0) + break; /* no more input */ + *buf = (unsigned char)(c); + buf++; + n--; + counter++; + } + rc = counter; + spin_unlock(&charqueue->lock); + return rc; +} +EXPORT_SYMBOL_GPL(visor_charqueue_dequeue_n); + +void visor_charqueue_destroy(struct charqueue *charqueue) +{ + if (charqueue == NULL) + return; + kfree(charqueue); +} +EXPORT_SYMBOL_GPL(visor_charqueue_destroy); diff --git a/kernel/drivers/staging/unisys/visorutil/charqueue.h b/kernel/drivers/staging/unisys/visorutil/charqueue.h new file mode 100644 index 000000000..f46a776b9 --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/charqueue.h @@ -0,0 +1,37 @@ +/* charqueue.h + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * 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 of the License, 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +#ifndef __CHARQUEUE_H__ +#define __CHARQUEUE_H__ + +#include "timskmod.h" + +/* struct charqueue is an opaque structure to users. + * Fields are declared only in the implementation .c files. + */ +struct charqueue; + +struct charqueue *visor_charqueue_create(ulong nslots); +void visor_charqueue_enqueue(struct charqueue *charqueue, unsigned char c); +int charqueue_dequeue(struct charqueue *charqueue); +int visor_charqueue_dequeue_n(struct charqueue *charqueue, unsigned char *buf, + int n); +BOOL visor_charqueue_is_empty(struct charqueue *charqueue); +void visor_charqueue_destroy(struct charqueue *charqueue); + +#endif + diff --git a/kernel/drivers/staging/unisys/visorutil/memregion.h b/kernel/drivers/staging/unisys/visorutil/memregion.h new file mode 100644 index 000000000..0c3eebcf6 --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/memregion.h @@ -0,0 +1,43 @@ +/* memregion.h + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * 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 of the License, 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +#ifndef __MEMREGION_H__ +#define __MEMREGION_H__ + +#include "timskmod.h" + +/* struct memregion is an opaque structure to users. + * Fields are declared only in the implementation .c files. + */ +struct memregion; + +struct memregion *visor_memregion_create(HOSTADDRESS physaddr, ulong nbytes); +struct memregion *visor_memregion_create_overlapped(struct memregion *parent, + ulong offset, ulong nbytes); +int visor_memregion_resize(struct memregion *memregion, ulong newsize); +int visor_memregion_read(struct memregion *memregion, + ulong offset, void *dest, ulong nbytes); +int visor_memregion_write(struct memregion *memregion, + ulong offset, void *src, ulong nbytes); +void visor_memregion_destroy(struct memregion *memregion); +HOSTADDRESS visor_memregion_get_physaddr(struct memregion *memregion); +ulong visor_memregion_get_nbytes(struct memregion *memregion); +void memregion_dump(struct memregion *memregion, char *s, + ulong off, ulong len, struct seq_file *seq); +void __iomem *visor_memregion_get_pointer(struct memregion *memregion); + +#endif diff --git a/kernel/drivers/staging/unisys/visorutil/memregion_direct.c b/kernel/drivers/staging/unisys/visorutil/memregion_direct.c new file mode 100644 index 000000000..eb7422fbe --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/memregion_direct.c @@ -0,0 +1,207 @@ +/* memregion_direct.c + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * 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 of the License, 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +/* + * This is an implementation of memory regions that can be used to read/write + * channel memory (in main memory of the host system) from code running in + * a virtual partition. + */ +#include "timskmod.h" +#include "memregion.h" + +#define MYDRVNAME "memregion" + +struct memregion { + HOSTADDRESS physaddr; + ulong nbytes; + void __iomem *mapped; + BOOL requested; + BOOL overlapped; +}; + +static BOOL mapit(struct memregion *memregion); +static void unmapit(struct memregion *memregion); + +struct memregion * +visor_memregion_create(HOSTADDRESS physaddr, ulong nbytes) +{ + struct memregion *rc = NULL; + struct memregion *memregion; + + memregion = kzalloc(sizeof(*memregion), GFP_KERNEL | __GFP_NORETRY); + if (memregion == NULL) + return NULL; + + memregion->physaddr = physaddr; + memregion->nbytes = nbytes; + memregion->overlapped = FALSE; + if (!mapit(memregion)) { + rc = NULL; + goto cleanup; + } + rc = memregion; +cleanup: + if (rc == NULL) { + visor_memregion_destroy(memregion); + memregion = NULL; + } + return rc; +} +EXPORT_SYMBOL_GPL(visor_memregion_create); + +struct memregion * +visor_memregion_create_overlapped(struct memregion *parent, ulong offset, + ulong nbytes) +{ + struct memregion *memregion = NULL; + + if (parent == NULL) + return NULL; + + if (parent->mapped == NULL) + return NULL; + + if ((offset >= parent->nbytes) || + ((offset + nbytes) >= parent->nbytes)) + return NULL; + + memregion = kzalloc(sizeof(*memregion), GFP_KERNEL|__GFP_NORETRY); + if (memregion == NULL) + return NULL; + + memregion->physaddr = parent->physaddr + offset; + memregion->nbytes = nbytes; + memregion->mapped = ((u8 __iomem *)(parent->mapped)) + offset; + memregion->requested = FALSE; + memregion->overlapped = TRUE; + return memregion; +} +EXPORT_SYMBOL_GPL(visor_memregion_create_overlapped); + +static BOOL +mapit(struct memregion *memregion) +{ + ulong physaddr = (ulong)(memregion->physaddr); + ulong nbytes = memregion->nbytes; + + memregion->requested = FALSE; + if (request_mem_region(physaddr, nbytes, MYDRVNAME)) + memregion->requested = TRUE; + memregion->mapped = ioremap_cache(physaddr, nbytes); + if (!memregion->mapped) + return FALSE; + return TRUE; +} + +static void +unmapit(struct memregion *memregion) +{ + if (memregion->mapped != NULL) { + iounmap(memregion->mapped); + memregion->mapped = NULL; + } + if (memregion->requested) { + release_mem_region((ulong)(memregion->physaddr), + memregion->nbytes); + memregion->requested = FALSE; + } +} + +HOSTADDRESS +visor_memregion_get_physaddr(struct memregion *memregion) +{ + return memregion->physaddr; +} +EXPORT_SYMBOL_GPL(visor_memregion_get_physaddr); + +ulong +visor_memregion_get_nbytes(struct memregion *memregion) +{ + return memregion->nbytes; +} +EXPORT_SYMBOL_GPL(visor_memregion_get_nbytes); + +void __iomem * +visor_memregion_get_pointer(struct memregion *memregion) +{ + return memregion->mapped; +} +EXPORT_SYMBOL_GPL(visor_memregion_get_pointer); + +int +visor_memregion_resize(struct memregion *memregion, ulong newsize) +{ + if (newsize == memregion->nbytes) + return 0; + if (memregion->overlapped) + /* no error check here - we no longer know the + * parent's range! + */ + memregion->nbytes = newsize; + else { + unmapit(memregion); + memregion->nbytes = newsize; + if (!mapit(memregion)) + return -1; + } + return 0; +} +EXPORT_SYMBOL_GPL(visor_memregion_resize); + +static int +memregion_readwrite(BOOL is_write, + struct memregion *memregion, ulong offset, + void *local, ulong nbytes) +{ + if (offset + nbytes > memregion->nbytes) + return -EIO; + + if (is_write) + memcpy_toio(memregion->mapped + offset, local, nbytes); + else + memcpy_fromio(local, memregion->mapped + offset, nbytes); + + return 0; +} + +int +visor_memregion_read(struct memregion *memregion, ulong offset, void *dest, + ulong nbytes) +{ + return memregion_readwrite(FALSE, memregion, offset, dest, nbytes); +} +EXPORT_SYMBOL_GPL(visor_memregion_read); + +int +visor_memregion_write(struct memregion *memregion, ulong offset, void *src, + ulong nbytes) +{ + return memregion_readwrite(TRUE, memregion, offset, src, nbytes); +} +EXPORT_SYMBOL_GPL(visor_memregion_write); + +void +visor_memregion_destroy(struct memregion *memregion) +{ + if (memregion == NULL) + return; + if (!memregion->overlapped) + unmapit(memregion); + kfree(memregion); +} +EXPORT_SYMBOL_GPL(visor_memregion_destroy); + diff --git a/kernel/drivers/staging/unisys/visorutil/periodic_work.c b/kernel/drivers/staging/unisys/visorutil/periodic_work.c new file mode 100644 index 000000000..abbfb4889 --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/periodic_work.c @@ -0,0 +1,204 @@ +/* periodic_work.c + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * 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 of the License, 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +/* + * Helper functions to schedule periodic work in Linux kernel mode. + */ + +#include "timskmod.h" +#include "periodic_work.h" + +#define MYDRVNAME "periodic_work" + +struct periodic_work { + rwlock_t lock; + struct delayed_work work; + void (*workfunc)(void *); + void *workfuncarg; + BOOL is_scheduled; + BOOL want_to_stop; + ulong jiffy_interval; + struct workqueue_struct *workqueue; + const char *devnam; +}; + +static void periodic_work_func(struct work_struct *work) +{ + struct periodic_work *pw; + + pw = container_of(work, struct periodic_work, work.work); + (*pw->workfunc)(pw->workfuncarg); +} + +struct periodic_work *visor_periodic_work_create(ulong jiffy_interval, + struct workqueue_struct *workqueue, + void (*workfunc)(void *), + void *workfuncarg, + const char *devnam) +{ + struct periodic_work *pw; + + pw = kzalloc(sizeof(*pw), GFP_KERNEL | __GFP_NORETRY); + if (!pw) + return NULL; + + rwlock_init(&pw->lock); + pw->jiffy_interval = jiffy_interval; + pw->workqueue = workqueue; + pw->workfunc = workfunc; + pw->workfuncarg = workfuncarg; + pw->devnam = devnam; + return pw; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_create); + +void visor_periodic_work_destroy(struct periodic_work *pw) +{ + kfree(pw); +} +EXPORT_SYMBOL_GPL(visor_periodic_work_destroy); + +/** Call this from your periodic work worker function to schedule the next + * call. + * If this function returns FALSE, there was a failure and the + * periodic work is no longer scheduled + */ +BOOL visor_periodic_work_nextperiod(struct periodic_work *pw) +{ + BOOL rc = FALSE; + + write_lock(&pw->lock); + if (pw->want_to_stop) { + pw->is_scheduled = FALSE; + pw->want_to_stop = FALSE; + rc = TRUE; /* yes, TRUE; see visor_periodic_work_stop() */ + goto unlock; + } else if (queue_delayed_work(pw->workqueue, &pw->work, + pw->jiffy_interval) < 0) { + pw->is_scheduled = FALSE; + rc = FALSE; + goto unlock; + } + rc = TRUE; +unlock: + write_unlock(&pw->lock); + return rc; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_nextperiod); + +/** This function returns TRUE iff new periodic work was actually started. + * If this function returns FALSE, then no work was started + * (either because it was already started, or because of a failure). + */ +BOOL visor_periodic_work_start(struct periodic_work *pw) +{ + BOOL rc = FALSE; + + write_lock(&pw->lock); + if (pw->is_scheduled) { + rc = FALSE; + goto unlock; + } + if (pw->want_to_stop) { + rc = FALSE; + goto unlock; + } + INIT_DELAYED_WORK(&pw->work, &periodic_work_func); + if (queue_delayed_work(pw->workqueue, &pw->work, + pw->jiffy_interval) < 0) { + rc = FALSE; + goto unlock; + } + pw->is_scheduled = TRUE; + rc = TRUE; +unlock: + write_unlock(&pw->lock); + return rc; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_start); + +/** This function returns TRUE iff your call actually stopped the periodic + * work. + * + * -- PAY ATTENTION... this is important -- + * + * NO NO #1 + * + * Do NOT call this function from some function that is running on the + * same workqueue as the work you are trying to stop might be running + * on! If you violate this rule, visor_periodic_work_stop() MIGHT work, + * but it also MIGHT get hung up in an infinite loop saying + * "waiting for delayed work...". This will happen if the delayed work + * you are trying to cancel has been put in the workqueue list, but can't + * run yet because we are running that same workqueue thread right now. + * + * Bottom line: If you need to call visor_periodic_work_stop() from a + * workitem, be sure the workitem is on a DIFFERENT workqueue than the + * workitem that you are trying to cancel. + * + * If I could figure out some way to check for this "no no" condition in + * the code, I would. It would have saved me the trouble of writing this + * long comment. And also, don't think this is some "theoretical" race + * condition. It is REAL, as I have spent the day chasing it. + * + * NO NO #2 + * + * Take close note of the locks that you own when you call this function. + * You must NOT own any locks that are needed by the periodic work + * function that is currently installed. If you DO, a deadlock may result, + * because stopping the periodic work often involves waiting for the last + * iteration of the periodic work function to complete. Again, if you hit + * this deadlock, you will get hung up in an infinite loop saying + * "waiting for delayed work...". + */ +BOOL visor_periodic_work_stop(struct periodic_work *pw) +{ + BOOL stopped_something = FALSE; + + write_lock(&pw->lock); + stopped_something = pw->is_scheduled && (!pw->want_to_stop); + while (pw->is_scheduled) { + pw->want_to_stop = TRUE; + if (cancel_delayed_work(&pw->work)) { + /* We get here if the delayed work was pending as + * delayed work, but was NOT run. + */ + WARN_ON(!pw->is_scheduled); + pw->is_scheduled = FALSE; + } else { + /* If we get here, either the delayed work: + * - was run, OR, + * - is running RIGHT NOW on another processor, OR, + * - wasn't even scheduled (there is a miniscule + * timing window where this could be the case) + * flush_workqueue() would make sure it is finished + * executing, but that still isn't very useful, which + * explains the loop... + */ + } + if (pw->is_scheduled) { + write_unlock(&pw->lock); + SLEEPJIFFIES(10); + write_lock(&pw->lock); + } else { + pw->want_to_stop = FALSE; + } + } + write_unlock(&pw->lock); + return stopped_something; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_stop); diff --git a/kernel/drivers/staging/unisys/visorutil/visorkmodutils.c b/kernel/drivers/staging/unisys/visorutil/visorkmodutils.c new file mode 100644 index 000000000..62f0f7046 --- /dev/null +++ b/kernel/drivers/staging/unisys/visorutil/visorkmodutils.c @@ -0,0 +1,71 @@ +/* timskmodutils.c + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * 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 of the License, 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +#include "timskmod.h" + +#define MYDRVNAME "timskmodutils" + +/* s-Par uses the Intel processor's VT-X features to separate groups of + * processors into partitions. The firmware sets the hypervisor bit and + * reports an ID in the HV capabilities leaf so that the partition's OS + * knows s-Par is present and managing the processors. + */ + +#define UNISYS_SPAR_LEAF_ID 0x40000000 + +/* The s-Par leaf ID returns "UnisysSpar64" encoded across ebx, ecx, edx */ +#define UNISYS_SPAR_ID_EBX 0x73696e55 +#define UNISYS_SPAR_ID_ECX 0x70537379 +#define UNISYS_SPAR_ID_EDX 0x34367261 + +int unisys_spar_platform; +EXPORT_SYMBOL_GPL(unisys_spar_platform); + +static __init uint32_t visorutil_spar_detect(void) +{ + unsigned int eax, ebx, ecx, edx; + + if (cpu_has_hypervisor) { + /* check the ID */ + cpuid(UNISYS_SPAR_LEAF_ID, &eax, &ebx, &ecx, &edx); + return (ebx == UNISYS_SPAR_ID_EBX) && + (ecx == UNISYS_SPAR_ID_ECX) && + (edx == UNISYS_SPAR_ID_EDX); + } else { + return 0; + } +} + +static __init int visorutil_mod_init(void) +{ + if (visorutil_spar_detect()) { + unisys_spar_platform = TRUE; + return 0; + } else { + return -ENODEV; + } +} + +static __exit void +visorutil_mod_exit(void) +{ +} + +module_init(visorutil_mod_init); +module_exit(visorutil_mod_exit); + +MODULE_LICENSE("GPL"); -- cgit 1.2.3-korg