/* * libhugetlbfs - Easy use of Linux hugepages * Copyright (C) 2008 Adam Litke, IBM Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define _GNU_SOURCE /* For strchrnul */ #include #include #include #include #include #include #include #include "kernel-features.h" #include "hugetlbfs.h" #include "libhugetlbfs_privutils.h" #include "libhugetlbfs_internal.h" #include "libhugetlbfs_debug.h" static struct kernel_version running_kernel_version; /* This mask should always be 32 bits, regardless of the platform word size */ static unsigned int feature_mask; static struct feature kernel_features[] = { [HUGETLB_FEATURE_PRIVATE_RESV] = { .name = "private_reservations", .required_version = "2.6.27-rc1", }, [HUGETLB_FEATURE_SAFE_NORESERVE] = { .name = "noreserve_safe", .required_version = "2.6.34", }, [HUGETLB_FEATURE_MAP_HUGETLB] = { .name = "map_hugetlb", .required_version = "2.6.32", } }; static void debug_kernel_version(void) { struct kernel_version *ver = &running_kernel_version; INFO("Parsed kernel version: [%u] . [%u] . [%u] ", ver->major, ver->minor, ver->release); if (ver->post) INFO_CONT(" [post-release: %u]\n", ver->post); else if (ver->pre) INFO_CONT(" [pre-release: %u]\n", ver->pre); else INFO_CONT("\n"); } static int str_to_ver(const char *str, struct kernel_version *ver) { char *start; char *end; /* Clear out version struct */ ver->major = ver->minor = ver->release = ver->post = ver->pre = 0; /* * The kernel always starts x.y.z * * Note: strtol is used in place of sscanf because when heap override is * used this step happens before the _morecore replacement and sscanf * does an internal heap allocation. This mean that the next allocation * from the heap would be on small pages until the first block allocated * by _morecore is exhausted */ errno = 0; ver->major = strtol(str, &end, 10); if (!ver->major && errno == EINVAL) { ERROR("Unable to determine base kernel version: %s\n", strerror(errno)); return -1; } start = end + 1; errno = 0; ver->minor = strtol(start, &end, 10); if (!ver->minor && errno == EINVAL) { ERROR("Unable to determine base kernel version: %s\n", strerror(errno)); return -1; } start = end + 1; errno = 0; ver->release = strtol(start, &end, 10); if (!ver->release && errno == EINVAL) { ERROR("Unable to determine base kernel version: %s\n", strerror(errno)); return -1; } /* Try to match a post/stable version */ start = end + 1; if (*end == '.') { ver->post = strtol(start, &end, 10); if (!ver->post && errno == EINVAL) return 0; } /* Try to match a preN/rcN version */ start = end + 1; if (*end == '-') { if (*start == 'r' && *(start + 1) == 'c') start += 2; else if (*start == 'p' && *(start + 1) == 'r' && *(start + 2) == 'e') start += 3; else { /* * For now we ignore any extraversions besides * pre and rc versions and treat them as equal * to the base version. */ return 0; } ver->pre = strtol(start, &end, 10); } return 0; } static int int_cmp(int a, int b) { if (a < b) return -1; if (a > b) return 1; else return 0; } /* * Pre-release kernels have the following compare rules: * X.Y.(Z - 1) < X.Y.Z-rcN < X.Y.X * This order can be enforced by simply decrementing the release (for * comparison purposes) when there is a pre/rc modifier in effect. */ static int ver_cmp_release(struct kernel_version *ver) { if (ver->pre) return ver->release - 1; else return ver->release; } static int ver_cmp(struct kernel_version *a, struct kernel_version *b) { int ret, a_release, b_release; if ((ret = int_cmp(a->major, b->major)) != 0) return ret; if ((ret = int_cmp(a->minor, b->minor)) != 0) return ret; a_release = ver_cmp_release(a); b_release = ver_cmp_release(b); if ((ret = int_cmp(a_release, b_release)) != 0) return ret; if ((ret = int_cmp(a->post, b->post)) != 0) return ret; if ((ret = int_cmp(a->pre, b->pre)) != 0) return ret; /* We ignore forks (such as -mm and -mjb) */ return 0; } int test_compare_kver(const char *a, const char *b) { struct kernel_version ka, kb; if (str_to_ver(a, &ka) < 0) return -EINVAL; if (str_to_ver(b, &kb) < 0) return -EINVAL; return ver_cmp(&ka, &kb); } int hugetlbfs_test_feature(int feature_code) { if (feature_code >= HUGETLB_FEATURE_NR) { ERROR("hugetlbfs_test_feature: invalid feature code\n"); return -EINVAL; } return feature_mask & (1 << feature_code); } static void print_valid_features(void) { int i; ERROR("HUGETLB_FEATURES=\"[,] ...\"\n"); ERROR_CONT("Valid features:\n"); for (i = 0; i < HUGETLB_FEATURE_NR; i++) ERROR_CONT("\t%s, no_%s\n", kernel_features[i].name, kernel_features[i].name); } static int check_features_env_valid(const char *env) { const char *pos = env; int i; while (pos && *pos != '\0') { int match = 0; char *next; if (*pos == ',') pos++; next = strchrnul(pos, ','); if (strncmp(pos, "no_", 3) == 0) pos += 3; for (i = 0; i < HUGETLB_FEATURE_NR; i++) { char *name = kernel_features[i].name; if (strncmp(pos, name, next - pos) == 0) { match = 1; break; } } if (!match) { print_valid_features(); return -1; } pos = next; } return 0; } void setup_features() { struct utsname u; int i; if (uname(&u)) { ERROR("Getting kernel version failed: %s\n", strerror(errno)); return; } str_to_ver(u.release, &running_kernel_version); debug_kernel_version(); /* Check if the user has overrided any features */ if (__hugetlb_opts.features && check_features_env_valid(__hugetlb_opts.features) == -1) { ERROR("HUGETLB_FEATURES was invalid -- ignoring.\n"); __hugetlb_opts.features = NULL; } for (i = 0; i < HUGETLB_FEATURE_NR; i++) { struct kernel_version ver; char *name = kernel_features[i].name; char *pos; str_to_ver(kernel_features[i].required_version, &ver); /* Has the user overridden feature detection? */ if (__hugetlb_opts.features && (pos = strstr(__hugetlb_opts.features, name))) { INFO("Overriding feature %s: ", name); /* If feature is preceeded by 'no_' then turn it off */ if (((pos - 3) >= __hugetlb_opts.features) && !strncmp(pos - 3, "no_", 3)) INFO_CONT("no\n"); else { INFO_CONT("yes\n"); feature_mask |= (1UL << i); } continue; } /* Is the running kernel version newer? */ if (ver_cmp(&running_kernel_version, &ver) >= 0) { INFO("Feature %s is present in this kernel\n", kernel_features[i].name); feature_mask |= (1UL << i); } } }