[PATCH v5 12/17] target-s390x: Add S390 CPU class initialization routines

From: Michael Mueller
Date: Mon Apr 13 2015 - 09:58:17 EST


This patch provides routines to dynamically update the previously defined
S390 CPU classes in the current host context. The main function performing
this process is s390_setup_cpu_classes(). It takes the current host context
and a facility list mask as parameter to setup the classes accordingly. It
basically performs the following sub-tasks:

- Update of CPU classes with accelerator specific host and QEMU properties
- Mark adequate CPU class as default CPU class to be used for CPU model 'host'
- Invalidate CPU classes not supported by this hosting machine
- Define machine type aliases to latest GA number of a processor model
- Define aliases for common CPU model names
- Set CPU model alias 'host' to default CPU class

Forthermore the patch provides the following routines:

- cpu_desc_avail(), s390 specific stub indicating that list_cpus() can run
- s390_setup_cpu_aliases(), adds cu model aliases
- s390_cpu_classes_initialized(), test if CPU classes have been initialized
- s390_fac_list_mask_by_machine(), returns facility list mask by machine
- s390_current_fac_list_mask(), returns facility list mask of current machine

Signed-off-by: Michael Mueller <mimu@xxxxxxxxxxxxxxxxxx>
---
target-s390x/cpu-models.c | 500 ++++++++++++++++++++++++++++++++++++++++++++++
target-s390x/cpu-models.h | 29 +++
target-s390x/cpu.c | 17 +-
target-s390x/kvm.c | 5 +-
4 files changed, 549 insertions(+), 2 deletions(-)

diff --git a/target-s390x/cpu-models.c b/target-s390x/cpu-models.c
index 28251f9..07039c0 100644
--- a/target-s390x/cpu-models.c
+++ b/target-s390x/cpu-models.c
@@ -13,6 +13,11 @@
#include "qemu-common.h"
#include "cpu-models.h"
#include "gen-facilities.h"
+#include "qemu/error-report.h"
+#ifndef CONFIG_USER_ONLY
+#include "exec/cpu-common.h"
+#include "hw/boards.h"
+#endif

#define S390_PROC_DEF(_name, _cpu_id, _desc) \
static void \
@@ -88,8 +93,41 @@ S390_PROC_DEF("2827-ga2", CPU_S390_2827_GA2, "IBM zEnterprise EC12 GA2")
S390_PROC_DEF("2828-ga1", CPU_S390_2828_GA1, "IBM zEnterprise BC12 GA1")
S390_PROC_DEF("2964-ga1", CPU_S390_2964_GA1, "IBM z13 GA1")

+/* some types for calls to g_list_foreach() with parameters */
+typedef struct ParmBoolShortShort {
+ bool valid;
+ unsigned short type;
+ union {
+ unsigned short class;
+ unsigned short gen;
+ unsigned short ga;
+ };
+} ParmBoolShortShort;
+
+typedef struct ParmAddrAddrModeMask {
+ S390MachineProps *prop;
+ S390CPUClass *host_cc;
+ S390AccelMode mode;
+ uint64_t *mask;
+} ParmAddrAddrModeMask;
+
GSList *s390_cpu_aliases;

+/* compare order of two cpu classes for ascending sort */
+gint s390_cpu_class_asc_order_compare(gconstpointer a, gconstpointer b)
+{
+ S390CPUClass *cc_a = S390_CPU_CLASS((ObjectClass *) a);
+ S390CPUClass *cc_b = S390_CPU_CLASS((ObjectClass *) b);
+
+ if (cc_a->mach.order < cc_b->mach.order) {
+ return -1;
+ }
+ if (cc_a->mach.order > cc_b->mach.order) {
+ return 1;
+ }
+ return 0;
+}
+
static gint s390_cpu_compare_name(gconstpointer a, gconstpointer b)
{
char *aname = strdup_cpu_name((S390CPUClass *) a);
@@ -170,3 +208,465 @@ int set_s390_cpu_alias(const char *name, const char *model)
return 0;
}

+/* return machine class for specific machine type */
+static void s390_machine_class_test_cpu_class(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmBoolShortShort *parm = user_data;
+
+ if (parm->valid || is_cpu_class_none(cc) || parm->type != cc->proc.type) {
+ return;
+ }
+
+ parm->class = cc->mach.class;
+ parm->valid = true;
+}
+
+/* return machine class by machine type */
+static unsigned short machine_class(unsigned short type, void *user_data)
+{
+ GSList *list = object_class_get_list(TYPE_S390_CPU, false);
+ ParmBoolShortShort parm_class, *parm = user_data;
+
+ if (parm->type != type) {
+ parm->class = 0;
+ }
+ if (!parm->class) {
+ parm_class.type = type;
+ parm_class.class = 0;
+ parm_class.valid = false;
+ g_slist_foreach(list, (GFunc) s390_machine_class_test_cpu_class,
+ &parm_class);
+ g_slist_free(list);
+ if (parm_class.valid) {
+ parm->class = parm_class.class;
+ }
+ }
+ parm->type = type;
+
+ return parm->class;
+}
+
+/* return CMOS generation for specific machine type */
+static void s390_machine_class_test_cpu_gen(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmBoolShortShort *parm = user_data;
+
+ if (parm->valid) {
+ return;
+ }
+
+ if (parm->type == cc->proc.type) {
+ parm->gen = cc->proc.gen;
+ parm->valid = true;
+ }
+}
+
+/* return CMOS generation by machine type */
+static uint16_t machine_gen(unsigned short type)
+{
+ GSList *list = object_class_get_list(TYPE_S390_CPU, false);
+ ParmBoolShortShort parm;
+
+ parm.type = type;
+ parm.gen = 0;
+ parm.valid = false;
+ g_slist_foreach(list, (GFunc) s390_machine_class_test_cpu_gen, &parm);
+ g_slist_free(list);
+
+ return parm.gen;
+}
+
+/* mark cpu class, used in host cpu model case */
+static void s390_mark_host_cpu_class(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmAddrAddrModeMask *parm = user_data;
+ ParmBoolShortShort parm_tc;
+
+ if (!cc->is_active[parm->mode]) {
+ return;
+ }
+
+ parm_tc.type = 0;
+ parm_tc.class = 0;
+ if (cc->mach.class != machine_class(cpuid_type(parm->prop->cpuid),
+ &parm_tc)) {
+ /* sort out machines that differ from host machine class */
+ return;
+ }
+ if (!parm->host_cc) {
+ /* use first matching machine type */
+ cc->is_host[parm->mode] = true;
+ parm->host_cc = cc;
+ return;
+ }
+ if (cc->proc.gen > machine_gen(cpuid_type(parm->prop->cpuid))) {
+ /* sort out CMOS generations later than hosts generation */
+ cc->is_active[parm->mode] = false;
+ return;
+ }
+ if (cc->mach.order > parm->host_cc->mach.order) {
+ /* select later machine as host */
+ parm->host_cc->is_host[parm->mode] = false;
+ cc->is_host[parm->mode] = true;
+ parm->host_cc = cc;
+ }
+}
+
+/* update a specific cpu model class with host retrieved configuration */
+static void s390_update_cpu_class(gpointer data, gpointer user_data)
+{
+ ObjectClass *oc = data;
+ ParmAddrAddrModeMask *parm = user_data;
+ S390CPUClass *cc = S390_CPU_CLASS(oc);
+ uint64_t nbits = FAC_LIST_CPU_S390_SIZE_UINT1;
+ static uint64_t fac_list[FAC_LIST_CPU_S390_SIZE_UINT64];
+
+ cc->is_host[parm->mode] = false;
+
+ if (is_cpu_class_none(cc)) {
+ return;
+ }
+
+ /* Set processor identifier */
+ cc->proc.id = cpuid_id(parm->prop->cpuid);
+
+ /*
+ * Define model specific IBC value in current host context.
+ * IBC was introduced with CMOS version 10 i.e. type 2097.
+ * For older CPUs it is assumed to be 0x000. The BC system
+ * has always the same IBC version as the previous EC system.
+ * If the host supports IBC but not the requested type, it
+ * will be set to the oldest supported value.
+ */
+ if (has_ibc(parm->prop->ibc)) {
+ if (cc->proc.gen >= S390_CMOS_G10) {
+ cc->proc.ibc = ((cc->proc.gen - S390_CMOS_G10) << 4);
+ cc->proc.ibc += cc->mach.ga;
+ if (cc->mach.class == S390_BC) {
+ cc->proc.ibc++;
+ }
+ if (cc->proc.ibc < oldest_ibc(parm->prop->ibc)) {
+ cc->proc.ibc = oldest_ibc(parm->prop->ibc);
+ }
+ if (cc->proc.ibc > newest_ibc(parm->prop->ibc)) {
+ cc->proc.ibc = newest_ibc(parm->prop->ibc);
+ }
+ } else {
+ cc->proc.ibc = oldest_ibc(parm->prop->ibc);
+ }
+ }
+
+ /*
+ * Processor generation and GA level specific facility properties:
+ *
+ * - cc->fac_list (RFL):
+ * resulting facility list to be requested for guest cpus
+ * - cc->proc.fac_list (PFL):
+ * facility list defined per processor generation and GA level
+ *
+ * Machine specific facility properties reported by the host:
+ *
+ * - parm->prop->fac_list (MFL):
+ * host specifc facility list, might be reduced by some facilities
+ * in case the host is backed by z/VM and not a LPAR
+ * - parm->prop->fac_list_mask (MFM):
+ * host specific facility list mask containing facilities
+ *
+ * QEMU defined properties:
+ *
+ * - qemu_s390_fac_list_mask (QFM):
+ * locally defined facilities, they are added to the set of
+ * facilities requested for a guest vcpu. They are visible in
+ * the guest and require qemu side instruction handling
+ *
+ * The calculation for the vcpu specific facility list (RFL) from the
+ * above defined lists/masks works as follows:
+ *
+ * RFL = PFL & (QFM | MFM)
+ *
+ * Set resulting/desired facilities of given cpu class
+ */
+ if (parm->mask) {
+ bitmap_or(cc->fac_list[parm->mode], parm->mask,
+ parm->prop->fac_mask, nbits);
+ bitmap_and(cc->fac_list[parm->mode], cc->fac_list[parm->mode],
+ cc->proc.fac_list, nbits);
+ } else {
+ bitmap_and(cc->fac_list[parm->mode], parm->prop->fac_mask,
+ cc->proc.fac_list, nbits);
+ }
+
+ /*
+ * Finally, mark the cpu class active if all resulting/desired
+ * facilities are offered by the host.
+ * (RFL & MFL) != RFL
+ */
+ bitmap_and(fac_list, cc->fac_list[parm->mode], parm->prop->fac_list, nbits);
+ if (bitmap_equal(fac_list, cc->fac_list[parm->mode], nbits)) {
+ cc->is_active[parm->mode] = true;
+ }
+}
+
+/* cpu models newer than the hosting machine are not supported */
+static void s390_disable_not_supported_cpu_class(gpointer data,
+ gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmAddrAddrModeMask *parm = user_data;
+
+ if (!cc->is_active[parm->mode]) {
+ return;
+ }
+ if (parm->host_cc) {
+ if (cc->proc.gen < parm->host_cc->proc.gen) {
+ return;
+ }
+ if (cc->proc.gen == parm->host_cc->proc.gen) {
+ if (cc->mach.class == parm->host_cc->mach.class &&
+ cc->mach.ga <= parm->host_cc->mach.ga) {
+ return;
+ }
+ if (cc->mach.class == S390_EC &&
+ cc->mach.ga > parm->host_cc->mach.ga) {
+ return;
+ }
+ if (cc->mach.class == S390_BC &&
+ cc->mach.ga < parm->host_cc->mach.ga) {
+ return;
+ }
+ }
+ }
+ cc->is_active[parm->mode] = false;
+}
+
+static void set_s390_cpu_alias_by_type_ga(unsigned short type,
+ unsigned short ga)
+{
+ set_s390_cpu_alias(g_strdup_printf("%04x", type),
+ g_strdup_printf("%04x-ga%u", type, ga));
+}
+
+/* set cpu model type alias to newest ga release */
+static void s390_set_ga_alias(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmBoolShortShort *parm = user_data;
+
+ if (!cc->is_active[ACCEL_CURRENT]) {
+ return;
+ }
+ if (!parm->type) {
+ parm->type = cc->proc.type;
+ }
+ if (cc->proc.type == parm->type) {
+ parm->ga = cc->mach.ga;
+ return;
+ }
+ set_s390_cpu_alias_by_type_ga(parm->type, parm->ga);
+ parm->type = cc->proc.type;
+ parm->ga = cc->mach.ga;
+}
+
+/* set cpu model alias host to cpu class marked is host cpu class */
+static void s390_set_host_alias(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+
+ if (cc->is_active[ACCEL_CURRENT] && cc->is_host[ACCEL_CURRENT]) {
+ set_s390_cpu_alias("host", g_strdup_printf("%04x-ga%u",
+ cc->proc.type,
+ cc->mach.ga));
+ }
+}
+
+/**
+ * s390_setup_cpu_classes:
+ * @mode: the accelerator mode
+ * @prop: the machine property structure's address
+ *
+ * This function validates the defined cpu classes against the given
+ * machine properties @prop. Only cpu classes that are runnable on the
+ * current host will be set active. In addition the corresponding
+ * cpuid, ibc value and the active set of facilities will be
+ * initialized. Depending on @mode, the function porforms operations
+ * on the current or the temporary accelerator properies.
+ *
+ * Since: 2.4
+ */
+void s390_setup_cpu_classes(S390AccelMode mode, S390MachineProps *prop,
+ uint64_t *fac_list_mask)
+{
+ GSList *list;
+ ParmAddrAddrModeMask parm = {
+ .mode = mode,
+ .prop = prop,
+ .mask = fac_list_mask,
+ .host_cc = NULL,
+ };
+
+ list = object_class_get_list(TYPE_S390_CPU, false);
+ list = g_slist_sort(list, s390_cpu_class_asc_order_compare);
+
+ g_slist_foreach(list, (GFunc) s390_update_cpu_class, (gpointer) &parm);
+ g_slist_foreach(list, (GFunc) s390_mark_host_cpu_class, (gpointer) &parm);
+ g_slist_foreach(list, (GFunc) s390_disable_not_supported_cpu_class, &parm);
+
+ g_slist_free(list);
+}
+
+/**
+ * s390_setup_cpu_aliases:
+ *
+ * This function addes cpu model aliases that will allow to specify common
+ * model names in cunjunction with the -cpu command line parameter.
+ * There will be aliases for cpu types, pointing to the respective newest
+ * ga of a cpu type, aliases like z-something which are widely known and
+ * a the alias host pointing to the cpu type representing the current hosting
+ * mahine.
+ *
+ * Since: 2.4
+ */
+void s390_setup_cpu_aliases(void)
+{
+ GSList *list;
+ ParmBoolShortShort parm = { .type = 0, .ga = 0 };
+
+ list = object_class_get_list(TYPE_S390_CPU, false);
+ list = g_slist_sort(list, s390_cpu_class_asc_order_compare);
+
+ g_slist_foreach(list, (GFunc) s390_set_ga_alias, &parm);
+ set_s390_cpu_alias_by_type_ga(parm.type, parm.ga);
+
+ set_s390_cpu_alias("z900", "2064");
+ set_s390_cpu_alias("z800", "2066");
+ set_s390_cpu_alias("z990", "2084");
+ set_s390_cpu_alias("z890", "2086");
+ set_s390_cpu_alias("z9-109", "2094-ga1");
+ set_s390_cpu_alias("z9", "2094");
+ set_s390_cpu_alias("z9-ec", "2094");
+ set_s390_cpu_alias("z9-bc", "2096");
+ set_s390_cpu_alias("z10", "2097");
+ set_s390_cpu_alias("z10-ec", "2097");
+ set_s390_cpu_alias("z10-bc", "2098");
+ set_s390_cpu_alias("z196", "2817");
+ set_s390_cpu_alias("z114", "2818");
+ set_s390_cpu_alias("zEC12", "2827");
+ set_s390_cpu_alias("zBC12", "2828");
+ set_s390_cpu_alias("z13", "2964");
+
+ g_slist_foreach(list, (GFunc) s390_set_host_alias, &parm);
+
+ g_slist_free(list);
+}
+
+/* list all supported cpu models and alias names */
+void s390_cpu_list_entry(gpointer data, gpointer user_data)
+{
+ ObjectClass *alias_oc, *oc = data;
+ CPUListState *s = user_data;
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ S390CPUClass *cc = S390_CPU_CLASS(oc);
+ S390CPUAlias *alias;
+ GSList *item;
+ char *name;
+
+ if (!kvm_enabled()) {
+ return;
+ }
+ if (!cc->is_active[ACCEL_CURRENT]) {
+ return;
+ }
+ name = strdup_cpu_name(cc);
+ (*s->cpu_fprintf)(s->file, "s390 %-10s %s\n",
+ name, dc->desc ? dc->desc : "");
+
+ for (item = s390_cpu_aliases; item != NULL; item = item->next) {
+ alias = (S390CPUAlias *) item->data;
+ alias_oc = s390_cpu_class_by_name(alias->model);
+ if (alias_oc != oc) {
+ continue;
+ }
+ (*s->cpu_fprintf)(s->file, "s390 %-10s (alias for %s)\n",
+ alias->name, name);
+ }
+
+ g_free(name);
+}
+
+/**
+ * s390_cpu_classes_initialized:
+ *
+ * This function indicates if the all cpu classes and their properties
+ * have been initialized.
+ *
+ * Returns: a boolean value.
+ *
+ * Since: 2.4
+ */
+bool s390_cpu_classes_initialized(void)
+{
+ if (kvm_enabled()) {
+ return kvm_s390_cpu_classes_initialized();
+ }
+ return false;
+}
+
+bool cpu_desc_avail(void)
+{
+ return s390_cpu_classes_initialized();
+}
+
+/**
+ * s390_fac_list_mask_by_machine:
+ * @name: machine name
+ *
+ * This function returns the address of a facility list mask to
+ * be used in cunjunction with the specified machine type name
+ * or alias.
+ *
+ * Returns: The address of the facility list mask or %NULL in case
+ * @name is not a valid machine type name or alias
+ *
+ * Since: 2.4
+ */
+#ifndef CONFIG_USER_ONLY
+uint64_t *s390_fac_list_mask_by_machine(const char *name)
+{
+ uint64_t *mask = NULL;
+ GSList *machine, *mlist = object_class_get_list(TYPE_MACHINE, false);
+ MachineClass *mc;
+
+ for (machine = mlist; machine; machine = machine->next) {
+ mc = machine->data;
+ if (!strcmp(mc->name, name) ||
+ (mc->alias && !strcmp(mc->alias, name))) {
+ /* add cases as required */
+ mask = qemu_s390_fac_list_mask;
+ break;
+ }
+ }
+ return mask;
+}
+#endif
+
+/**
+ * s390_current_fac_list_mask:
+ *
+ * This function returns the address of a facility list mask of the
+ * currently active machine.
+ *
+ * Returns: The address of the facility list mask.
+ *
+ * Since: 2.4
+ */
+#ifndef CONFIG_USER_ONLY
+uint64_t *s390_current_fac_list_mask(void)
+{
+ MachineClass *mc = MACHINE_GET_CLASS(MACHINE(qdev_get_machine()));
+
+ return s390_fac_list_mask_by_machine(mc->name);
+}
+#endif
diff --git a/target-s390x/cpu-models.h b/target-s390x/cpu-models.h
index efa255e..00454d3 100644
--- a/target-s390x/cpu-models.h
+++ b/target-s390x/cpu-models.h
@@ -29,12 +29,32 @@
#define S390_DEF_ID 0xdecade
#define S390_DEF_TYPE 0x2064

+/* first s390 CMOS generation supporting IBC */
+#define S390_CMOS_G10 0xa
+
#define cpu_type(x) (((x) >> 0) & 0xffff)
#define cpu_order(x) (((x) >> 16) & 0xffff)
#define cpu_ga(x) (((x) >> 16) & 0xf)
#define cpu_class(x) (((x) >> 20) & 0x3)
#define cpu_generation(x) (((x) >> 24) & 0xff)

+#define cpuid_type(x) (((x) >> 16) & 0xffff)
+#define cpuid_id(x) (((x) >> 32) & 0xffffff)
+#define cpuid_ver(x) (((x) >> 56) & 0xff)
+
+#define type_cpuid(x) ((uint64_t)((x) & 0xffff) << 16)
+#define id_cpuid(x) ((uint64_t)((x) & 0xffffff) << 32)
+#define ver_cpuid(x) ((uint64_t)((x) & 0xff) << 56)
+
+#define oldest_ibc(x) (((uint32_t)(x) >> 16) & 0xfff)
+#define newest_ibc(x) ((uint32_t)(x) & 0xfff)
+#define has_ibc(x) (oldest_ibc(x) != 0)
+
+#define S390_DEF_CPUID \
+ (ver_cpuid(S390_DEF_VERSION) | \
+ id_cpuid(S390_DEF_ID) | \
+ type_cpuid(S390_DEF_TYPE))
+
/*
* S390 cpu aliases will be added dynamically
*/
@@ -60,6 +80,14 @@ typedef struct S390MachineProps {

ObjectClass *s390_cpu_class_by_name(const char *name);
int set_s390_cpu_alias(const char *name, const char *model);
+void s390_setup_cpu_classes(S390AccelMode mode, S390MachineProps *prop,
+ uint64_t *fac_list_mask);
+void s390_setup_cpu_aliases(void);
+gint s390_cpu_class_asc_order_compare(gconstpointer a, gconstpointer b);
+void s390_cpu_list_entry(gpointer data, gpointer user_data);
+bool s390_cpu_classes_initialized(void);
+uint64_t *s390_fac_list_mask_by_machine(const char *name);
+uint64_t *s390_current_fac_list_mask(void);

#ifdef CONFIG_KVM
int kvm_s390_get_processor_props(S390ProcessorProps *prop);
@@ -81,6 +109,7 @@ static inline bool kvm_s390_cpu_classes_initialized(void)
#endif

extern GSList *s390_cpu_aliases;
+extern uint64_t qemu_s390_fac_list_mask[];

/*
* bits 0-7 : CMOS generation
diff --git a/target-s390x/cpu.c b/target-s390x/cpu.c
index c081885..cbab627 100644
--- a/target-s390x/cpu.c
+++ b/target-s390x/cpu.c
@@ -41,7 +41,22 @@
void s390_cpu_list(FILE *f, fprintf_function cpu_fprintf)
{
#ifdef CONFIG_KVM
- (*cpu_fprintf)(f, "s390 %16s\n", "host");
+ CPUListState s = {
+ .file = f,
+ .cpu_fprintf = cpu_fprintf,
+ };
+ GSList *list;
+
+ if (kvm_enabled() && s390_cpu_classes_initialized()) {
+ list = object_class_get_list(TYPE_S390_CPU, false);
+ list = g_slist_sort(list, s390_cpu_class_asc_order_compare);
+ g_slist_foreach(list, s390_cpu_list_entry, &s);
+ g_slist_free(list);
+ } else {
+#endif
+ (*cpu_fprintf)(f, "s390 host\n");
+#ifdef CONFIG_KVM
+ }
#endif
}

diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c
index 42f01a8..784ea8c 100644
--- a/target-s390x/kvm.c
+++ b/target-s390x/kvm.c
@@ -293,7 +293,10 @@ static void kvm_setup_cpu_classes(KVMState *s)
S390MachineProps mach;

if (!kvm_s390_get_machine_props(s, &mach)) {
- cpu_classes_initialized = false;
+ s390_setup_cpu_classes(ACCEL_CURRENT, &mach,
+ s390_current_fac_list_mask());
+ s390_setup_cpu_aliases();
+ cpu_classes_initialized = true;
}
}

--
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/