| // SPDX-License-Identifier: BSD-3-Clause | |
| /* Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries. | |
| * Microchip VCAP API kunit test suite | |
| */ | |
| #include <kunit/test.h> | |
| #include "vcap_api.h" | |
| #include "vcap_api_client.h" | |
| #include "vcap_api_debugfs.h" | |
| #include "vcap_model_kunit.h" | |
| /* First we have the test infrastructure that emulates the platform | |
| * implementation | |
| */ | |
| #define TEST_BUF_CNT 100 | |
| #define TEST_BUF_SZ 350 | |
| #define STREAMWSIZE 64 | |
| static u32 test_updateaddr[STREAMWSIZE] = {}; | |
| static int test_updateaddridx; | |
| static int test_cache_erase_count; | |
| static u32 test_init_start; | |
| static u32 test_init_count; | |
| static u32 test_hw_counter_id; | |
| static struct vcap_cache_data test_hw_cache; | |
| static struct net_device test_netdev = {}; | |
| static int test_move_addr; | |
| static int test_move_offset; | |
| static int test_move_count; | |
| static char test_pr_buffer[TEST_BUF_CNT][TEST_BUF_SZ]; | |
| static int test_pr_bufferidx; | |
| static int test_pr_idx; | |
| /* Callback used by the VCAP API */ | |
| static enum vcap_keyfield_set test_val_keyset(struct net_device *ndev, | |
| struct vcap_admin *admin, | |
| struct vcap_rule *rule, | |
| struct vcap_keyset_list *kslist, | |
| u16 l3_proto) | |
| { | |
| int idx; | |
| if (kslist->cnt > 0) { | |
| switch (admin->vtype) { | |
| case VCAP_TYPE_IS0: | |
| for (idx = 0; idx < kslist->cnt; idx++) { | |
| if (kslist->keysets[idx] == VCAP_KFS_ETAG) | |
| return kslist->keysets[idx]; | |
| if (kslist->keysets[idx] == | |
| VCAP_KFS_PURE_5TUPLE_IP4) | |
| return kslist->keysets[idx]; | |
| if (kslist->keysets[idx] == | |
| VCAP_KFS_NORMAL_5TUPLE_IP4) | |
| return kslist->keysets[idx]; | |
| if (kslist->keysets[idx] == | |
| VCAP_KFS_NORMAL_7TUPLE) | |
| return kslist->keysets[idx]; | |
| } | |
| break; | |
| case VCAP_TYPE_IS2: | |
| for (idx = 0; idx < kslist->cnt; idx++) { | |
| if (kslist->keysets[idx] == VCAP_KFS_MAC_ETYPE) | |
| return kslist->keysets[idx]; | |
| if (kslist->keysets[idx] == VCAP_KFS_ARP) | |
| return kslist->keysets[idx]; | |
| if (kslist->keysets[idx] == VCAP_KFS_IP_7TUPLE) | |
| return kslist->keysets[idx]; | |
| } | |
| break; | |
| default: | |
| pr_info("%s:%d: no validation for VCAP %d\n", | |
| __func__, __LINE__, admin->vtype); | |
| break; | |
| } | |
| } | |
| return -EINVAL; | |
| } | |
| /* Callback used by the VCAP API */ | |
| static void test_add_def_fields(struct net_device *ndev, | |
| struct vcap_admin *admin, | |
| struct vcap_rule *rule) | |
| { | |
| if (admin->vinst == 0 || admin->vinst == 2) | |
| vcap_rule_add_key_bit(rule, VCAP_KF_LOOKUP_FIRST_IS, | |
| VCAP_BIT_1); | |
| else | |
| vcap_rule_add_key_bit(rule, VCAP_KF_LOOKUP_FIRST_IS, | |
| VCAP_BIT_0); | |
| } | |
| /* Callback used by the VCAP API */ | |
| static void test_cache_erase(struct vcap_admin *admin) | |
| { | |
| if (test_cache_erase_count) { | |
| memset(admin->cache.keystream, 0, test_cache_erase_count); | |
| memset(admin->cache.maskstream, 0, test_cache_erase_count); | |
| memset(admin->cache.actionstream, 0, test_cache_erase_count); | |
| test_cache_erase_count = 0; | |
| } | |
| } | |
| /* Callback used by the VCAP API */ | |
| static void test_cache_init(struct net_device *ndev, struct vcap_admin *admin, | |
| u32 start, u32 count) | |
| { | |
| test_init_start = start; | |
| test_init_count = count; | |
| } | |
| /* Callback used by the VCAP API */ | |
| static void test_cache_read(struct net_device *ndev, struct vcap_admin *admin, | |
| enum vcap_selection sel, u32 start, u32 count) | |
| { | |
| u32 *keystr, *mskstr, *actstr; | |
| int idx; | |
| pr_debug("%s:%d: %d %d\n", __func__, __LINE__, start, count); | |
| switch (sel) { | |
| case VCAP_SEL_ENTRY: | |
| keystr = &admin->cache.keystream[start]; | |
| mskstr = &admin->cache.maskstream[start]; | |
| for (idx = 0; idx < count; ++idx) { | |
| pr_debug("%s:%d: keydata[%02d]: 0x%08x\n", __func__, | |
| __LINE__, start + idx, keystr[idx]); | |
| } | |
| for (idx = 0; idx < count; ++idx) { | |
| /* Invert the mask before decoding starts */ | |
| mskstr[idx] = ~mskstr[idx]; | |
| pr_debug("%s:%d: mskdata[%02d]: 0x%08x\n", __func__, | |
| __LINE__, start + idx, mskstr[idx]); | |
| } | |
| break; | |
| case VCAP_SEL_ACTION: | |
| actstr = &admin->cache.actionstream[start]; | |
| for (idx = 0; idx < count; ++idx) { | |
| pr_debug("%s:%d: actdata[%02d]: 0x%08x\n", __func__, | |
| __LINE__, start + idx, actstr[idx]); | |
| } | |
| break; | |
| case VCAP_SEL_COUNTER: | |
| pr_debug("%s:%d\n", __func__, __LINE__); | |
| test_hw_counter_id = start; | |
| admin->cache.counter = test_hw_cache.counter; | |
| admin->cache.sticky = test_hw_cache.sticky; | |
| break; | |
| case VCAP_SEL_ALL: | |
| pr_debug("%s:%d\n", __func__, __LINE__); | |
| break; | |
| } | |
| } | |
| /* Callback used by the VCAP API */ | |
| static void test_cache_write(struct net_device *ndev, struct vcap_admin *admin, | |
| enum vcap_selection sel, u32 start, u32 count) | |
| { | |
| u32 *keystr, *mskstr, *actstr; | |
| int idx; | |
| switch (sel) { | |
| case VCAP_SEL_ENTRY: | |
| keystr = &admin->cache.keystream[start]; | |
| mskstr = &admin->cache.maskstream[start]; | |
| for (idx = 0; idx < count; ++idx) { | |
| pr_debug("%s:%d: keydata[%02d]: 0x%08x\n", __func__, | |
| __LINE__, start + idx, keystr[idx]); | |
| } | |
| for (idx = 0; idx < count; ++idx) { | |
| /* Invert the mask before encoding starts */ | |
| mskstr[idx] = ~mskstr[idx]; | |
| pr_debug("%s:%d: mskdata[%02d]: 0x%08x\n", __func__, | |
| __LINE__, start + idx, mskstr[idx]); | |
| } | |
| break; | |
| case VCAP_SEL_ACTION: | |
| actstr = &admin->cache.actionstream[start]; | |
| for (idx = 0; idx < count; ++idx) { | |
| pr_debug("%s:%d: actdata[%02d]: 0x%08x\n", __func__, | |
| __LINE__, start + idx, actstr[idx]); | |
| } | |
| break; | |
| case VCAP_SEL_COUNTER: | |
| pr_debug("%s:%d\n", __func__, __LINE__); | |
| test_hw_counter_id = start; | |
| test_hw_cache.counter = admin->cache.counter; | |
| test_hw_cache.sticky = admin->cache.sticky; | |
| break; | |
| case VCAP_SEL_ALL: | |
| pr_err("%s:%d: cannot write all streams at once\n", | |
| __func__, __LINE__); | |
| break; | |
| } | |
| } | |
| /* Callback used by the VCAP API */ | |
| static void test_cache_update(struct net_device *ndev, struct vcap_admin *admin, | |
| enum vcap_command cmd, | |
| enum vcap_selection sel, u32 addr) | |
| { | |
| if (test_updateaddridx < ARRAY_SIZE(test_updateaddr)) | |
| test_updateaddr[test_updateaddridx] = addr; | |
| else | |
| pr_err("%s:%d: overflow: %d\n", __func__, __LINE__, | |
| test_updateaddridx); | |
| test_updateaddridx++; | |
| } | |
| static void test_cache_move(struct net_device *ndev, struct vcap_admin *admin, | |
| u32 addr, int offset, int count) | |
| { | |
| test_move_addr = addr; | |
| test_move_offset = offset; | |
| test_move_count = count; | |
| } | |
| /* Provide port information via a callback interface */ | |
| static int vcap_test_port_info(struct net_device *ndev, | |
| struct vcap_admin *admin, | |
| struct vcap_output_print *out) | |
| { | |
| return 0; | |
| } | |
| static const struct vcap_operations test_callbacks = { | |
| .validate_keyset = test_val_keyset, | |
| .add_default_fields = test_add_def_fields, | |
| .cache_erase = test_cache_erase, | |
| .cache_write = test_cache_write, | |
| .cache_read = test_cache_read, | |
| .init = test_cache_init, | |
| .update = test_cache_update, | |
| .move = test_cache_move, | |
| .port_info = vcap_test_port_info, | |
| }; | |
| static struct vcap_control test_vctrl = { | |
| .vcaps = kunit_test_vcaps, | |
| .stats = &kunit_test_vcap_stats, | |
| .ops = &test_callbacks, | |
| }; | |
| static void vcap_test_api_init(struct vcap_admin *admin) | |
| { | |
| /* Initialize the shared objects */ | |
| INIT_LIST_HEAD(&test_vctrl.list); | |
| mutex_init(&test_vctrl.lock); | |
| INIT_LIST_HEAD(&admin->list); | |
| INIT_LIST_HEAD(&admin->rules); | |
| INIT_LIST_HEAD(&admin->enabled); | |
| admin->vctrl = &test_vctrl; | |
| list_add_tail(&admin->list, &test_vctrl.list); | |
| memset(test_updateaddr, 0, sizeof(test_updateaddr)); | |
| test_updateaddridx = 0; | |
| test_pr_bufferidx = 0; | |
| test_pr_idx = 0; | |
| } | |
| /* callback used by the show_admin function */ | |
| static __printf(2, 3) | |
| int test_prf(void *out, const char *fmt, ...) | |
| { | |
| static char test_buffer[TEST_BUF_SZ]; | |
| va_list args; | |
| int idx, cnt; | |
| if (test_pr_bufferidx >= TEST_BUF_CNT) { | |
| pr_err("%s:%d: overflow: %d\n", __func__, __LINE__, | |
| test_pr_bufferidx); | |
| return 0; | |
| } | |
| va_start(args, fmt); | |
| cnt = vscnprintf(test_buffer, TEST_BUF_SZ, fmt, args); | |
| va_end(args); | |
| for (idx = 0; idx < cnt; ++idx) { | |
| test_pr_buffer[test_pr_bufferidx][test_pr_idx] = | |
| test_buffer[idx]; | |
| if (test_buffer[idx] == '\n') { | |
| test_pr_buffer[test_pr_bufferidx][++test_pr_idx] = 0; | |
| test_pr_idx = 0; | |
| test_pr_bufferidx++; | |
| } else { | |
| ++test_pr_idx; | |
| } | |
| } | |
| return cnt; | |
| } | |
| /* Define the test cases. */ | |
| static void vcap_api_addr_keyset_test(struct kunit *test) | |
| { | |
| u32 keydata[12] = { | |
| 0x40450042, 0x000feaf3, 0x00000003, 0x00050600, | |
| 0x10203040, 0x00075880, 0x633c6864, 0x00040003, | |
| 0x00000020, 0x00000008, 0x00000240, 0x00000000, | |
| }; | |
| u32 mskdata[12] = { | |
| 0x0030ff80, 0xfff00000, 0xfffffffc, 0xfff000ff, | |
| 0x00000000, 0xfff00000, 0x00000000, 0xfff3fffc, | |
| 0xffffffc0, 0xffffffff, 0xfffffc03, 0xffffffff, | |
| }; | |
| u32 actdata[12] = {}; | |
| struct vcap_admin admin = { | |
| .vtype = VCAP_TYPE_IS2, | |
| .cache = { | |
| .keystream = keydata, | |
| .maskstream = mskdata, | |
| .actionstream = actdata, | |
| }, | |
| }; | |
| enum vcap_keyfield_set keysets[10]; | |
| struct vcap_keyset_list matches; | |
| int ret, idx, addr; | |
| vcap_test_api_init(&admin); | |
| /* Go from higher to lower addresses searching for a keyset */ | |
| matches.keysets = keysets; | |
| matches.cnt = 0; | |
| matches.max = ARRAY_SIZE(keysets); | |
| for (idx = ARRAY_SIZE(keydata) - 1, addr = 799; idx > 0; | |
| --idx, --addr) { | |
| admin.cache.keystream = &keydata[idx]; | |
| admin.cache.maskstream = &mskdata[idx]; | |
| ret = vcap_addr_keysets(&test_vctrl, &test_netdev, &admin, | |
| addr, &matches); | |
| KUNIT_EXPECT_EQ(test, -EINVAL, ret); | |
| } | |
| /* Finally we hit the start of the rule */ | |
| admin.cache.keystream = &keydata[idx]; | |
| admin.cache.maskstream = &mskdata[idx]; | |
| matches.cnt = 0; | |
| ret = vcap_addr_keysets(&test_vctrl, &test_netdev, &admin, | |
| addr, &matches); | |
| KUNIT_EXPECT_EQ(test, 0, ret); | |
| KUNIT_EXPECT_EQ(test, matches.cnt, 1); | |
| KUNIT_EXPECT_EQ(test, matches.keysets[0], VCAP_KFS_MAC_ETYPE); | |
| } | |
| static void vcap_api_show_admin_raw_test(struct kunit *test) | |
| { | |
| u32 keydata[4] = { | |
| 0x40450042, 0x000feaf3, 0x00000003, 0x00050600, | |
| }; | |
| u32 mskdata[4] = { | |
| 0x0030ff80, 0xfff00000, 0xfffffffc, 0xfff000ff, | |
| }; | |
| u32 actdata[12] = {}; | |
| struct vcap_admin admin = { | |
| .vtype = VCAP_TYPE_IS2, | |
| .cache = { | |
| .keystream = keydata, | |
| .maskstream = mskdata, | |
| .actionstream = actdata, | |
| }, | |
| .first_valid_addr = 786, | |
| .last_valid_addr = 788, | |
| }; | |
| struct vcap_rule_internal ri = { | |
| .ndev = &test_netdev, | |
| }; | |
| struct vcap_output_print out = { | |
| .prf = (void *)test_prf, | |
| }; | |
| const char *test_expected = | |
| " addr: 786, X6 rule, keysets: VCAP_KFS_MAC_ETYPE\n"; | |
| int ret; | |
| vcap_test_api_init(&admin); | |
| list_add_tail(&ri.list, &admin.rules); | |
| ret = vcap_show_admin_raw(&test_vctrl, &admin, &out); | |
| KUNIT_EXPECT_EQ(test, 0, ret); | |
| KUNIT_EXPECT_STREQ(test, test_expected, test_pr_buffer[0]); | |
| } | |
| static const char * const test_admin_info_expect[] = { | |
| "name: is2\n", | |
| "rows: 256\n", | |
| "sw_count: 12\n", | |
| "sw_width: 52\n", | |
| "sticky_width: 1\n", | |
| "act_width: 110\n", | |
| "default_cnt: 73\n", | |
| "require_cnt_dis: 0\n", | |
| "version: 1\n", | |
| "vtype: 4\n", | |
| "vinst: 0\n", | |
| "ingress: 1\n", | |
| "first_cid: 10000\n", | |
| "last_cid: 19999\n", | |
| "lookups: 4\n", | |
| "first_valid_addr: 0\n", | |
| "last_valid_addr: 3071\n", | |
| "last_used_addr: 794\n", | |
| }; | |
| static void vcap_api_show_admin_test(struct kunit *test) | |
| { | |
| struct vcap_admin admin = { | |
| .vtype = VCAP_TYPE_IS2, | |
| .first_cid = 10000, | |
| .last_cid = 19999, | |
| .lookups = 4, | |
| .last_valid_addr = 3071, | |
| .first_valid_addr = 0, | |
| .last_used_addr = 794, | |
| .ingress = true, | |
| }; | |
| struct vcap_output_print out = { | |
| .prf = (void *)test_prf, | |
| }; | |
| int idx; | |
| vcap_test_api_init(&admin); | |
| vcap_show_admin_info(&test_vctrl, &admin, &out); | |
| for (idx = 0; idx < test_pr_bufferidx; ++idx) { | |
| /* pr_info("log[%02d]: %s", idx, test_pr_buffer[idx]); */ | |
| KUNIT_EXPECT_STREQ(test, test_admin_info_expect[idx], | |
| test_pr_buffer[idx]); | |
| } | |
| } | |
| static const char * const test_admin_expect[] = { | |
| "name: is2\n", | |
| "rows: 256\n", | |
| "sw_count: 12\n", | |
| "sw_width: 52\n", | |
| "sticky_width: 1\n", | |
| "act_width: 110\n", | |
| "default_cnt: 73\n", | |
| "require_cnt_dis: 0\n", | |
| "version: 1\n", | |
| "vtype: 4\n", | |
| "vinst: 0\n", | |
| "ingress: 1\n", | |
| "first_cid: 8000000\n", | |
| "last_cid: 8199999\n", | |
| "lookups: 4\n", | |
| "first_valid_addr: 0\n", | |
| "last_valid_addr: 3071\n", | |
| "last_used_addr: 794\n", | |
| "\n", | |
| "rule: 100, addr: [794,799], X6, ctr[0]: 0, hit: 0\n", | |
| " chain_id: 0\n", | |
| " user: 0\n", | |
| " priority: 0\n", | |
| " state: permanent\n", | |
| " keysets: VCAP_KFS_MAC_ETYPE\n", | |
| " keyset_sw: 6\n", | |
| " keyset_sw_regs: 2\n", | |
| " ETYPE_LEN_IS: W1: 1/1\n", | |
| " IF_IGR_PORT_MASK: W32: 0xffabcd01/0xffffffff\n", | |
| " IF_IGR_PORT_MASK_RNG: W4: 5/15\n", | |
| " L2_DMAC: W48: 01:02:03:04:05:06/ff:ff:ff:ff:ff:ff\n", | |
| " L2_PAYLOAD_ETYPE: W64: 0x9000002000000081/0xff000000000000ff\n", | |
| " L2_SMAC: W48: b1:9e:34:32:75:88/ff:ff:ff:ff:ff:ff\n", | |
| " LOOKUP_FIRST_IS: W1: 1/1\n", | |
| " TYPE: W4: 0/15\n", | |
| " actionset: VCAP_AFS_BASE_TYPE\n", | |
| " actionset_sw: 3\n", | |
| " actionset_sw_regs: 4\n", | |
| " CNT_ID: W12: 100\n", | |
| " MATCH_ID: W16: 1\n", | |
| " MATCH_ID_MASK: W16: 1\n", | |
| " POLICE_ENA: W1: 1\n", | |
| " PORT_MASK: W68: 0x0514670115f3324589\n", | |
| }; | |
| static void vcap_api_show_admin_rule_test(struct kunit *test) | |
| { | |
| u32 keydata[] = { | |
| 0x40450042, 0x000feaf3, 0x00000003, 0x00050600, | |
| 0x10203040, 0x00075880, 0x633c6864, 0x00040003, | |
| 0x00000020, 0x00000008, 0x00000240, 0x00000000, | |
| }; | |
| u32 mskdata[] = { | |
| 0x0030ff80, 0xfff00000, 0xfffffffc, 0xfff000ff, | |
| 0x00000000, 0xfff00000, 0x00000000, 0xfff3fffc, | |
| 0xffffffc0, 0xffffffff, 0xfffffc03, 0xffffffff, | |
| }; | |
| u32 actdata[] = { | |
| 0x00040002, 0xf3324589, 0x14670115, 0x00000005, | |
| 0x00000000, 0x00100000, 0x06400010, 0x00000000, | |
| 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| }; | |
| struct vcap_admin admin = { | |
| .vtype = VCAP_TYPE_IS2, | |
| .first_cid = 8000000, | |
| .last_cid = 8199999, | |
| .lookups = 4, | |
| .last_valid_addr = 3071, | |
| .first_valid_addr = 0, | |
| .last_used_addr = 794, | |
| .ingress = true, | |
| .cache = { | |
| .keystream = keydata, | |
| .maskstream = mskdata, | |
| .actionstream = actdata, | |
| }, | |
| }; | |
| struct vcap_rule_internal ri = { | |
| .admin = &admin, | |
| .data = { | |
| .id = 100, | |
| .keyset = VCAP_KFS_MAC_ETYPE, | |
| .actionset = VCAP_AFS_BASE_TYPE, | |
| }, | |
| .size = 6, | |
| .keyset_sw = 6, | |
| .keyset_sw_regs = 2, | |
| .actionset_sw = 3, | |
| .actionset_sw_regs = 4, | |
| .addr = 794, | |
| .vctrl = &test_vctrl, | |
| }; | |
| struct vcap_output_print out = { | |
| .prf = (void *)test_prf, | |
| }; | |
| int ret, idx; | |
| vcap_test_api_init(&admin); | |
| list_add_tail(&ri.list, &admin.rules); | |
| ret = vcap_show_admin(&test_vctrl, &admin, &out); | |
| KUNIT_EXPECT_EQ(test, 0, ret); | |
| for (idx = 0; idx < test_pr_bufferidx; ++idx) { | |
| /* pr_info("log[%02d]: %s", idx, test_pr_buffer[idx]); */ | |
| KUNIT_EXPECT_STREQ(test, test_admin_expect[idx], | |
| test_pr_buffer[idx]); | |
| } | |
| } | |
| static struct kunit_case vcap_api_debugfs_test_cases[] = { | |
| KUNIT_CASE(vcap_api_addr_keyset_test), | |
| KUNIT_CASE(vcap_api_show_admin_raw_test), | |
| KUNIT_CASE(vcap_api_show_admin_test), | |
| KUNIT_CASE(vcap_api_show_admin_rule_test), | |
| {} | |
| }; | |
| static struct kunit_suite vcap_api_debugfs_test_suite = { | |
| .name = "VCAP_API_DebugFS_Testsuite", | |
| .test_cases = vcap_api_debugfs_test_cases, | |
| }; | |
| kunit_test_suite(vcap_api_debugfs_test_suite); |