/* * Copyright (c) 2016-2024 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include "skywalk_test_driver.h" #include "skywalk_test_common.h" #include "skywalk_test_utils.h" /************************************************************************ * Utility code */ struct context { uuid_t nexus_uuid; int argc; char **argv; int test_selector; struct stage_ctx stage; }; static void skt_kqueue_init(void) { struct sktc_nexus_attr attr = SKTC_NEXUS_ATTR_INIT(); strncpy((char *)attr.name, "skywalk_test_kqueue_upipe", sizeof(nexus_name_t) - 1); attr.type = NEXUS_TYPE_USER_PIPE; attr.ntxrings = 1; attr.nrxrings = 1; attr.ntxslots = 64; attr.nrxslots = 64; attr.anonymous = 1; sktc_setup_nexus(&attr); } static void skt_kqueue_fini(void) { sktc_cleanup_nexus(); } enum kqueue_common_test_stage { SKT_KQUEUE_INIT=0, }; static int skt_kqueue_main(int argc, char *argv[], int test_selector, void *(rx_body)(void *), void *(tx_body)(void *)) { pthread_t rx_thread, tx_thread; struct context ctx; int error; test_stage_init(&ctx.stage, SKT_KQUEUE_INIT); ctx.argc = argc; ctx.argv = argv; ctx.test_selector = test_selector; error = uuid_parse(argv[3], ctx.nexus_uuid); SKTC_ASSERT_ERR(!error); error = pthread_create(&rx_thread, NULL, rx_body, &ctx); SKTC_ASSERT_ERR(!error); error = pthread_create(&tx_thread, NULL, tx_body, &ctx); SKTC_ASSERT_ERR(!error); pthread_join(rx_thread, NULL); pthread_join(tx_thread, NULL); test_stage_destroy(&ctx.stage); return 0; } /************************************************************************ * Basic sweep test */ enum kqueue_basic_test_stage { SKT_KQUEUE_BASIC_RX_SWEEP_1=1, SKT_KQUEUE_BASIC_RX_SWEEP_2, SKT_KQUEUE_BASIC_TX_SWEEP_1, SKT_KQUEUE_BASIC_TX_SWEEP_2, SKT_KQUEUE_BASIC_TX_SWEEP_3, }; static void * skt_kqueue_basic_rx(void *ctx_) { struct context *ctx = (struct context *)ctx_; channel_t channel; channel_ring_t rxring; channel_attr_t ch_attr; ring_dir_t ring_dir = CHANNEL_DIR_RX; ring_id_t ring_id = CHANNEL_RING_ID_ANY; uint32_t port = 0; int kq_fd; int error; int i; assert(ctx->stage.test_stage == SKT_KQUEUE_INIT); /* Initialize kqueue */ kq_fd = kqueue(); assert(kq_fd >= 0); /* Initialize channel */ ch_attr = os_channel_attr_create(); channel = sktu_channel_create_extended( ctx->nexus_uuid, port, ring_dir, ring_id, ch_attr, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1); assert(channel != NULL); ring_id = os_channel_ring_id(channel, CHANNEL_FIRST_RX_RING); rxring = os_channel_rx_ring(channel, ring_id); assert(rxring); /* Wait before any data and confirm that the ring is reported as empty */ error = wait_on_fd(kq_fd, EVFILT_READ, channel, 0, TIMEOUT_EXPECT); SKTC_ASSERT_ERR(!error); /* Test basic RX data reporting, sweep from 0 to slots-1 to make sure * the ring pointers wrap at some point */ test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_1); int lim = ch_attr->cha_rx_slots - 1; for (i = 1; i <= lim; i++) { test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_2); #ifndef NDEBUG int result = #endif wait_on_fd(kq_fd, EVFILT_READ, channel, 0, TIMEOUT_FAIL); assert(result == i); chew_slots(rxring, 0); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(!error); test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_1); } test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_2); /* Get ready for TX sweep part of the test */ test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_1); /* Drain RX ring */ test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_2); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(!error); test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_1); /* Allow TX backlog to trickle in */ for (i = 1; i <= lim; i++) { test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_2); chew_slots(rxring, 1); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(!error); test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_1); } os_channel_attr_destroy(ch_attr); os_channel_destroy(channel); return 0; } static void * skt_kqueue_basic_tx(void *ctx_) { struct context *ctx = (struct context *)ctx_; channel_t channel; channel_ring_t txring; channel_attr_t ch_attr; ring_dir_t ring_dir = CHANNEL_DIR_TX; ring_id_t ring_id = CHANNEL_RING_ID_ANY; uint32_t port = 1; int error; int kq_fd; int i, j; kq_fd = kqueue(); assert(kq_fd >= 0); ch_attr = os_channel_attr_create(); channel = sktu_channel_create_extended( ctx->nexus_uuid, port, ring_dir, ring_id, ch_attr, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1); assert(channel != NULL); ring_id = os_channel_ring_id(channel, CHANNEL_FIRST_TX_RING); txring = os_channel_tx_ring(channel, ring_id); assert(txring); /* Wait for RX to initialize */ test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_1); /* Test basic RX data reporting, sweep from 0 to slots-1 to make sure * the ring pointers wrap at some point */ int lim = ch_attr->cha_tx_slots - 1; for (i = 1; i <= lim; i++) { while (os_channel_available_slot_count(txring) < i) { wait_on_fd(kq_fd, EVFILT_WRITE, channel, 0, TIMEOUT_DONT_CARE); /* Abort if we were woken up but there are no slots. * This can happen if the channel is defuncted such as * in the skywalk_shutdown tests and we'll get stuck. */ assert(os_channel_available_slot_count(txring)); } for (j = 0; j < i; j++) { send_bytes(txring, i); } error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(!error); test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_2); test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_1); } test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_2); /* Test TX data reporting - start by filling the RX ring */ test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_1); for (j = 0; j < lim; j++) { send_bytes(txring, j); } error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(!error); /* Send more packets and confirm TX backs up */ for (i = 0; i < lim; i++) { send_bytes(txring, 8); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(!error); assert(os_channel_available_slot_count(txring) == lim - i - 1); } /* Confirm we time out waiting for more slots */ error = wait_on_fd(kq_fd, EVFILT_WRITE, channel, 0, TIMEOUT_EXPECT); SKTC_ASSERT_ERR(!error); /* Start draining the rings */ test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_2); test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_1); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(!error); /* Drain the rings and confirm reporting is accurate */ for (i = 1; i <= lim; i++) { test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_2); test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_TX_SWEEP_1); error = wait_on_fd(kq_fd, EVFILT_WRITE, channel, 0, TIMEOUT_FAIL); SKTC_ASSERT_ERR(error == i); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(!error); } os_channel_attr_destroy(ch_attr); os_channel_destroy(channel); return 0; } static int skt_kqueue_basic_main(int argc, char *argv[]) { return skt_kqueue_main(argc, argv, 0, &skt_kqueue_basic_rx, &skt_kqueue_basic_tx); } /************************************************************************ * Basic lowat test */ #define LOWAT_TYPE 0x00000001 #define LOWAT_TYPE_CHAN 0x00000000 #define LOWAT_TYPE_NOTE 0x00000001 #define LOWAT_UNIT 0x00000010 #define LOWAT_UNIT_SLOTS 0x00000000 #define LOWAT_UNIT_BYTES 0x00000010 enum kqueue_lowat_basic_test_stage { SKT_KQUEUE_LOWAT_BASIC_1=1, SKT_KQUEUE_LOWAT_BASIC_2, SKT_KQUEUE_LOWAT_BASIC_3 }; static void * skt_kqueue_lowat_basic_rx(void *ctx_) { struct context *ctx = (struct context *)ctx_; channel_t channel; channel_ring_t rxring; channel_attr_t ch_attr; ring_dir_t ring_dir = CHANNEL_DIR_RX; ring_id_t ring_id = CHANNEL_RING_ID_ANY; uint32_t port = 0; int kq_fd; channel_threshold_unit_t lowat_unit; int lowat_val; int error; int i; assert(ctx->stage.test_stage == SKT_KQUEUE_INIT); /* Initialize kqueue */ kq_fd = kqueue(); assert(kq_fd >= 0); /* Initialize channel */ ch_attr = os_channel_attr_create(); channel = sktu_channel_create_extended( ctx->nexus_uuid, port, ring_dir, ring_id, ch_attr, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1); assert(channel != NULL); ring_id = os_channel_ring_id(channel, CHANNEL_FIRST_RX_RING); rxring = os_channel_rx_ring(channel, ring_id); assert(rxring); /* Set up watermarks */ if ((ctx->test_selector & LOWAT_UNIT) == LOWAT_UNIT_BYTES) { lowat_unit = CHANNEL_THRESHOLD_UNIT_BYTES; } else { lowat_unit = CHANNEL_THRESHOLD_UNIT_SLOTS; } if ((ctx->test_selector & LOWAT_TYPE) == LOWAT_TYPE_CHAN) { set_watermark(channel, false, lowat_unit, 10); set_watermark(channel, true, lowat_unit, 15); lowat_val = 0; } else { set_watermark(channel, false, lowat_unit, 1); set_watermark(channel, true, lowat_unit, 1); lowat_val = 10; } test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); /* The first N waits should time out (3 waits for slots, 1 wait for * bytes) */ int N; if (lowat_unit == CHANNEL_THRESHOLD_UNIT_SLOTS) { N = 3; } else { N = 1; } for (i = 0; i < N; i++) { error = wait_on_fd(kq_fd, EVFILT_READ, channel, lowat_val, TIMEOUT_EXPECT); SKTC_ASSERT_ERR(error == 0); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(error == 0); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); } /* The next wait should trigger */ error = wait_on_fd(kq_fd, EVFILT_READ, channel, lowat_val, TIMEOUT_FAIL); SKTC_ASSERT_ERR(error == 12); chew_slots(rxring, 0); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(error == 0); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); /* Do it all again, but make sure the TX thread transmits while * we're waiting this time */ /* (TODO: is there a better way to do this, without the TX thread * sleeping an arbitrary amount of time?) */ error = wait_on_fd(kq_fd, EVFILT_READ, channel, lowat_val, TIMEOUT_DISABLE); SKTC_ASSERT_ERR(error == 12); chew_slots(rxring, 0); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(error == 0); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); /* Let the TX thread fill our RX ring up, to test TX watermarks */ test_stage_wait(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_2); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(error == 0); /* Free up some slots for TX, enough to trigger its TX watermark but * not its RX watermark */ for (i = 0; i < 5; i++) { chew_slots(rxring, 1); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(error == 0); } test_stage_change(&ctx->stage, SKT_KQUEUE_BASIC_RX_SWEEP_1); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); /* Chew more slots to wake TX up, but wait a bit first to make sure TX * is really sleeping (TODO: better way to do this?) */ usleep(50000); for (i = 0; i < 5; i++) { chew_slots(rxring, 1); error = os_channel_sync(channel, CHANNEL_SYNC_RX); SKTC_ASSERT_ERR(error == 0); } os_channel_attr_destroy(ch_attr); os_channel_destroy(channel); return 0; } static void * skt_kqueue_lowat_basic_tx(void *ctx_) { struct context *ctx = (struct context *)ctx_; channel_t channel; channel_ring_t txring; channel_attr_t ch_attr; ring_dir_t ring_dir = CHANNEL_DIR_TX; ring_id_t ring_id = CHANNEL_RING_ID_ANY; uint32_t port = 1; int kq_fd; channel_threshold_unit_t lowat_unit; int lowat_val; int note_lowat_val; int slot_size; int error; int i; kq_fd = kqueue(); assert(kq_fd >= 0); ch_attr = os_channel_attr_create(); channel = sktu_channel_create_extended( ctx->nexus_uuid, port, ring_dir, ring_id, ch_attr, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1); assert(channel != NULL); slot_size = channel->chd_info->cinfo_nxprov_params.nxp_buf_size; ring_id = os_channel_ring_id(channel, CHANNEL_FIRST_TX_RING); txring = os_channel_tx_ring(channel, ring_id); assert(txring); if ((ctx->test_selector & LOWAT_UNIT) == LOWAT_UNIT_BYTES) { lowat_unit = CHANNEL_THRESHOLD_UNIT_BYTES; } else { lowat_unit = CHANNEL_THRESHOLD_UNIT_SLOTS; } /* Wait for RX to initialize */ test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); /* Send packets in bursts of 3 to allow RX to confirm its watermark is * being respected */ int N; if (lowat_unit == CHANNEL_THRESHOLD_UNIT_SLOTS) { N = 4; } else { N = 2; } for (i = 0; i < N; i++) { send_bytes(txring, 2); send_bytes(txring, 2); send_bytes(txring, 2); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(error == 0); test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); } /* Send packets in bursts of 3 again, but wait a bit to give the RX * thread time to wait (TODO: is there a better way to do this, without * sleeping an arbitrary amount of time?) */ usleep(50000); for (i = 0; i < N; i++) { send_bytes(txring, 2); send_bytes(txring, 2); send_bytes(txring, 2); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(error == 0); } test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(error == 0); /* Test TX watermarks - start by filling the RX ring */ int lim = ch_attr->cha_tx_slots - 1; for (i = 0; i < lim; i++) { send_bytes(txring, 5); } error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(error == 0); /* Send more packets and confirm TX backs up _almost_ all the way */ for (i = 0; i < lim - 5; i++) { send_bytes(txring, 8); error = os_channel_sync(channel, CHANNEL_SYNC_TX); SKTC_ASSERT_ERR(error == 0); assert(os_channel_available_slot_count(txring) == lim - i - 1); } /* Set up watermarks */ lowat_val = 10; if (lowat_unit == CHANNEL_THRESHOLD_UNIT_BYTES) { lowat_val *= slot_size; } if ((ctx->test_selector & LOWAT_TYPE) == LOWAT_TYPE_CHAN) { set_watermark(channel, false, lowat_unit, lowat_val * 2); set_watermark(channel, true, lowat_unit, lowat_val); note_lowat_val = 0; } else { set_watermark(channel, false, lowat_unit, 1); set_watermark(channel, true, lowat_unit, 1); note_lowat_val = lowat_val; } /* Wait for TX slots, confirm that even though some are available * there aren't enough to trigger the watermark */ assert(os_channel_available_slot_count(txring) > 0); error = wait_on_fd(kq_fd, EVFILT_WRITE, channel, note_lowat_val, TIMEOUT_EXPECT); SKTC_ASSERT_ERR(error == 0); /* Let the RX thread drain some slots and try again */ test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); test_stage_wait(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_1); error = wait_on_fd(kq_fd, EVFILT_WRITE, channel, note_lowat_val, TIMEOUT_FAIL); assert(error == lowat_val); /* Do it all again, but make sure RX triggers the watermark _while_ * we're sleeping this time */ /* TODO: is there a better way to do this than having the RX * thread sleep? */ lowat_val = 15; if (lowat_unit == CHANNEL_THRESHOLD_UNIT_BYTES) { lowat_val *= slot_size; } if ((ctx->test_selector & LOWAT_TYPE) == LOWAT_TYPE_CHAN) { set_watermark(channel, false, lowat_unit, lowat_val * 2); set_watermark(channel, true, lowat_unit, lowat_val); note_lowat_val = 0; } else { set_watermark(channel, false, lowat_unit, 1); set_watermark(channel, true, lowat_unit, 1); note_lowat_val = lowat_val; } test_stage_change(&ctx->stage, SKT_KQUEUE_LOWAT_BASIC_2); error = wait_on_fd(kq_fd, EVFILT_WRITE, channel, note_lowat_val, TIMEOUT_DISABLE); SKTC_ASSERT_ERR(error == lowat_val); os_channel_attr_destroy(ch_attr); os_channel_destroy(channel); return 0; } static int skt_kqueue_lowat_chan_bytes_main(int argc, char *argv[]) { return skt_kqueue_main(argc, argv, LOWAT_TYPE_CHAN | LOWAT_UNIT_BYTES, &skt_kqueue_lowat_basic_rx, &skt_kqueue_lowat_basic_tx); } static int skt_kqueue_lowat_chan_slots_main(int argc, char *argv[]) { return skt_kqueue_main(argc, argv, LOWAT_TYPE_CHAN | LOWAT_UNIT_SLOTS, &skt_kqueue_lowat_basic_rx, &skt_kqueue_lowat_basic_tx); } static int skt_kqueue_lowat_note_bytes_main(int argc, char *argv[]) { return skt_kqueue_main(argc, argv, LOWAT_TYPE_NOTE | LOWAT_UNIT_BYTES, &skt_kqueue_lowat_basic_rx, &skt_kqueue_lowat_basic_tx); } static int skt_kqueue_lowat_note_slots_main(int argc, char *argv[]) { return skt_kqueue_main(argc, argv, LOWAT_TYPE_NOTE | LOWAT_UNIT_SLOTS, &skt_kqueue_lowat_basic_rx, &skt_kqueue_lowat_basic_tx); } /**************************************************************** * TODO: Tests to write: * - kqueue_selwakeup - Test that wakeups associated with one channel * descriptor don't spuriously wake up other threads waiting on other * channels, or on channel descriptors that don't include the rings * relevant to the waking event * - lowat_edge - Test edge cases related to low watermarks: changing the * unit while running, wakeups as a result of that unit change, issuing * the watermark in the knote vs the channel, etc */ struct skywalk_test skt_kqueue_basic = { "kqueue_basic", "tests kqueue return values", SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_USER_PIPE, skt_kqueue_basic_main, SKTC_GENERIC_UPIPE_ARGV, skt_kqueue_init, skt_kqueue_fini, }; struct skywalk_test skt_kqueue_lowat_chan_bytes = { "kqueue_lowat_chan_bytes", "tests kqueue low watermark (byte watermark on channel)", SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_USER_PIPE, skt_kqueue_lowat_chan_bytes_main, SKTC_GENERIC_UPIPE_ARGV, skt_kqueue_init, skt_kqueue_fini, }; struct skywalk_test skt_kqueue_lowat_chan_slots = { "kqueue_lowat_chan_slots", "tests kqueue low watermark (slot watermark on channel)", SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_USER_PIPE, skt_kqueue_lowat_chan_slots_main, SKTC_GENERIC_UPIPE_ARGV, skt_kqueue_init, skt_kqueue_fini, }; struct skywalk_test skt_kqueue_lowat_note_bytes = { "kqueue_lowat_note_bytes", "tests kqueue low watermark (byte watermark on knote)", SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_USER_PIPE, skt_kqueue_lowat_note_bytes_main, SKTC_GENERIC_UPIPE_ARGV, skt_kqueue_init, skt_kqueue_fini, }; struct skywalk_test skt_kqueue_lowat_note_slots = { "kqueue_lowat_note_slots", "tests kqueue low watermark (slot watermark on knote)", SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_USER_PIPE, skt_kqueue_lowat_note_slots_main, SKTC_GENERIC_UPIPE_ARGV, skt_kqueue_init, skt_kqueue_fini, }; /****************************************************************/