/* * Copyright (C) 2001 Sistina Software (UK) Limited. * Copyright (C) 2004-2008 Red Hat, Inc. All rights reserved. * * This file is released under the GPL. */ #include "dm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define DM_MSG_PREFIX "table" #define MAX_DEPTH 16 #define NODE_SIZE L1_CACHE_BYTES #define KEYS_PER_NODE (NODE_SIZE / sizeof(sector_t)) #define CHILDREN_PER_NODE (KEYS_PER_NODE + 1) struct dm_table { struct mapped_device *md; unsigned type; /* btree table */ unsigned int depth; unsigned int counts[MAX_DEPTH]; /* in nodes */ sector_t *index[MAX_DEPTH]; unsigned int num_targets; unsigned int num_allocated; sector_t *highs; struct dm_target *targets; struct target_type *immutable_target_type; unsigned integrity_supported:1; unsigned singleton:1; /* * Indicates the rw permissions for the new logical * device. This should be a combination of FMODE_READ * and FMODE_WRITE. */ fmode_t mode; /* a list of devices used by this table */ struct list_head devices; /* events get handed up using this callback */ void (*event_fn)(void *); void *event_context; struct dm_md_mempools *mempools; struct list_head target_callbacks; }; /* * Similar to ceiling(log_size(n)) */ static unsigned int int_log(unsigned int n, unsigned int base) { int result = 0; while (n > 1) { n = dm_div_up(n, base); result++; } return result; } /* * Calculate the index of the child node of the n'th node k'th key. */ static inline unsigned int get_child(unsigned int n, unsigned int k) { return (n * CHILDREN_PER_NODE) + k; } /* * Return the n'th node of level l from table t. */ static inline sector_t *get_node(struct dm_table *t, unsigned int l, unsigned int n) { return t->index[l] + (n * KEYS_PER_NODE); } /* * Return the highest key that you could lookup from the n'th * node on level l of the btree. */ static sector_t high(struct dm_table *t, unsigned int l, unsigned int n) { for (; l < t->depth - 1; l++) n = get_child(n, CHILDREN_PER_NODE - 1); if (n >= t->counts[l]) return (sector_t) - 1; return get_node(t, l, n)[KEYS_PER_NODE - 1]; } /* * Fills in a level of the btree based on the highs of the level * below it. */ static int setup_btree_index(unsigned int l, struct dm_table *t) { unsigned int n, k; sector_t *node; for (n = 0U; n < t->counts[l]; n++) { node = get_node(t, l, n); for (k = 0U; k < KEYS_PER_NODE; k++) node[k] = high(t, l + 1, get_child(n, k)); } return 0; } void *dm_vcalloc(unsigned long nmemb, unsigned long elem_size) { unsigned long size; void *addr; /* * Check that we're not going to overflow. */ if (nmemb > (ULONG_MAX / elem_size)) return NULL; size = nmemb * elem_size; addr = vzalloc(size); return addr; } EXPORT_SYMBOL(dm_vcalloc); /* * highs, and targets are managed as dynamic arrays during a * table load. */ static int alloc_targets(struct dm_table *t, unsigned int num) { sector_t *n_highs; struct dm_target *n_targets; /* * Allocate both the target array and offset array at once. * Append an empty entry to catch sectors beyond the end of * the device. */ n_highs = (sector_t *) dm_vcalloc(num + 1, sizeof(struct dm_target) + sizeof(sector_t)); if (!n_highs) return -ENOMEM; n_targets = (struct dm_target *) (n_highs + num); memset(n_highs, -1, sizeof(*n_highs) * num); vfree(t->highs); t->num_allocated = num; t->highs = n_highs; t->targets = n_targets; return 0; } int dm_table_create(struct dm_table **result, fmode_t mode, unsigned num_targets, struct mapped_device *md) { struct dm_table *t = kzalloc(sizeof(*t), GFP_KERNEL); if (!t) return -ENOMEM; INIT_LIST_HEAD(&t->devices); INIT_LIST_HEAD(&t->target_callbacks); if (!num_targets) num_targets = KEYS_PER_NODE; num_targets = dm_round_up(num_targets, KEYS_PER_NODE); if (!num_targets) { kfree(t); return -ENOMEM; } if (alloc_targets(t, num_targets)) { kfree(t); return -ENOMEM; } t->mode = mode; t->md = md; *result = t; return 0; } static void free_devices(struct list_head *devices, struct mapped_device *md) { struct list_head *tmp, *next; list_for_each_safe(tmp, next, devices) { struct dm_dev_internal *dd = list_entry(tmp, struct dm_dev_internal, list); DMWARN("%s: dm_table_destroy: dm_put_device call missing for %s", dm_device_name(md), dd->dm_dev->name); dm_put_table_device(md, dd->dm_dev); kfree(dd); } } void dm_table_destroy(struct dm_table *t) { unsigned int i; if (!t) return; /* free the indexes */ if (t->depth >= 2) vfree(t->index[t->depth - 2]); /* free the targets */ for (i = 0; i < t->num_targets; i++) { struct dm_target *tgt = t->targets + i; if (tgt->type->dtr) tgt->type->dtr(tgt); dm_put_target_type(tgt->type); } vfree(t->highs); /* free the device list */ free_devices(&t->devices, t->md); dm_free_md_mempools(t->mempools); kfree(t); } /* * See if we've already got a device in the list. */ static struct dm_dev_internal *find_device(struct list_head *l, dev_t dev) { struct dm_dev_internal *dd; list_for_each_entry (dd, l, list) if (dd->dm_dev->bdev->bd_dev == dev) return dd; return NULL; } /* * If possible, this checks an area of a destination device is invalid. */ static int device_area_is_invalid(struct dm_target *ti, struct dm_dev *dev, sector_t start, sector_t len, void *data) { struct request_queue *q; struct queue_limits *limits = data; struct block_device *bdev = dev->bdev; sector_t dev_size = i_size_read(bdev->bd_inode) >> SECTOR_SHIFT; unsigned short logical_block_size_sectors = limits->logical_block_size >> SECTOR_SHIFT; char b[BDEVNAME_SIZE]; /* * Some devices exist without request functions, * such as loop devices not yet bound to backing files. * Forbid the use of such devices. */ q = bdev_get_queue(bdev); if (!q || !q->make_request_fn) { DMWARN("%s: %s is not yet initialised: " "start=%llu, len=%llu, dev_size=%llu", dm_device_name(ti->table->md), bdevname(bdev, b), (unsigned long long)start, (unsigned long long)len, (unsigned long long)dev_size); return 1; } if (!dev_size) return 0; if ((start >= dev_size) || (start + len > dev_size)) { DMWARN("%s: %s too small for target: " "start=%llu, len=%llu, dev_size=%llu", dm_device_name(ti->table->md), bdevname(bdev, b), (unsigned long long)start, (unsigned long long)len, (unsigned long long)dev_size); return 1; } if (logical_block_size_sectors <= 1) return 0; if (start & (logical_block_size_sectors - 1)) { DMWARN("%s: start=%llu not aligned to h/w " "logical block size %u of %s", dm_device_name(ti->table->md), (unsigned long long)start, limits->logical_block_size, bdevname(bdev, b)); return 1; } if (len & (logical_block_size_sectors - 1)) { DMWARN("%s: len=%llu not aligned to h/w " "logical block size %u of %s", dm_device_name(ti->table->md), (unsigned long long)len, limits->logical_block_size, bdevname(bdev, b)); return 1; } return 0; } /* * This upgrades the mode on an already open dm_dev, being * careful to leave things as they were if we fail to reopen the * device and not to touch the existing bdev field in case * it is accessed concurrently inside dm_table_any_congested(). */ static int upgrade_mode(struct dm_dev_internal *dd, fmode_t new_mode, struct mapped_device *md) { int r; struct dm_dev *old_dev, *new_dev; old_dev = dd->dm_dev; r = dm_get_table_device(md, dd->dm_dev->bdev->bd_dev, dd->dm_dev->mode | new_mode, &new_dev); if (r) return r; dd->dm_dev = new_dev; dm_put_table_device(md, old_dev); return 0; } /* * Add a device to the list, or just increment the usage count if * it's already present. */ int dm_get_device(struct dm_target *ti, const char *path, fmode_t mode, struct dm_dev **result) { int r; dev_t uninitialized_var(dev); struct dm_dev_internal *dd; struct dm_table *t = ti->table; struct block_device *bdev; BUG_ON(!t); /* convert the path to a device */ bdev = lookup_bdev(path); if (IS_ERR(bdev)) { dev = name_to_dev_t(path); if (!dev) return -ENODEV; } else { dev = bdev->bd_dev; bdput(bdev); } dd = find_device(&t->devices, dev); if (!dd) { dd = kmalloc(sizeof(*dd), GFP_KERNEL); if (!dd) return -ENOMEM; if ((r = dm_get_table_device(t->md, dev, mode, &dd->dm_dev))) { kfree(dd); return r; } atomic_set(&dd->count, 0); list_add(&dd->list, &t->devices); } else if (dd->dm_dev->mode != (mode | dd->dm_dev->mode)) { r = upgrade_mode(dd, mode, t->md); if (r) return r; } atomic_inc(&dd->count); *result = dd->dm_dev; return 0; } EXPORT_SYMBOL(dm_get_device); static int dm_set_device_limits(struct dm_target *ti, struct dm_dev *dev, sector_t start, sector_t len, void *data) { struct queue_limits *limits = data; struct block_device *bdev = dev->bdev; struct request_queue *q = bdev_get_queue(bdev); char b[BDEVNAME_SIZE]; if (unlikely(!q)) { DMWARN("%s: Cannot set limits for nonexistent device %s", dm_device_name(ti->table->md), bdevname(bdev, b)); return 0; } if (bdev_stack_limits(limits, bdev, start) < 0) DMWARN("%s: adding target device %s caused an alignment inconsistency: " "physical_block_size=%u, logical_block_size=%u, " "alignment_offset=%u, start=%llu", dm_device_name(ti->table->md), bdevname(bdev, b), q->limits.physical_block_size, q->limits.logical_block_size, q->limits.alignment_offset, (unsigned long long) start << SECTOR_SHIFT); return 0; } /* * Decrement a device's use count and remove it if necessary. */ void dm_put_device(struct dm_target *ti, struct dm_dev *d) { int found = 0; struct list_head *devices = &ti->table->devices; struct dm_dev_internal *dd; list_for_each_entry(dd, devices, list) { if (dd->dm_dev == d) { found = 1; break; } } if (!found) { DMWARN("%s: device %s not in table devices list", dm_device_name(ti->table->md), d->name); return; } if (atomic_dec_and_test(&dd->count)) { dm_put_table_device(ti->table->md, d); list_del(&dd->list); kfree(dd); } } EXPORT_SYMBOL(dm_put_device); /* * Checks to see if the target joins onto the end of the table. */ static int adjoin(struct dm_table *table, struct dm_target *ti) { struct dm_target *prev; if (!table->num_targets) return !ti->begin; prev = &table->targets[table->num_targets - 1]; return (ti->begin == (prev->begin + prev->len)); } /* * Used to dynamically allocate the arg array. * * We do first allocation with GFP_NOIO because dm-mpath and
# Copyright 2016-2017 Intel Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Various helper functions
"""

import os
import logging
import glob
import shutil
import re
from conf import settings as S

MAX_L4_FLOWS = 65536

#
# Support functions
#
# pylint: disable=too-many-branches
def settings_update_paths():
    """ Configure paths to OVS, DPDK and QEMU sources and binaries based on
        selected vswitch type and src/binary switch. Data are taken from
        PATHS dictionary and after their processing they are stored inside TOOLS.
        PATHS dictionary has specific section for 'vswitch', 'qemu' and 'dpdk'
        Following processing is done for every item:
            item 'type' - string, which defines the type of paths ('src' or 'bin') to be selected
                  for a given section:
                      'src' means, that VSPERF will use OVS, DPDK or QEMU built from sources
                          e.g. by execution of systems/build_base_machine.sh script during VSPERF
                          installation
                      'bin' means, that VSPERF will use OVS, DPDK or QEMU binaries installed
                          in the OS, e.g. via OS specific packaging system
            item 'path' - string with valid path; Its content is checked for existence, prefixed
                  with section name and stored into TOOLS for later use
                  e.g. TOOLS['dpdk_src'] or TOOLS['vswitch_src']
            item 'modules' - list of strings; Every value from given list is checked for '.ko'
                  suffix. In case it matches and it is not an absolute path to the module, then
                  module name is prefixed with 'path' defined for the same section
                  e.g. TOOLS['vswitch_modules'] = [
                      '/tmp/vsperf/src_vanilla/ovs/ovs/datapath/linux/openvswitch.ko']
            all other items - string - if given string is a relative path and item 'path'
                  is defined for a given section, then item content will be prefixed with
                  content of the 'path'. Otherwise tool name will be searched within
                  standard system directories. Also any OS filename wildcards will be
                  expanded to the real path. At the end of processing, every absolute
                  path is checked for its existence. In case that temporary path (i.e. path
                  with '_tmp' suffix) doesn't exist, then log will be written and vsperf will
                  continue. If any other path will not exist, then vsperf execution will
                  be terminated with runtime error.

        Note: In case that 'bin' type is set for DPDK, then TOOLS['dpdk_src'] will be set to
        the value of PATHS['dpdk']['src']['path']. The reason is, that VSPERF uses downloaded
        DPDK sources to copy DPDK and testpmd into the GUEST, where testpmd is built. In case,
        that DPDK sources are not available, then vsperf will continue with test execution,
        but testpmd can't be used as a guest loopback. This is useful in case, that other guest
        loopback applications (e.g. buildin) are used by CI jobs, etc.
    """
    # set dpdk and ovs paths according to VNF, VSWITCH and TRAFFICGEN selection
    paths = {}
    if S.getValue("mode") != 'trafficgen':
        # VSWITCH & (probably) VNF are needed
        vswitch_type = S.getValue('PATHS')['vswitch'][S.getValue('VSWITCH')]['type']
        paths['vswitch'] = S.getValue('PATHS')['vswitch'][S.getValue('VSWITCH')][vswitch_type]
        paths['dpdk'] = S.getValue('PATHS')['dpdk'][S.getValue('PATHS')['dpdk']['type']]
        paths['qemu'] = S.getValue('PATHS')['qemu'][S.getValue('PATHS')['qemu']['type']]
        paths['paths'] = {}
        paths['paths']['ovs_var_tmp'] = S.getValue('PATHS')['vswitch']['ovs_var_tmp']
        paths['paths']['ovs_etc_tmp'] = S.getValue('PATHS')['vswitch']['ovs_etc_tmp']

    if S.getValue("mode") != 'trafficgen-off':
        # TRAFFCIGEN is required
        if S.getValue('TRAFFICGEN') in S.getValue('PATHS')['trafficgen']:
            tmp_trafficgen = S.getValue('PATHS')['trafficgen'][S.getValue('TRAFFICGEN')]
            paths['trafficgen'] = tmp_trafficgen[tmp_trafficgen['type']]

    tools = {}
    # pylint: disable=too-many-nested-blocks
    for path_class in paths:
        for tool in paths[path_class]:
            tmp_tool = paths[path_class][tool]

            # store valid path of given class into tools dict
            if tool == 'path':
                if os.path.isdir(tmp_tool):
                    tools['{}_src'.format(path_class)] = tmp_tool
                    continue
                else:
                    raise RuntimeError('Path {} does not exist.'.format(tmp_tool))

            # store list of modules of given class into tools dict
            if tool == 'modules':
                tmp_modules = []
                for module in tmp_tool:
                    # add path to the .ko modules and check it for existence
                    if module.endswith('.ko') and not os.path.isabs(module):
                        module = os.path.join(paths[path_class]['path'], module)
                        if not os.path.exists(module):
                            raise RuntimeError('Cannot locate modlue {}'.format(module))

                    tmp_modules.append(module)

                tools['{}_modules'.format(path_class)] = tmp_modules
                continue

            # if path to the tool is relative, then 'path' will be prefixed
            # in case that 'path' is not defined, then tool will be searched
            # within standard system paths
            if not os.path.isabs(tmp_tool):
                if 'path' in paths[path_class]:
                    tmp_tool = os.path.join(paths[path_class]['path'], tmp_tool)
                elif shutil.which(tmp_tool):
                    tmp_tool = shutil.which(tmp_tool)
                else:
                    raise RuntimeError('Cannot locate tool {}'.format(tmp_tool))

            # expand OS wildcards in paths if needed
            if glob.has_magic(tmp_tool):
                tmp_glob = glob.glob(tmp_tool)
                if len(tmp_glob) == 0:
                    raise RuntimeError('Path to the {} is not valid: {}.'.format(tool, tmp_tool))
                elif len(tmp_glob) > 1:
                    raise RuntimeError('Path to the {} is ambiguous {}'.format