get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/73346/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 73346,
    "url": "https://patches.dpdk.org/api/patches/73346/?format=api",
    "web_url": "https://patches.dpdk.org/project/dpdk/patch/20200706205234.8040-8-david.marchand@redhat.com/",
    "project": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20200706205234.8040-8-david.marchand@redhat.com>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20200706205234.8040-8-david.marchand@redhat.com",
    "date": "2020-07-06T20:52:31",
    "name": "[v6,07/10] eal: add lcore init callbacks",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": true,
    "hash": "3dfa923acc79e0be04f7a7c09b73d8ed02e3969d",
    "submitter": {
        "id": 1173,
        "url": "https://patches.dpdk.org/api/people/1173/?format=api",
        "name": "David Marchand",
        "email": "david.marchand@redhat.com"
    },
    "delegate": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "https://patches.dpdk.org/project/dpdk/patch/20200706205234.8040-8-david.marchand@redhat.com/mbox/",
    "series": [
        {
            "id": 10826,
            "url": "https://patches.dpdk.org/api/series/10826/?format=api",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=10826",
            "date": "2020-07-06T20:52:25",
            "name": "Register non-EAL threads as lcore",
            "version": 6,
            "mbox": "https://patches.dpdk.org/series/10826/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/73346/comments/",
    "check": "fail",
    "checks": "https://patches.dpdk.org/api/patches/73346/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from dpdk.org (dpdk.org [92.243.14.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 8D229A00C5;\n\tMon,  6 Jul 2020 22:54:05 +0200 (CEST)",
            "from [92.243.14.124] (localhost [127.0.0.1])\n\tby dpdk.org (Postfix) with ESMTP id 5064C1DDDB;\n\tMon,  6 Jul 2020 22:53:32 +0200 (CEST)",
            "from us-smtp-delivery-1.mimecast.com (us-smtp-2.mimecast.com\n [207.211.31.81]) by dpdk.org (Postfix) with ESMTP id B92181DDD3\n for <dev@dpdk.org>; Mon,  6 Jul 2020 22:53:30 +0200 (CEST)",
            "from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com\n [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id\n us-mta-23-ELdl3-PyOSmmDV87xG-JWw-1; Mon, 06 Jul 2020 16:53:28 -0400",
            "from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com\n [10.5.11.22])\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 2099F8014D4;\n Mon,  6 Jul 2020 20:53:26 +0000 (UTC)",
            "from dmarchan.remote.csb (unknown [10.40.195.188])\n by smtp.corp.redhat.com (Postfix) with ESMTP id 1BEEC10013D7;\n Mon,  6 Jul 2020 20:53:21 +0000 (UTC)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1594068810;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=FMrx449Dp+quLDBDMJF8y8dcyqGH2sxdRWZlr/F+WV0=;\n b=FTiNu3vH/jm9M6m5zQSGYhP9202pyOi+ftGpsYZTYJVJ6QmR3xthtGbBZbITZIqKjjdL3C\n AFg56l8LVAjpqL9HcBoT7DXsL14uNopgl7DN1Tq+LKFJ+uz9Xg4Ipb0ybp+9ucJYqkrNsP\n mBwtZewGQF4DaW3X3SYzwsRBMOXV3a0=",
        "X-MC-Unique": "ELdl3-PyOSmmDV87xG-JWw-1",
        "From": "David Marchand <david.marchand@redhat.com>",
        "To": "dev@dpdk.org",
        "Cc": "jerinjacobk@gmail.com, bruce.richardson@intel.com, mdr@ashroe.eu,\n thomas@monjalon.net, arybchenko@solarflare.com, ktraynor@redhat.com,\n ian.stokes@intel.com, i.maximets@ovn.org, olivier.matz@6wind.com,\n konstantin.ananyev@intel.com, Neil Horman <nhorman@tuxdriver.com>",
        "Date": "Mon,  6 Jul 2020 22:52:31 +0200",
        "Message-Id": "<20200706205234.8040-8-david.marchand@redhat.com>",
        "In-Reply-To": "<20200706205234.8040-1-david.marchand@redhat.com>",
        "References": "<20200610144506.30505-1-david.marchand@redhat.com>\n <20200706205234.8040-1-david.marchand@redhat.com>",
        "MIME-Version": "1.0",
        "X-Scanned-By": "MIMEDefang 2.84 on 10.5.11.22",
        "Authentication-Results": "relay.mimecast.com;\n auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=david.marchand@redhat.com",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-Originator": "redhat.com",
        "Content-Type": "text/plain; charset=US-ASCII",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[dpdk-dev] [PATCH v6 07/10] eal: add lcore init callbacks",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.15",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org",
        "Sender": "\"dev\" <dev-bounces@dpdk.org>"
    },
    "content": "DPDK components and applications can have their say when a new lcore is\ninitialized. For this, they can register a callback for initializing and\nreleasing their private data.\n\nSigned-off-by: David Marchand <david.marchand@redhat.com>\n---\nChanges since v4:\n- fixed leak on callback register failure,\n- fixed nits from Konstantin and Olivier,\n- prefixed unit tests logs with Error: when applicable,\n\nChanges since v2:\n- added missing test,\n- fixed rollback on lcore register,\n\nChanges since v1:\n- added unit test (since missing some coverage, for v3),\n- preferred callback and removed mention of notification,\n\n---\n app/test/test_lcores.c                   | 227 +++++++++++++++++++++++\n lib/librte_eal/common/eal_common_lcore.c | 146 ++++++++++++++-\n lib/librte_eal/common/eal_private.h      |   3 +-\n lib/librte_eal/include/rte_lcore.h       |  70 +++++++\n lib/librte_eal/rte_eal_version.map       |   2 +\n 5 files changed, 442 insertions(+), 6 deletions(-)",
    "diff": "diff --git a/app/test/test_lcores.c b/app/test/test_lcores.c\nindex afb9cdd444..7df827b4e8 100644\n--- a/app/test/test_lcores.c\n+++ b/app/test/test_lcores.c\n@@ -5,6 +5,7 @@\n #include <pthread.h>\n #include <string.h>\n \n+#include <rte_common.h>\n #include <rte_errno.h>\n #include <rte_lcore.h>\n \n@@ -117,6 +118,226 @@ test_non_eal_lcores(unsigned int eal_threads_count)\n \treturn ret;\n }\n \n+struct limit_lcore_context {\n+\tunsigned int init;\n+\tunsigned int max;\n+\tunsigned int uninit;\n+};\n+\n+static int\n+limit_lcores_init(unsigned int lcore_id __rte_unused, void *arg)\n+{\n+\tstruct limit_lcore_context *l = arg;\n+\n+\tl->init++;\n+\tif (l->init > l->max)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static void\n+limit_lcores_uninit(unsigned int lcore_id __rte_unused, void *arg)\n+{\n+\tstruct limit_lcore_context *l = arg;\n+\n+\tl->uninit++;\n+}\n+\n+static int\n+test_lcores_callback(unsigned int eal_threads_count)\n+{\n+\tstruct limit_lcore_context l;\n+\tvoid *handle;\n+\n+\t/* Refuse last lcore => callback register error. */\n+\tmemset(&l, 0, sizeof(l));\n+\tl.max = eal_threads_count - 1;\n+\thandle = rte_lcore_callback_register(\"limit\", limit_lcores_init,\n+\t\tlimit_lcores_uninit, &l);\n+\tif (handle != NULL) {\n+\t\tprintf(\"Error: lcore callback register should have failed\\n\");\n+\t\tgoto error;\n+\t}\n+\t/* Refusal happens at the n th call to the init callback.\n+\t * Besides, n - 1 were accepted, so we expect as many uninit calls when\n+\t * the rollback happens.\n+\t */\n+\tif (l.init != eal_threads_count) {\n+\t\tprintf(\"Error: lcore callback register failed but incorrect init calls, expected %u, got %u\\n\",\n+\t\t\teal_threads_count, l.init);\n+\t\tgoto error;\n+\t}\n+\tif (l.uninit != eal_threads_count - 1) {\n+\t\tprintf(\"Error: lcore callback register failed but incorrect uninit calls, expected %u, got %u\\n\",\n+\t\t\teal_threads_count - 1, l.uninit);\n+\t\tgoto error;\n+\t}\n+\n+\t/* Accept all lcore and unregister. */\n+\tmemset(&l, 0, sizeof(l));\n+\tl.max = eal_threads_count;\n+\thandle = rte_lcore_callback_register(\"limit\", limit_lcores_init,\n+\t\tlimit_lcores_uninit, &l);\n+\tif (handle == NULL) {\n+\t\tprintf(\"Error: lcore callback register failed\\n\");\n+\t\tgoto error;\n+\t}\n+\tif (l.uninit != 0) {\n+\t\tprintf(\"Error: lcore callback register succeeded but incorrect uninit calls, expected 0, got %u\\n\",\n+\t\t\tl.uninit);\n+\t\tgoto error;\n+\t}\n+\trte_lcore_callback_unregister(handle);\n+\thandle = NULL;\n+\tif (l.init != eal_threads_count) {\n+\t\tprintf(\"Error: lcore callback unregister done but incorrect init calls, expected %u, got %u\\n\",\n+\t\t\teal_threads_count, l.init);\n+\t\tgoto error;\n+\t}\n+\tif (l.uninit != eal_threads_count) {\n+\t\tprintf(\"Error: lcore callback unregister done but incorrect uninit calls, expected %u, got %u\\n\",\n+\t\t\teal_threads_count, l.uninit);\n+\t\tgoto error;\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tif (handle != NULL)\n+\t\trte_lcore_callback_unregister(handle);\n+\n+\treturn -1;\n+}\n+\n+static int\n+test_non_eal_lcores_callback(unsigned int eal_threads_count)\n+{\n+\tstruct thread_context thread_contexts[2];\n+\tunsigned int non_eal_threads_count = 0;\n+\tstruct limit_lcore_context l[2] = {};\n+\tunsigned int registered_count = 0;\n+\tstruct thread_context *t;\n+\tvoid *handle[2] = {};\n+\tunsigned int i;\n+\tint ret;\n+\n+\t/* This test requires two empty slots to be sure lcore init refusal is\n+\t * because of callback execution.\n+\t */\n+\tif (eal_threads_count + 2 >= RTE_MAX_LCORE)\n+\t\treturn 0;\n+\n+\t/* Register two callbacks:\n+\t * - first one accepts any lcore,\n+\t * - second one accepts all EAL lcore + one more for the first non-EAL\n+\t *   thread, then refuses the next lcore.\n+\t */\n+\tl[0].max = UINT_MAX;\n+\thandle[0] = rte_lcore_callback_register(\"no_limit\", limit_lcores_init,\n+\t\tlimit_lcores_uninit, &l[0]);\n+\tif (handle[0] == NULL) {\n+\t\tprintf(\"Error: lcore callback [0] register failed\\n\");\n+\t\tgoto error;\n+\t}\n+\tl[1].max = eal_threads_count + 1;\n+\thandle[1] = rte_lcore_callback_register(\"limit\", limit_lcores_init,\n+\t\tlimit_lcores_uninit, &l[1]);\n+\tif (handle[1] == NULL) {\n+\t\tprintf(\"Error: lcore callback [1] register failed\\n\");\n+\t\tgoto error;\n+\t}\n+\tif (l[0].init != eal_threads_count || l[1].init != eal_threads_count) {\n+\t\tprintf(\"Error: lcore callbacks register succeeded but incorrect init calls, expected %u, %u, got %u, %u\\n\",\n+\t\t\teal_threads_count, eal_threads_count,\n+\t\t\tl[0].init, l[1].init);\n+\t\tgoto error;\n+\t}\n+\tif (l[0].uninit != 0 || l[1].uninit != 0) {\n+\t\tprintf(\"Error: lcore callbacks register succeeded but incorrect uninit calls, expected 0, 1, got %u, %u\\n\",\n+\t\t\tl[0].uninit, l[1].uninit);\n+\t\tgoto error;\n+\t}\n+\t/* First thread that expects a valid lcore id. */\n+\tt = &thread_contexts[0];\n+\tt->state = INIT;\n+\tt->registered_count = &registered_count;\n+\tt->lcore_id_any = false;\n+\tif (pthread_create(&t->id, NULL, thread_loop, t) != 0)\n+\t\tgoto cleanup_threads;\n+\tnon_eal_threads_count++;\n+\twhile (__atomic_load_n(&registered_count, __ATOMIC_ACQUIRE) !=\n+\t\t\tnon_eal_threads_count)\n+\t\t;\n+\tif (l[0].init != eal_threads_count + 1 ||\n+\t\t\tl[1].init != eal_threads_count + 1) {\n+\t\tprintf(\"Error: incorrect init calls, expected %u, %u, got %u, %u\\n\",\n+\t\t\teal_threads_count + 1, eal_threads_count + 1,\n+\t\t\tl[0].init, l[1].init);\n+\t\tgoto cleanup_threads;\n+\t}\n+\tif (l[0].uninit != 0 || l[1].uninit != 0) {\n+\t\tprintf(\"Error: incorrect uninit calls, expected 0, 0, got %u, %u\\n\",\n+\t\t\tl[0].uninit, l[1].uninit);\n+\t\tgoto cleanup_threads;\n+\t}\n+\t/* Second thread, that expects LCORE_ID_ANY because of init refusal. */\n+\tt = &thread_contexts[1];\n+\tt->state = INIT;\n+\tt->registered_count = &registered_count;\n+\tt->lcore_id_any = true;\n+\tif (pthread_create(&t->id, NULL, thread_loop, t) != 0)\n+\t\tgoto cleanup_threads;\n+\tnon_eal_threads_count++;\n+\twhile (__atomic_load_n(&registered_count, __ATOMIC_ACQUIRE) !=\n+\t\t\tnon_eal_threads_count)\n+\t\t;\n+\tif (l[0].init != eal_threads_count + 2 ||\n+\t\t\tl[1].init != eal_threads_count + 2) {\n+\t\tprintf(\"Error: incorrect init calls, expected %u, %u, got %u, %u\\n\",\n+\t\t\teal_threads_count + 2, eal_threads_count + 2,\n+\t\t\tl[0].init, l[1].init);\n+\t\tgoto cleanup_threads;\n+\t}\n+\tif (l[0].uninit != 1 || l[1].uninit != 0) {\n+\t\tprintf(\"Error: incorrect uninit calls, expected 1, 0, got %u, %u\\n\",\n+\t\t\tl[0].uninit, l[1].uninit);\n+\t\tgoto cleanup_threads;\n+\t}\n+\t/* Release all threads, and check their states. */\n+\t__atomic_store_n(&registered_count, 0, __ATOMIC_RELEASE);\n+\tret = 0;\n+\tfor (i = 0; i < non_eal_threads_count; i++) {\n+\t\tt = &thread_contexts[i];\n+\t\tpthread_join(t->id, NULL);\n+\t\tif (t->state != DONE)\n+\t\t\tret = -1;\n+\t}\n+\tif (ret < 0)\n+\t\tgoto error;\n+\tif (l[0].uninit != 2 || l[1].uninit != 1) {\n+\t\tprintf(\"Error: threads reported having successfully registered and unregistered, but incorrect uninit calls, expected 2, 1, got %u, %u\\n\",\n+\t\t\tl[0].uninit, l[1].uninit);\n+\t\tgoto error;\n+\t}\n+\trte_lcore_callback_unregister(handle[0]);\n+\trte_lcore_callback_unregister(handle[1]);\n+\treturn 0;\n+\n+cleanup_threads:\n+\t/* Release all threads */\n+\t__atomic_store_n(&registered_count, 0, __ATOMIC_RELEASE);\n+\tfor (i = 0; i < non_eal_threads_count; i++) {\n+\t\tt = &thread_contexts[i];\n+\t\tpthread_join(t->id, NULL);\n+\t}\n+error:\n+\tif (handle[1] != NULL)\n+\t\trte_lcore_callback_unregister(handle[1]);\n+\tif (handle[0] != NULL)\n+\t\trte_lcore_callback_unregister(handle[0]);\n+\treturn -1;\n+}\n+\n static int\n test_lcores(void)\n {\n@@ -137,6 +358,12 @@ test_lcores(void)\n \tif (test_non_eal_lcores(eal_threads_count) < 0)\n \t\treturn TEST_FAILED;\n \n+\tif (test_lcores_callback(eal_threads_count) < 0)\n+\t\treturn TEST_FAILED;\n+\n+\tif (test_non_eal_lcores_callback(eal_threads_count) < 0)\n+\t\treturn TEST_FAILED;\n+\n \treturn TEST_SUCCESS;\n }\n \ndiff --git a/lib/librte_eal/common/eal_common_lcore.c b/lib/librte_eal/common/eal_common_lcore.c\nindex 2b7d262372..90139c77ff 100644\n--- a/lib/librte_eal/common/eal_common_lcore.c\n+++ b/lib/librte_eal/common/eal_common_lcore.c\n@@ -224,11 +224,122 @@ rte_socket_id_by_idx(unsigned int idx)\n }\n \n static rte_spinlock_t lcore_lock = RTE_SPINLOCK_INITIALIZER;\n+struct lcore_callback {\n+\tTAILQ_ENTRY(lcore_callback) next;\n+\tchar *name;\n+\trte_lcore_init_cb init;\n+\trte_lcore_uninit_cb uninit;\n+\tvoid *arg;\n+};\n+static TAILQ_HEAD(lcore_callbacks_head, lcore_callback) lcore_callbacks =\n+\tTAILQ_HEAD_INITIALIZER(lcore_callbacks);\n+\n+static int\n+callback_init(struct lcore_callback *callback, unsigned int lcore_id)\n+{\n+\tif (callback->init == NULL)\n+\t\treturn 0;\n+\tRTE_LOG(DEBUG, EAL, \"Call init for lcore callback %s, lcore_id %u\\n\",\n+\t\tcallback->name, lcore_id);\n+\treturn callback->init(lcore_id, callback->arg);\n+}\n+\n+static void\n+callback_uninit(struct lcore_callback *callback, unsigned int lcore_id)\n+{\n+\tif (callback->uninit == NULL)\n+\t\treturn;\n+\tRTE_LOG(DEBUG, EAL, \"Call uninit for lcore callback %s, lcore_id %u\\n\",\n+\t\tcallback->name, lcore_id);\n+\tcallback->uninit(lcore_id, callback->arg);\n+}\n+\n+static void\n+free_callback(struct lcore_callback *callback)\n+{\n+\tfree(callback->name);\n+\tfree(callback);\n+}\n+\n+void *\n+rte_lcore_callback_register(const char *name, rte_lcore_init_cb init,\n+\trte_lcore_uninit_cb uninit, void *arg)\n+{\n+\tstruct rte_config *cfg = rte_eal_get_configuration();\n+\tstruct lcore_callback *callback;\n+\tunsigned int lcore_id;\n+\n+\tif (name == NULL)\n+\t\treturn NULL;\n+\tcallback = calloc(1, sizeof(*callback));\n+\tif (callback == NULL)\n+\t\treturn NULL;\n+\tif (asprintf(&callback->name, \"%s-%p\", name, arg) == -1) {\n+\t\tfree(callback);\n+\t\treturn NULL;\n+\t}\n+\tcallback->init = init;\n+\tcallback->uninit = uninit;\n+\tcallback->arg = arg;\n+\trte_spinlock_lock(&lcore_lock);\n+\tif (callback->init == NULL)\n+\t\tgoto no_init;\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\tif (cfg->lcore_role[lcore_id] == ROLE_OFF)\n+\t\t\tcontinue;\n+\t\tif (callback_init(callback, lcore_id) == 0)\n+\t\t\tcontinue;\n+\t\t/* Callback refused init for this lcore, uninitialize all\n+\t\t * previous lcore.\n+\t\t */\n+\t\twhile (lcore_id-- != 0) {\n+\t\t\tif (cfg->lcore_role[lcore_id] == ROLE_OFF)\n+\t\t\t\tcontinue;\n+\t\t\tcallback_uninit(callback, lcore_id);\n+\t\t}\n+\t\tfree_callback(callback);\n+\t\tcallback = NULL;\n+\t\tgoto out;\n+\t}\n+no_init:\n+\tTAILQ_INSERT_TAIL(&lcore_callbacks, callback, next);\n+\tRTE_LOG(DEBUG, EAL, \"Registered new lcore callback %s (%sinit, %suninit).\\n\",\n+\t\tcallback->name, callback->init == NULL ? \"NO \" : \"\",\n+\t\tcallback->uninit == NULL ? \"NO \" : \"\");\n+out:\n+\trte_spinlock_unlock(&lcore_lock);\n+\treturn callback;\n+}\n+\n+void\n+rte_lcore_callback_unregister(void *handle)\n+{\n+\tstruct rte_config *cfg = rte_eal_get_configuration();\n+\tstruct lcore_callback *callback = handle;\n+\tunsigned int lcore_id;\n+\n+\trte_spinlock_lock(&lcore_lock);\n+\tif (callback->uninit == NULL)\n+\t\tgoto no_uninit;\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\tif (cfg->lcore_role[lcore_id] == ROLE_OFF)\n+\t\t\tcontinue;\n+\t\tcallback_uninit(callback, lcore_id);\n+\t}\n+no_uninit:\n+\tTAILQ_REMOVE(&lcore_callbacks, callback, next);\n+\trte_spinlock_unlock(&lcore_lock);\n+\tRTE_LOG(DEBUG, EAL, \"Unregistered lcore callback %s-%p.\\n\",\n+\t\tcallback->name, callback->arg);\n+\tfree_callback(callback);\n+}\n \n unsigned int\n eal_lcore_non_eal_allocate(void)\n {\n \tstruct rte_config *cfg = rte_eal_get_configuration();\n+\tstruct lcore_callback *callback;\n+\tstruct lcore_callback *prev;\n \tunsigned int lcore_id;\n \n \trte_spinlock_lock(&lcore_lock);\n@@ -239,8 +350,29 @@ eal_lcore_non_eal_allocate(void)\n \t\tcfg->lcore_count++;\n \t\tbreak;\n \t}\n-\tif (lcore_id == RTE_MAX_LCORE)\n+\tif (lcore_id == RTE_MAX_LCORE) {\n \t\tRTE_LOG(DEBUG, EAL, \"No lcore available.\\n\");\n+\t\tgoto out;\n+\t}\n+\tTAILQ_FOREACH(callback, &lcore_callbacks, next) {\n+\t\tif (callback_init(callback, lcore_id) == 0)\n+\t\t\tcontinue;\n+\t\t/* Callback refused init for this lcore, call uninit for all\n+\t\t * previous callbacks.\n+\t\t */\n+\t\tprev = TAILQ_PREV(callback, lcore_callbacks_head, next);\n+\t\twhile (prev != NULL) {\n+\t\t\tcallback_uninit(prev, lcore_id);\n+\t\t\tprev = TAILQ_PREV(prev, lcore_callbacks_head, next);\n+\t\t}\n+\t\tRTE_LOG(DEBUG, EAL, \"Initialization refused for lcore %u.\\n\",\n+\t\t\tlcore_id);\n+\t\tcfg->lcore_role[lcore_id] = ROLE_OFF;\n+\t\tcfg->lcore_count--;\n+\t\tlcore_id = RTE_MAX_LCORE;\n+\t\tgoto out;\n+\t}\n+out:\n \trte_spinlock_unlock(&lcore_lock);\n \treturn lcore_id;\n }\n@@ -249,11 +381,15 @@ void\n eal_lcore_non_eal_release(unsigned int lcore_id)\n {\n \tstruct rte_config *cfg = rte_eal_get_configuration();\n+\tstruct lcore_callback *callback;\n \n \trte_spinlock_lock(&lcore_lock);\n-\tif (cfg->lcore_role[lcore_id] == ROLE_NON_EAL) {\n-\t\tcfg->lcore_role[lcore_id] = ROLE_OFF;\n-\t\tcfg->lcore_count--;\n-\t}\n+\tif (cfg->lcore_role[lcore_id] != ROLE_NON_EAL)\n+\t\tgoto out;\n+\tTAILQ_FOREACH(callback, &lcore_callbacks, next)\n+\t\tcallback_uninit(callback, lcore_id);\n+\tcfg->lcore_role[lcore_id] = ROLE_OFF;\n+\tcfg->lcore_count--;\n+out:\n \trte_spinlock_unlock(&lcore_lock);\n }\ndiff --git a/lib/librte_eal/common/eal_private.h b/lib/librte_eal/common/eal_private.h\nindex e82fb80aa0..535e008474 100644\n--- a/lib/librte_eal/common/eal_private.h\n+++ b/lib/librte_eal/common/eal_private.h\n@@ -401,7 +401,8 @@ uint64_t get_tsc_freq_arch(void);\n  *\n  * @return\n  *   - the id of a lcore with role ROLE_NON_EAL on success.\n- *   - RTE_MAX_LCORE if none was available.\n+ *   - RTE_MAX_LCORE if none was available or initializing was refused (see\n+ *     rte_lcore_callback_register).\n  */\n unsigned int eal_lcore_non_eal_allocate(void);\n \ndiff --git a/lib/librte_eal/include/rte_lcore.h b/lib/librte_eal/include/rte_lcore.h\nindex 2fd1a03275..6e7206c79f 100644\n--- a/lib/librte_eal/include/rte_lcore.h\n+++ b/lib/librte_eal/include/rte_lcore.h\n@@ -229,6 +229,76 @@ unsigned int rte_get_next_lcore(unsigned int i, int skip_master, int wrap);\n \t     i<RTE_MAX_LCORE;\t\t\t\t\t\t\\\n \t     i = rte_get_next_lcore(i, 1, 0))\n \n+/**\n+ * Callback prototype for initializing lcores.\n+ *\n+ * @param lcore_id\n+ *   The lcore to consider.\n+ * @param arg\n+ *   An opaque pointer passed at callback registration.\n+ * @return\n+ *   - -1 when refusing this operation,\n+ *   - 0 otherwise.\n+ */\n+typedef int (*rte_lcore_init_cb)(unsigned int lcore_id, void *arg);\n+\n+/**\n+ * Callback prototype for uninitializing lcores.\n+ *\n+ * @param lcore_id\n+ *   The lcore to consider.\n+ * @param arg\n+ *   An opaque pointer passed at callback registration.\n+ */\n+typedef void (*rte_lcore_uninit_cb)(unsigned int lcore_id, void *arg);\n+\n+/**\n+ * Register callbacks invoked when initializing and uninitializing a lcore.\n+ *\n+ * This function calls the init callback with all initialized lcores.\n+ * Any error reported by the init callback triggers a rollback calling the\n+ * uninit callback for each lcore.\n+ * If this step succeeds, the callbacks are put in the lcore callbacks list\n+ * that will get called for each lcore allocation/release.\n+ *\n+ * Note: callbacks execution is serialised under a lock protecting the lcores\n+ * and callbacks list.\n+ *\n+ * @param name\n+ *   A name serving as a small description for this callback.\n+ * @param init\n+ *   The callback invoked when a lcore_id is initialized.\n+ *   init can be NULL.\n+ * @param uninit\n+ *   The callback invoked when a lcore_id is uninitialized.\n+ *   uninit can be NULL.\n+ * @param arg\n+ *   An optional argument that gets passed to the callback when it gets\n+ *   invoked.\n+ * @return\n+ *   On success, returns an opaque pointer for the registered object.\n+ *   On failure (either memory allocation issue in the function itself or an\n+ *   error is returned by the init callback itself), returns NULL.\n+ */\n+__rte_experimental\n+void *\n+rte_lcore_callback_register(const char *name, rte_lcore_init_cb init,\n+\trte_lcore_uninit_cb uninit, void *arg);\n+\n+/**\n+ * Unregister callbacks previously registered with rte_lcore_callback_register.\n+ *\n+ * This function calls the uninit callback with all initialized lcores.\n+ * The callbacks are then removed from the lcore callbacks list.\n+ *\n+ * @param handle\n+ *   The handle pointer returned by a former successful call to\n+ *   rte_lcore_callback_register.\n+ */\n+__rte_experimental\n+void\n+rte_lcore_callback_unregister(void *handle);\n+\n /**\n  * Set core affinity of the current thread.\n  * Support both EAL and non-EAL thread and update TLS.\ndiff --git a/lib/librte_eal/rte_eal_version.map b/lib/librte_eal/rte_eal_version.map\nindex 5503dd7620..c3e762c1d9 100644\n--- a/lib/librte_eal/rte_eal_version.map\n+++ b/lib/librte_eal/rte_eal_version.map\n@@ -395,6 +395,8 @@ EXPERIMENTAL {\n \trte_trace_save;\n \n \t# added in 20.08\n+\trte_lcore_callback_register;\n+\trte_lcore_callback_unregister;\n \trte_thread_register;\n \trte_thread_unregister;\n };\n",
    "prefixes": [
        "v6",
        "07/10"
    ]
}