/*
* BSD LICENSE
*
* Copyright(c) 2014-2018 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* @brief Implementation of CAT related PQoS API
*
* CPUID and MSR operations are done on the 'local'/host system.
* Module operate directly on CAT registers.
*/
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "pqos.h"
#include "cap.h"
#include "allocation.h"
#include "os_allocation.h"
#include "machine.h"
#include "types.h"
#include "log.h"
/**
* ---------------------------------------
* Local macros
* ---------------------------------------
*/
/**
* Allocation & Monitoring association MSR register
* - bits [63..32] QE COS
* - bits [31..10] Reserved
* - bits [9..0] RMID
*/
#define PQOS_MSR_ASSOC 0xC8F
#define PQOS_MSR_ASSOC_QECOS_SHIFT 32
#define PQOS_MSR_ASSOC_QECOS_MASK 0xffffffff00000000ULL
/**
* Allocation class of service (COS) MSR registers
*/
#define PQOS_MSR_L3CA_MASK_START 0xC90
#define PQOS_MSR_L3CA_MASK_END 0xD0F
#define PQOS_MSR_L3CA_MASK_NUMOF \
(PQOS_MSR_L3CA_MASK_END - PQOS_MSR_L3CA_MASK_START + 1)
#define PQOS_MSR_L2CA_MASK_START 0xD10
#define PQOS_MSR_MBA_MASK_START 0xD50
#define PQOS_MSR_L3_QOS_CFG 0xC81 /**< L3 CAT config register */
#define PQOS_MSR_L3_QOS_CFG_CDP_EN 1ULL /**< L3 CDP enable bit */
#define PQOS_MSR_L2_QOS_CFG 0xC82 /**< L2 CAT config register */
#define PQOS_MSR_L2_QOS_CFG_CDP_EN 1ULL /**< L2 CDP enable bit */
/**
* MBA linear max value
*/
#define PQOS_MBA_LINEAR_MAX 100
/**
* ---------------------------------------
* Local data types
* ---------------------------------------
*/
/**
* ---------------------------------------
* Local data structures
* ---------------------------------------
*/
static const struct pqos_cap *m_cap = NULL;
static const struct pqos_cpuinfo *m_cpu = NULL;
static int m_interface = PQOS_INTER_MSR;
/**
* ---------------------------------------
* External data
* ---------------------------------------
*/
/**
* ---------------------------------------
* Internal functions
* ---------------------------------------
*/
/**
* @brief Gets highest COS id which could be used to configure set technologies
*
* @param [in] technology technologies bitmask to get highest common COS id for
* @param [out] hi_class_id highest common COS id
*
* @return Operation status
*/
static int
get_hi_cos_id(const unsigned technology,
unsigned *hi_class_id)
{
const int l2_req = ((technology & (1 << PQOS_CAP_TYPE_L2CA)) != 0);
const int l3_req = ((technology & (1 << PQOS_CAP_TYPE_L3CA)) != 0);
const int mba_req = ((technology & (1 << PQOS_CAP_TYPE_MBA)) != 0);
unsigned num_l2_cos = 0, num_l3_cos = 0, num_mba_cos = 0, num_cos = 0;
int ret;
ASSERT(l2_req || l3_req || mba_req);
if (hi_class_id == NULL)
return PQOS_RETVAL_PARAM;
ASSERT(m_cap != NULL);
if (l3_req) {
ret = pqos_l3ca_get_cos_num(m_cap, &num_l3_cos);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
if (num_l3_cos == 0)
return PQOS_RETVAL_ERROR;
num_cos = num_l3_cos;
}
if (l2_req) {
ret = pqos_l2ca_get_cos_num(m_cap, &num_l2_cos);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
if (num_l2_cos == 0)
return PQOS_RETVAL_ERROR;
if (num_cos == 0 || num_l2_cos < num_cos)
num_cos = num_l2_cos;
}
if (mba_req) {
ret = pqos_mba_get_cos_num(m_cap, &num_mba_cos);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
if (num_mba_cos == 0)
return PQOS_RETVAL_ERROR;
if (num_cos == 0 || num_mba_cos < num_cos)
num_cos = num_mba_cos;
}
*hi_class_id = num_cos - 1;
return PQOS_RETVAL_OK;
}
/**
* @brief Gets COS associated to \a lcore
*
* @param [in] lcore lcore to read COS association from
* @param [out] class_id associated COS
*
* @return Operation status
*/
static int
cos_assoc_get(const unsigned lcore, unsigned *class_id)
{
const uint32_t reg = PQOS_MSR_ASSOC;
uint64_t val = 0;
if (class_id == NULL)
return PQOS_RETVAL_PARAM;
if (msr_read(lcore, reg, &val) != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
val >>= PQOS_MSR_ASSOC_QECOS_SHIFT;
*class_id = (unsigned) val;
return PQOS_RETVAL_OK;
}
/**
* @brief Gets unused COS on a socket or L2 cluster
*
* The lowest acceptable COS is 1, as 0 is a default one
*
* @param [in] id socket or L2 cache ID to search for unused COS on
* @param [in] technology selection of allocation technologies
* @param [out] class_id unused COS
*
* @return Operation status
*/
static int
get_unused_cos(const unsigned id,
const unsigned technology,
unsigned *class_id)
{
const int l2_req = ((technology & (1 << PQOS_CAP_TYPE_L2CA)) != 0);
unsigned used_classes[PQOS_MAX_L3CA_COS];
unsigned i, cos;
unsigned hi_class_id;
int ret;
if (class_id == NULL)
return PQOS_RETVAL_PARAM;
/* obtain highest class id for all requested technologies */
ret = get_hi_cos_id(technology, &hi_class_id);
if (ret != PQOS_RETVAL_OK)
return ret;
memset(used_classes, 0, sizeof(used_classes));
/* Create a list of COS used on socket/L2 cluster */
for (i = 0; i < m_cpu->num_cores; i++) {
if (l2_req) {
/* L2 requested so looking in L2 cluster scope */
if (m_cpu->cores[i].l2_id != id)
continue;
} else {
/* L2 not requested so looking at socket scope */
if (m_cpu->cores[i].socket != id)
continue;
}
ret = cos_assoc_get(m_cpu->cores[i].lcore, &cos);
if (ret != PQOS_RETVAL_OK)
return ret;
if (cos > hi_class_id)
continue;
/* Mark as used */
used_classes[cos] = 1;
}
/* Find unused COS */
for (cos = hi_class_id; cos != 0; cos--) {
if (used_classes[cos] == 0) {
*class_id = cos;
return PQOS_RETVAL_OK;
}
}
return PQOS_RETVAL_RESOURCE;
}
/**
* =======================================
* =======================================
*
* initialize and shutdown
*
* =======================================
* =======================================
*/
int
pqos_alloc_init(const struct pqos_cpuinfo *cpu,
const struct pqos_cap *cap,
const struct pqos_config *cfg)
{
int ret = PQOS_RETVAL_OK;
m_cap = cap;
m_cpu = cpu;
if (cfg == NULL)
m_interface = PQOS_INTER_MSR;
else if (cfg->interface == PQOS_INTER_OS_RESCTRL_MON)
m_interface = PQOS_INTER_OS;
else
m_interface = cfg->interface;
#ifdef __linux__
if (m_interface == PQOS_INTER_OS)
ret = os_alloc_init(cpu, cap);
#endif
return ret;
}
int
pqos_alloc_fini(void)
{
int ret = PQOS_RETVAL_OK;
m_cap = NULL;
m_cpu = NULL;
#ifdef __linux__
if (m_interface == PQOS_INTER_OS)
ret = os_alloc_fini();
#endif
return ret;
}
/**
* =======================================
* L3 cache allocation
* =======================================
*/
int
hw_l3ca_set(const unsigned socket,
const unsigned num_ca,
const struct pqos_l3ca *ca)
{
int ret = PQOS_RETVAL_OK;
unsigned i = 0, count = 0, core = 0;
int cdp_enabled = 0;
ASSERT(ca != NULL);
ASSERT(num_ca != 0);
ASSERT(m_cap != NULL);
ret = pqos_l3ca_get_cos_num(m_cap, &count);
if (ret != PQOS_RETVAL_OK)
return ret; /**< perhaps no L3CA capability */
if (num_ca > count)
return PQOS_RETVAL_ERROR;
ret = pqos_l3ca_cdp_enabled(m_cap, NULL, &cdp_enabled);
if (ret != PQOS_RETVAL_OK)
return ret;
ASSERT(m_cpu != NULL);
ret = pqos_cpu_get_one_core(m_cpu, socket, &core);
if (ret != PQOS_RETVAL_OK)
return ret;
if (cdp_enabled) {
for (i = 0; i < num_ca; i++) {
uint32_t reg =
(ca[i].class_id*2) + PQOS_MSR_L3CA_MASK_START;
int retval = MACHINE_RETVAL_OK;
uint64_t cmask = 0, dmask = 0;
if (ca[i].cdp) {
dmask = ca[i].u.s.data_mask;
cmask = ca[i].u.s.code_mask;
} else {
dmask = ca[i].u.ways_mask;
cmask = ca[i].u.ways_mask;
}
retval = msr_write(core, reg, dmask);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
retval = msr_write(core, reg+1, cmask);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
}
} else {
for (i = 0; i < num_ca; i++) {
uint32_t reg =
ca[i].class_id + PQOS_MSR_L3CA_MASK_START;
uint64_t val = ca[i].u.ways_mask;
int retval = MACHINE_RETVAL_OK;
if (ca[i].cdp) {
LOG_ERROR("Attempting to set CDP COS "
"while L3 CDP is disabled!\n");
return PQOS_RETVAL_ERROR;
}
retval = msr_write(core, reg, val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
}
}
return ret;
}
int
hw_l3ca_get(const unsigned socket,
const unsigned max_num_ca,
unsigned *num_ca,
struct pqos_l3ca *ca)
{
int ret = PQOS_RETVAL_OK;
unsigned i = 0, count = 0, core = 0;
uint32_t reg = 0;
uint64_t val = 0;
int retval = MACHINE_RETVAL_OK;
int cdp_enabled = 0;
ASSERT(num_ca != NULL);
ASSERT(ca != NULL);
ASSERT(max_num_ca != 0);
ASSERT(m_cap != NULL);
ret = pqos_l3ca_get_cos_num(m_cap, &count);
if (ret != PQOS_RETVAL_OK)
return ret; /**< perhaps no L3CA capability */
ret = pqos_l3ca_cdp_enabled(m_cap, NULL, &cdp_enabled);
if (ret != PQOS_RETVAL_OK)
return ret;
if (count > max_num_ca)
return PQOS_RETVAL_ERROR;
ASSERT(m_cpu != NULL);
ret = pqos_cpu_get_one_core(m_cpu, socket, &core);
if (ret != PQOS_RETVAL_OK)
return ret;
if (cdp_enabled) {
for (i = 0, reg = PQOS_MSR_L3CA_MASK_START;
i < count; i++, reg += 2) {
ca[i].cdp = 1;
ca[i].class_id = i;
retval = msr_read(core, reg, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
ca[i].u.s.data_mask = val;
retval = msr_read(core, reg + 1, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
ca[i].u.s.code_mask = val;
}
} else {
for (i = 0, reg = PQOS_MSR_L3CA_MASK_START;
i < count; i++, reg++) {
retval = msr_read(core, reg, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
ca[i].cdp = 0;
ca[i].class_id = i;
ca[i].u.ways_mask = val;
}
}
*num_ca = count;
return ret;
}
int
hw_l3ca_get_min_cbm_bits(unsigned *min_cbm_bits)
{
int ret;
unsigned *sockets, socket_id, sockets_num;
unsigned class_id, l3ca_num, ways, i;
int technology = 1 << PQOS_CAP_TYPE_L3CA;
const struct pqos_capability *l3_cap = NULL;
struct pqos_l3ca l3ca_config[PQOS_MAX_L3CA_COS];
ASSERT(m_cap != NULL);
ASSERT(m_cpu != NULL);
ASSERT(min_cbm_bits != NULL);
/**
* Get L3 CAT capabilities
*/
ret = pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_L3CA, &l3_cap);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_RESOURCE; /* L3 CAT not supported */
/**
* Get number & list of sockets in the system
*/
sockets = pqos_cpu_get_sockets(m_cpu, &sockets_num);
if (sockets == NULL || sockets_num == 0) {
ret = PQOS_RETVAL_ERROR;
goto pqos_l3ca_get_min_cbm_bits_exit;
}
/**
* Find free COS
*/
for (socket_id = 0; socket_id < sockets_num; socket_id++) {
ret = get_unused_cos(socket_id, technology, &class_id);
if (ret == PQOS_RETVAL_OK)
break;
if (ret != PQOS_RETVAL_RESOURCE)
goto pqos_l3ca_get_min_cbm_bits_exit;
}
if (ret == PQOS_RETVAL_RESOURCE) {
LOG_INFO("No free L3 COS available. "
"Unable to determine minimum L3 CBM bits\n");
goto pqos_l3ca_get_min_cbm_bits_exit;
}
/**
* Get current configuration
*/
ret = hw_l3ca_get(socket_id, PQOS_MAX_L3CA_COS, &l3ca_num, l3ca_config);
if (ret != PQOS_RETVAL_OK)
goto pqos_l3ca_get_min_cbm_bits_exit;
/**
* Probe for min cbm bits
*/
for (ways = 1; ways <= l3_cap->u.l3ca->num_ways; ways++) {
struct pqos_l3ca l3ca_tab[PQOS_MAX_L3CA_COS];
unsigned num_ca;
uint64_t mask = (1 << ways) - 1;
memset(l3ca_tab, 0, sizeof(struct pqos_l3ca));
l3ca_tab[0].class_id = class_id;
l3ca_tab[0].u.ways_mask = mask;
/**
* Try to set mask
*/
ret = hw_l3ca_set(socket_id, 1, l3ca_tab);
if (ret != PQOS_RETVAL_OK)
continue;
/**
* Validate if mask was correctly set
*/
ret = hw_l3ca_get(socket_id, PQOS_MAX_L3CA_COS, &num_ca,
l3ca_tab);
if (ret != PQOS_RETVAL_OK)
goto pqos_l3ca_get_min_cbm_bits_restore;
for (i = 0; i < num_ca; i++) {
struct pqos_l3ca *l3ca = &(l3ca_tab[i]);
if (l3ca->class_id != class_id)
continue;
if ((l3ca->cdp &&
l3ca->u.s.data_mask == mask &&
l3ca->u.s.code_mask == mask) ||
(!l3ca->cdp && l3ca->u.ways_mask == mask)) {
*min_cbm_bits = ways;
ret = PQOS_RETVAL_OK;
goto pqos_l3ca_get_min_cbm_bits_restore;
}
}
}
/**
* Restore old settings
*/
pqos_l3ca_get_min_cbm_bits_restore:
for (i = 0; i < l3ca_num; i++) {
int ret_val;
if (l3ca_config[i].class_id != class_id)
continue;
ret_val = hw_l3ca_set(socket_id, 1, &(l3ca_config[i]));
if (ret_val != PQOS_RETVAL_OK) {
LOG_ERROR("Failed to restore CAT configuration. CAT"
" configuration has been altered!\n");
ret = ret_val;
break;
}
}
pqos_l3ca_get_min_cbm_bits_exit:
if (sockets != NULL)
free(sockets);
return ret;
}
int
hw_l2ca_set(const unsigned l2id,
const unsigned num_ca,
const struct pqos_l2ca *ca)
{
int ret = PQOS_RETVAL_OK;
unsigned i = 0, count = 0, core = 0;
int cdp_enabled = 0;
ASSERT(ca != NULL);
ASSERT(num_ca != 0);
ASSERT(m_cap != NULL);
ASSERT(m_cpu != NULL);
/*
* Check if L2 CAT is supported
*/
ret = pqos_l2ca_get_cos_num(m_cap, &count);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_RESOURCE; /* L2 CAT not supported */
/**
* Check if class id's are within allowed range.
*/
for (i = 0; i < num_ca; i++) {
if (ca[i].class_id >= count) {
LOG_ERROR("L2 COS%u is out of range (COS%u is max)!\n",
ca[i].class_id, count - 1);
return PQOS_RETVAL_PARAM;
}
}
ret = pqos_l2ca_cdp_enabled(m_cap, NULL, &cdp_enabled);
if (ret != PQOS_RETVAL_OK)
return ret;
/**
* Pick one core from the L2 cluster and
* perform MSR writes to COS registers on the cluster.
*/
ret = pqos_cpu_get_one_by_l2id(m_cpu, l2id, &core);
if (ret != PQOS_RETVAL_OK)
return ret;
for (i = 0; i < num_ca; i++) {
if (cdp_enabled) {
uint32_t reg =
(ca[i].class_id * 2) + PQOS_MSR_L2CA_MASK_START;
int retval = MACHINE_RETVAL_OK;
uint64_t cmask = 0, dmask = 0;
if (ca[i].cdp) {
dmask = ca[i].u.s.data_mask;
cmask = ca[i].u.s.code_mask;
} else {
dmask = ca[i].u.ways_mask;
cmask = ca[i].u.ways_mask;
}
retval = msr_write(core, reg, dmask);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
retval = msr_write(core, reg + 1, cmask);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
} else {
uint32_t reg =
ca[i].class_id + PQOS_MSR_L2CA_MASK_START;
uint64_t val = ca[i].u.ways_mask;
int retval;
if (ca[i].cdp) {
LOG_ERROR("Attempting to set CDP COS "
"while L2 CDP is disabled!\n");
return PQOS_RETVAL_ERROR;
}
retval = msr_write(core, reg, val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
}
}
return ret;
}
int
hw_l2ca_get(const unsigned l2id,
const unsigned max_num_ca,
unsigned *num_ca,
struct pqos_l2ca *ca)
{
int ret = PQOS_RETVAL_OK;
unsigned i = 0, count = 0;
unsigned core = 0;
int cdp_enabled = 0;
ASSERT(num_ca != NULL);
ASSERT(ca != NULL);
ASSERT(max_num_ca != 0);
ASSERT(m_cap != NULL);
ret = pqos_l2ca_get_cos_num(m_cap, &count);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_RESOURCE; /* L2 CAT not supported */
ret = pqos_l2ca_cdp_enabled(m_cap, NULL, &cdp_enabled);
if (ret != PQOS_RETVAL_OK)
return ret;
if (max_num_ca < count)
/* Not enough space to store the classes */
return PQOS_RETVAL_PARAM;
ASSERT(m_cpu != NULL);
ret = pqos_cpu_get_one_by_l2id(m_cpu, l2id, &core);
if (ret != PQOS_RETVAL_OK)
return ret;
for (i = 0; i < count; i++) {
int retval;
uint64_t val;
ca[i].class_id = i;
ca[i].cdp = cdp_enabled;
if (cdp_enabled) {
const uint32_t reg = PQOS_MSR_L2CA_MASK_START + i * 2;
retval = msr_read(core, reg, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
ca[i].u.s.data_mask = val;
retval = msr_read(core, reg + 1, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
ca[i].u.s.code_mask = val;
} else {
const uint32_t reg = PQOS_MSR_L2CA_MASK_START + i;
retval = msr_read(core, reg, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
ca[i].u.ways_mask = val;
}
}
*num_ca = count;
return ret;
}
int
hw_l2ca_get_min_cbm_bits(unsigned *min_cbm_bits)
{
int ret;
unsigned *l2ids = NULL, l2id_num = 0, l2id;
unsigned class_id, l2ca_num, ways, i;
int technology = 1 << PQOS_CAP_TYPE_L2CA;
const struct pqos_capability *l2_cap = NULL;
struct pqos_l2ca l2ca_config[PQOS_MAX_L2CA_COS];
ASSERT(m_cap != NULL);
ASSERT(m_cpu != NULL);
ASSERT(min_cbm_bits != NULL);
/**
* Get L2 CAT capabilities
*/
ret = pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_L2CA, &l2_cap);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_RESOURCE; /* L2 CAT not supported */
/**
* Get number & list of L2ids in the system
*/
l2ids = pqos_cpu_get_l2ids(m_cpu, &l2id_num);
if (l2ids == NULL || l2id_num == 0) {
ret = PQOS_RETVAL_ERROR;
goto hw_l2ca_get_min_cbm_bits_exit;
}
/**
* Find free COS
*/
for (l2id = 0; l2id < l2id_num; l2id++) {
ret = get_unused_cos(l2id, technology, &class_id);
if (ret == PQOS_RETVAL_OK)
break;
if (ret != PQOS_RETVAL_RESOURCE)
goto hw_l2ca_get_min_cbm_bits_exit;
}
if (ret == PQOS_RETVAL_RESOURCE) {
LOG_INFO("No free L2 COS available. "
"Unable to determine minimum L2 CBM bits\n");
goto hw_l2ca_get_min_cbm_bits_exit;
}
/**
* Get current configuration
*/
ret = hw_l2ca_get(l2id, PQOS_MAX_L2CA_COS, &l2ca_num, l2ca_config);
if (ret != PQOS_RETVAL_OK)
goto hw_l2ca_get_min_cbm_bits_exit;
/**
* Probe for min cbm bits
*/
for (ways = 1; ways <= l2_cap->u.l2ca->num_ways; ways++) {
struct pqos_l2ca l2ca_tab[PQOS_MAX_L2CA_COS];
unsigned num_ca;
uint64_t mask = (1 << ways) - 1;
memset(l2ca_tab, 0, sizeof(struct pqos_l2ca));
l2ca_tab[0].class_id = class_id;
l2ca_tab[0].u.ways_mask = mask;
/**
* Try to set mask
*/
ret = hw_l2ca_set(l2id, 1, l2ca_tab);
if (ret != PQOS_RETVAL_OK)
continue;
/**
* Validate if mask was correctly set
*/
ret = hw_l2ca_get(l2id, PQOS_MAX_L2CA_COS, &num_ca, l2ca_tab);
if (ret != PQOS_RETVAL_OK)
goto hw_l2ca_get_min_cbm_bits_restore;
for (i = 0; i < num_ca; i++) {
struct pqos_l2ca *l2ca = &(l2ca_tab[i]);
if (l2ca->class_id != class_id)
continue;
if ((l2ca->cdp &&
l2ca->u.s.data_mask == mask &&
l2ca->u.s.code_mask == mask) ||
(!l2ca->cdp && l2ca->u.ways_mask == mask)) {
*min_cbm_bits = ways;
ret = PQOS_RETVAL_OK;
goto hw_l2ca_get_min_cbm_bits_restore;
}
}
}
/**
* Restore old settings
*/
hw_l2ca_get_min_cbm_bits_restore:
for (i = 0; i < l2ca_num; i++) {
int ret_val;
if (l2ca_config[i].class_id != class_id)
continue;
ret_val = hw_l2ca_set(l2id, 1, &(l2ca_config[i]));
if (ret_val != PQOS_RETVAL_OK) {
LOG_ERROR("Failed to restore CAT configuration. CAT"
" configuration has been altered!\n");
ret = ret_val;
break;
}
}
hw_l2ca_get_min_cbm_bits_exit:
if (l2ids != NULL)
free(l2ids);
return ret;
}
int
hw_mba_set(const unsigned socket,
const unsigned num_cos,
const struct pqos_mba *requested,
struct pqos_mba *actual)
{
int ret = PQOS_RETVAL_OK;
unsigned i = 0, count = 0, core = 0, step = 0;
const struct pqos_capability *mba_cap = NULL;
ASSERT(requested != NULL);
ASSERT(num_cos != 0);
/**
* Check if MBA is supported
*/
ASSERT(m_cap != NULL);
ret = pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_MBA, &mba_cap);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_RESOURCE; /* MBA not supported */
count = mba_cap->u.mba->num_classes;
step = mba_cap->u.mba->throttle_step;
/**
* Non-linear mode not currently supported
*/
if (!mba_cap->u.mba->is_linear) {
LOG_ERROR("MBA non-linear mode not currently supported!\n");
return PQOS_RETVAL_RESOURCE;
}
/**
* Check if class id's are within allowed range.
*/
for (i = 0; i < num_cos; i++)
if (requested[i].class_id >= count) {
LOG_ERROR("MBA COS%u is out of range (COS%u is max)!\n",
requested[i].class_id, count - 1);
return PQOS_RETVAL_PARAM;
}
ASSERT(m_cpu != NULL);
ret = pqos_cpu_get_one_core(m_cpu, socket, &core);
if (ret != PQOS_RETVAL_OK)
return ret;
for (i = 0; i < num_cos; i++) {
const uint32_t reg =
requested[i].class_id + PQOS_MSR_MBA_MASK_START;
uint64_t val = PQOS_MBA_LINEAR_MAX -
(((requested[i].mb_rate + (step/2)) / step) * step);
int retval = MACHINE_RETVAL_OK;
if (val > mba_cap->u.mba->throttle_max)
val = mba_cap->u.mba->throttle_max;
retval = msr_write(core, reg, val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
/**
* If table to store actual values set is passed,
* read MSR values and store in table
*/
if (actual == NULL)
continue;
retval = msr_read(core, reg, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
actual[i] = requested[i];
actual[i].mb_rate = (PQOS_MBA_LINEAR_MAX - val);
}
return ret;
}
int
hw_mba_get(const unsigned socket,
const unsigned max_num_cos,
unsigned *num_cos,
struct pqos_mba *mba_tab)
{
int ret = PQOS_RETVAL_OK;
unsigned i = 0, count = 0, core = 0;
ASSERT(num_cos != NULL);
ASSERT(mba_tab != NULL);
ASSERT(max_num_cos != 0);
ASSERT(m_cap != NULL);
ret = pqos_mba_get_cos_num(m_cap, &count);
if (ret != PQOS_RETVAL_OK)
return ret; /**< no MBA capability */
if (count > max_num_cos)
return PQOS_RETVAL_ERROR;
ASSERT(m_cpu != NULL);
ret = pqos_cpu_get_one_core(m_cpu, socket, &core);
if (ret != PQOS_RETVAL_OK)
return ret;
for (i = 0; i < count; i++) {
const uint32_t reg = PQOS_MSR_MBA_MASK_START + i;
uint64_t val = 0;
int retval = msr_read(core, reg, &val);
if (retval != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
mba_tab[i].class_id = i;
mba_tab[i].mb_rate = (unsigned) PQOS_MBA_LINEAR_MAX - val;
}
*num_cos = count;
return ret;
}
/**
* @brief Sets COS associated to \a lcore
*
* @param [in] lcore lcore to set COS association
* @param [in] class_id COS to associate lcore to
*
* @return Operation status
*/
static int
cos_assoc_set(const unsigned lcore, const unsigned class_id)
{
const uint32_t reg = PQOS_MSR_ASSOC;
uint64_t val = 0;
int ret;
ret = msr_read(lcore, reg, &val);
if (ret != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
val &= (~PQOS_MSR_ASSOC_QECOS_MASK);
val |= (((uint64_t) class_id) << PQOS_MSR_ASSOC_QECOS_SHIFT);
ret = msr_write(lcore, reg, val);
if (ret != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
return PQOS_RETVAL_OK;
}
int
hw_alloc_assoc_set(const unsigned lcore,
const unsigned class_id)
{
int ret = PQOS_RETVAL_OK;
unsigned num_l2_cos = 0, num_l3_cos = 0;
ASSERT(m_cpu != NULL);
ret = pqos_cpu_check_core(m_cpu, lcore);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_PARAM;
ASSERT(m_cap != NULL);
ret = pqos_l3ca_get_cos_num(m_cap, &num_l3_cos);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
ret = pqos_l2ca_get_cos_num(m_cap, &num_l2_cos);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
if (class_id >= num_l3_cos && class_id >= num_l2_cos)
/* class_id is out of bounds */
return PQOS_RETVAL_PARAM;
ret = cos_assoc_set(lcore, class_id);
return ret;
}
int
hw_alloc_assoc_get(const unsigned lcore,
unsigned *class_id)
{
const struct pqos_capability *l3_cap = NULL;
const struct pqos_capability *l2_cap = NULL;
int ret = PQOS_RETVAL_OK;
ASSERT(class_id != NULL);
ASSERT(m_cpu != NULL);
ret = pqos_cpu_check_core(m_cpu, lcore);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_PARAM;
ASSERT(m_cap != NULL);
ret = pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_L3CA, &l3_cap);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
ret = pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_L2CA, &l2_cap);
if (ret != PQOS_RETVAL_OK && ret != PQOS_RETVAL_RESOURCE)
return ret;
if (l2_cap == NULL && l3_cap == NULL)
/* no L2/L3 CAT detected */
return PQOS_RETVAL_RESOURCE;
ret = cos_assoc_get(lcore, class_id);
return ret;
}
int
hw_alloc_assign(const unsigned technology,
const unsigned *core_array,
const unsigned core_num,
unsigned *class_id)
{
const int l2_req = ((technology & (1 << PQOS_CAP_TYPE_L2CA)) != 0);
unsigned i;
unsigned socket = 0, l2id = 0;
int ret;
ASSERT(core_num > 0);
ASSERT(core_array != NULL);
ASSERT(class_id != NULL);
ASSERT(technology != 0);
/* Check if core belongs to one resource entity */
for (i = 0; i < core_num; i++) {
const struct pqos_coreinfo *pi = NULL;
pi = pqos_cpu_get_core_info(m_cpu, core_array[i]);
if (pi == NULL) {
ret = PQOS_RETVAL_PARAM;
goto pqos_alloc_assign_exit;
}
if (l2_req) {
/* L2 is requested
* The smallest manageable entity is L2 cluster
*/
if (i != 0 && l2id != pi->l2_id) {
ret = PQOS_RETVAL_PARAM;
goto pqos_alloc_assign_exit;
}
l2id = pi->l2_id;
} else {
if (i != 0 && socket != pi->socket) {
ret = PQOS_RETVAL_PARAM;
goto pqos_alloc_assign_exit;
}
socket = pi->socket;
}
}
/* find an unused class from highest down */
if (!l2_req)
ret = get_unused_cos(socket, technology, class_id);
else
ret = get_unused_cos(l2id, technology, class_id);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_assign_exit;
/* assign cores to the unused class */
for (i = 0; i < core_num; i++) {
ret = cos_assoc_set(core_array[i], *class_id);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_assign_exit;
}
pqos_alloc_assign_exit:
return ret;
}
int
hw_alloc_release(const unsigned *core_array,
const unsigned core_num)
{
unsigned i;
int ret = PQOS_RETVAL_OK;
ASSERT(core_num > 0 && core_array != NULL);
for (i = 0; i < core_num; i++)
if (cos_assoc_set(core_array[i], 0) != PQOS_RETVAL_OK)
ret = PQOS_RETVAL_ERROR;
return ret;
}
/**
* @brief Enables or disables CDP across selected CPU sockets
*
* @param [in] sockets_num dimension of \a sockets array
* @param [in] sockets array with socket ids to change CDP config on
* @param [in] enable CDP enable/disable flag, 1 - enable, 0 - disable
*
* @return Operations status
* @retval PQOS_RETVAL_OK on success
* @retval PQOS_RETVAL_ERROR on failure, MSR read/write error
*/
static int
l3cdp_enable(const unsigned sockets_num,
const unsigned *sockets,
const int enable)
{
unsigned j = 0;
ASSERT(sockets_num > 0 && sockets != NULL);
LOG_INFO("%s L3 CDP across sockets...\n",
(enable) ? "Enabling" : "Disabling");
for (j = 0; j < sockets_num; j++) {
uint64_t reg = 0;
unsigned core = 0;
int ret = PQOS_RETVAL_OK;
ret = pqos_cpu_get_one_core(m_cpu, sockets[j], &core);
if (ret != PQOS_RETVAL_OK)
return ret;
ret = msr_read(core, PQOS_MSR_L3_QOS_CFG, ®);
if (ret != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
if (enable)
reg |= PQOS_MSR_L3_QOS_CFG_CDP_EN;
else
reg &= ~PQOS_MSR_L3_QOS_CFG_CDP_EN;
ret = msr_write(core, PQOS_MSR_L3_QOS_CFG, reg);
if (ret != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
}
return PQOS_RETVAL_OK;
}
/**
* @brief Enables or disables CDP across selected CPU clusters
*
* @param [in] l2id_num dimension of \a l2ids array
* @param [in] l2ids array with clusters ids to change CDP config on
* @param [in] enable CDP enable/disable flag, 1 - enable, 0 - disable
*
* @return Operations status
* @retval PQOS_RETVAL_OK on success
* @retval PQOS_RETVAL_ERROR on failure, MSR read/write error
*/
static int
l2cdp_enable(const unsigned l2id_num,
const unsigned *l2ids,
const int enable)
{
unsigned i = 0;
int ret;
ASSERT(l2id_num > 0 && l2ids != NULL);
LOG_INFO("%s L2 CDP across clusters...\n",
(enable) ? "Enabling" : "Disabling");
for (i = 0; i < l2id_num; i++) {
uint64_t reg = 0;
unsigned core = 0;
ret = pqos_cpu_get_one_by_l2id(m_cpu, l2ids[i], &core);
if (ret != PQOS_RETVAL_OK)
return ret;
ret = msr_read(core, PQOS_MSR_L2_QOS_CFG, ®);
if (ret != PQOS_RETVAL_OK)
return PQOS_RETVAL_ERROR;
if (enable)
reg |= PQOS_MSR_L2_QOS_CFG_CDP_EN;
else
reg &= ~PQOS_MSR_L2_QOS_CFG_CDP_EN;
ret = msr_write(core, PQOS_MSR_L2_QOS_CFG, reg);
if (ret != MACHINE_RETVAL_OK)
return PQOS_RETVAL_ERROR;
}
return PQOS_RETVAL_OK;
}
/**
* @brief Writes range of MBA/CAT COS MSR's with \a msr_val value
*
* Used as part of CAT/MBA reset process.
*
* @param [in] msr_start First MSR to be written
* @param [in] msr_num Number of MSR's to be written
* @param [in] coreid Core ID to be used for MSR write operations
* @param [in] msr_val Value to be written to MSR's
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
* @retval PQOS_RETVAL_ERROR on MSR write error
*/
static int
alloc_cos_reset(const unsigned msr_start,
const unsigned msr_num,
const unsigned coreid,
const uint64_t msr_val)
{
int ret = PQOS_RETVAL_OK;
unsigned i;
for (i = 0; i < msr_num; i++) {
int retval = msr_write(coreid, msr_start + i, msr_val);
if (retval != MACHINE_RETVAL_OK)
ret = PQOS_RETVAL_ERROR;
}
return ret;
}
/**
* @brief Associates each of the cores with COS0
*
* Operates on m_cpu structure.
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
* @retval PQOS_RETVAL_ERROR on MSR write error
*/
static int
alloc_assoc_reset(void)
{
int ret = PQOS_RETVAL_OK;
unsigned i;
for (i = 0; i < m_cpu->num_cores; i++)
if (cos_assoc_set(m_cpu->cores[i].lcore, 0) != PQOS_RETVAL_OK)
ret = PQOS_RETVAL_ERROR;
return ret;
}
int
hw_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
const enum pqos_cdp_config l2_cdp_cfg)
{
unsigned *sockets = NULL;
unsigned sockets_num = 0;
unsigned *l2ids = NULL;
unsigned l2id_num = 0;
const struct pqos_capability *alloc_cap = NULL;
const struct pqos_cap_l3ca *l3_cap = NULL;
const struct pqos_cap_l2ca *l2_cap = NULL;
const struct pqos_cap_mba *mba_cap = NULL;
int ret = PQOS_RETVAL_OK;
unsigned max_l3_cos = 0;
unsigned max_l2_cos = 0;
unsigned j;
int cdp_supported;
ASSERT(l3_cdp_cfg == PQOS_REQUIRE_CDP_ON ||
l3_cdp_cfg == PQOS_REQUIRE_CDP_OFF ||
l3_cdp_cfg == PQOS_REQUIRE_CDP_ANY);
ASSERT(l2_cdp_cfg == PQOS_REQUIRE_CDP_ON ||
l2_cdp_cfg == PQOS_REQUIRE_CDP_OFF ||
l2_cdp_cfg == PQOS_REQUIRE_CDP_ANY);
/* Get L3 CAT capabilities */
(void) pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_L3CA, &alloc_cap);
if (alloc_cap != NULL)
l3_cap = alloc_cap->u.l3ca;
/* Get L2 CAT capabilities */
alloc_cap = NULL;
(void) pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_L2CA, &alloc_cap);
if (alloc_cap != NULL)
l2_cap = alloc_cap->u.l2ca;
/* Get MBA capabilities */
alloc_cap = NULL;
(void) pqos_cap_get_type(m_cap, PQOS_CAP_TYPE_MBA, &alloc_cap);
if (alloc_cap != NULL)
mba_cap = alloc_cap->u.mba;
/* Check if either L2 CAT, L3 CAT or MBA is supported */
if (l2_cap == NULL && l3_cap == NULL && mba_cap == NULL) {
LOG_ERROR("L2 CAT/L3 CAT/MBA not present!\n");
ret = PQOS_RETVAL_RESOURCE; /* no L2/L3 CAT present */
goto pqos_alloc_reset_exit;
}
/* Check L3 CDP requested while not present */
if (l3_cap == NULL && l3_cdp_cfg != PQOS_REQUIRE_CDP_ANY) {
LOG_ERROR("L3 CDP setting requested but no L3 CAT present!\n");
ret = PQOS_RETVAL_RESOURCE;
goto pqos_alloc_reset_exit;
}
/* Check L2 CDP requested while not present */
if (l2_cap == NULL && l2_cdp_cfg != PQOS_REQUIRE_CDP_ANY) {
LOG_ERROR("L2 CDP setting requested but no L2 CAT present!\n");
ret = PQOS_RETVAL_RESOURCE;
goto pqos_alloc_reset_exit;
}
if (l3_cap != NULL) {
ret = pqos_l3ca_cdp_enabled(m_cap, &cdp_supported, NULL);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
/* Check against erroneous L3 CDP request */
if (l3_cdp_cfg == PQOS_REQUIRE_CDP_ON && !cdp_supported) {
LOG_ERROR("L3 CAT/CDP requested but not supported by "
"the platform!\n");
ret = PQOS_RETVAL_PARAM;
goto pqos_alloc_reset_exit;
}
/* Get maximum number of L3 CAT classes */
max_l3_cos = l3_cap->num_classes;
if (l3_cap->cdp && l3_cap->cdp_on)
max_l3_cos = max_l3_cos * 2;
}
if (l2_cap != NULL) {
ret = pqos_l2ca_cdp_enabled(m_cap, &cdp_supported, NULL);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
/* Check against erroneous L2 CDP request */
if (l2_cdp_cfg == PQOS_REQUIRE_CDP_ON && !cdp_supported) {
LOG_ERROR("L2 CAT/CDP requested but not supported by "
"the platform!\n");
ret = PQOS_RETVAL_PARAM;
goto pqos_alloc_reset_exit;
}
/* Get maximum number of L2 CAT classes */
max_l2_cos = l2_cap->num_classes;
if (l2_cap->cdp && l2_cap->cdp_on)
max_l2_cos = max_l2_cos * 2;
}
/**
* Get number & list of sockets in the system
*/
sockets = pqos_cpu_get_sockets(m_cpu, &sockets_num);
if (sockets == NULL || sockets_num == 0)
goto pqos_alloc_reset_exit;
if (l3_cap != NULL) {
/**
* Change L3 COS definition on all sockets
* so that each COS allows for access to all cache ways
*/
for (j = 0; j < sockets_num; j++) {
unsigned core = 0;
ret = pqos_cpu_get_one_core(m_cpu, sockets[j], &core);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
const uint64_t ways_mask =
(1ULL << l3_cap->num_ways) - 1ULL;
ret = alloc_cos_reset(PQOS_MSR_L3CA_MASK_START,
max_l3_cos, core, ways_mask);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
}
}
if (l2_cap != NULL) {
/**
* Get number & list of L2ids in the system
* Then go through all L2 ids and reset L2 classes on them
*/
l2ids = pqos_cpu_get_l2ids(m_cpu, &l2id_num);
if (l2ids == NULL || l2id_num == 0)
goto pqos_alloc_reset_exit;
for (j = 0; j < l2id_num; j++) {
const uint64_t ways_mask =
(1ULL << l2_cap->num_ways) - 1ULL;
unsigned core = 0;
ret = pqos_cpu_get_one_by_l2id(m_cpu, l2ids[j], &core);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
ret = alloc_cos_reset(PQOS_MSR_L2CA_MASK_START,
max_l2_cos, core, ways_mask);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
}
}
if (mba_cap != NULL) {
/**
* Go through all sockets and reset MBA class defintions
* 0 is the default MBA COS value in linear mode.
*/
for (j = 0; j < sockets_num; j++) {
unsigned core = 0;
ret = pqos_cpu_get_one_core(m_cpu, sockets[j], &core);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
ret = alloc_cos_reset(PQOS_MSR_MBA_MASK_START,
mba_cap->num_classes, core, 0);
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
}
}
/**
* Associate all cores with COS0
*/
ret = alloc_assoc_reset();
if (ret != PQOS_RETVAL_OK)
goto pqos_alloc_reset_exit;
/**
* Turn L3 CDP ON or OFF upon the request
*/
if (l3_cap != NULL) {
if (l3_cdp_cfg == PQOS_REQUIRE_CDP_ON && !l3_cap->cdp_on) {
/**
* Turn on L3 CDP
*/
LOG_INFO("Turning L3 CDP ON ...\n");
ret = l3cdp_enable(sockets_num, sockets, 1);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("L3 CDP enable error!\n");
goto pqos_alloc_reset_exit;
}
}
if (l3_cdp_cfg == PQOS_REQUIRE_CDP_OFF && l3_cap->cdp_on) {
/**
* Turn off L3 CDP
*/
LOG_INFO("Turning L3 CDP OFF ...\n");
ret = l3cdp_enable(sockets_num, sockets, 0);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("L3 CDP disable error!\n");
goto pqos_alloc_reset_exit;
}
}
_pqos_cap_l3cdp_change(l3_cdp_cfg);
}
/**
* Turn L2 CDP ON or OFF upon the request
*/
if (l2_cap != NULL) {
if (l2_cdp_cfg == PQOS_REQUIRE_CDP_ON && !l2_cap->cdp_on) {
/**
* Turn on L2 CDP
*/
LOG_INFO("Turning L2 CDP ON ...\n");
ret = l2cdp_enable(l2id_num, l2ids, 1);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("L2 CDP enable error!\n");
goto pqos_alloc_reset_exit;
}
}
if (l2_cdp_cfg == PQOS_REQUIRE_CDP_OFF && l2_cap->cdp_on) {
/**
* Turn off L2 CDP
*/
LOG_INFO("Turning L2 CDP OFF ...\n");
ret = l2cdp_enable(l2id_num, l2ids, 0);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("L2 CDP disable error!\n");
goto pqos_alloc_reset_exit;
}
}
_pqos_cap_l2cdp_change(l2_cdp_cfg);
}
pqos_alloc_reset_exit:
if (sockets != NULL)
free(sockets);
if (l2ids != NULL)
free(l2ids);
return ret;
}