| /* SPDX-License-Identifier: Apache-2.0 |
| * Copyright (c) 2023 Cisco Systems, Inc. |
| */ |
| |
| #include "vppinfra/error.h" |
| #include <vnet/vnet.h> |
| #include <vnet/dev/dev.h> |
| #include <vnet/dev/log.h> |
| |
| VLIB_REGISTER_LOG_CLASS (dev_log, static) = { |
| .class_name = "dev", |
| .subclass_name = "process", |
| }; |
| |
| typedef enum |
| { |
| VNET_DEV_EVENT_PERIODIC_STOP, |
| VNET_DEV_EVENT_PERIODIC_START, |
| VNET_DEV_EVENT_PORT_CONFIG_CHANGE_REQ, |
| VNET_DEV_EVENT_PROCESS_QUIT, |
| VNET_DEV_EVENT_CALL_OP, |
| VNET_DEV_EVENT_CALL_OP_NO_RV, |
| VNET_DEV_EVENT_CALL_OP_NO_WAIT, |
| VNET_DEV_EVENT_CALL_PORT_OP, |
| VNET_DEV_EVENT_CALL_PORT_OP_NO_RV, |
| VNET_DEV_EVENT_CALL_PORT_OP_NO_WAIT, |
| VNET_DEV_EVENT_CLOCK = ~0 |
| } __clib_packed vnet_dev_event_t; |
| |
| typedef struct |
| { |
| vnet_dev_event_t event; |
| u8 reply_needed : 1; |
| u32 calling_process_index; |
| union |
| { |
| struct |
| { |
| vnet_dev_port_t *port; |
| vnet_dev_port_cfg_change_req_t *change_req; |
| } port_cfg_change; |
| struct |
| { |
| vnet_dev_op_t *op; |
| } call_op; |
| struct |
| { |
| vnet_dev_op_no_rv_t *op; |
| } call_op_no_rv; |
| struct |
| { |
| vnet_dev_op_no_rv_t *op; |
| } call_op_no_wait; |
| struct |
| { |
| vnet_dev_port_op_t *op; |
| vnet_dev_port_t *port; |
| } call_port_op; |
| struct |
| { |
| vnet_dev_port_op_no_rv_t *op; |
| vnet_dev_port_t *port; |
| } call_port_op_no_rv; |
| struct |
| { |
| vnet_dev_port_op_no_rv_t *op; |
| vnet_dev_port_t *port; |
| } call_port_op_no_wait; |
| }; |
| } vnet_dev_event_data_t; |
| |
| static vnet_dev_rv_t |
| vnet_dev_process_one_event (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_event_data_t *ed) |
| { |
| vnet_dev_port_t *p; |
| vnet_dev_rv_t rv = VNET_DEV_OK; |
| |
| switch (ed->event) |
| { |
| case VNET_DEV_EVENT_CLOCK: |
| break; |
| case VNET_DEV_EVENT_PROCESS_QUIT: |
| log_debug (dev, "quit requested"); |
| dev->process_node_quit = 1; |
| break; |
| case VNET_DEV_EVENT_PERIODIC_START: |
| log_debug (dev, "periodic start"); |
| dev->process_node_periodic = 1; |
| break; |
| case VNET_DEV_EVENT_PERIODIC_STOP: |
| log_debug (dev, "periodic stop"); |
| dev->process_node_periodic = 0; |
| break; |
| case VNET_DEV_EVENT_PORT_CONFIG_CHANGE_REQ: |
| log_debug (dev, "port config change"); |
| p = ed->port_cfg_change.port; |
| rv = vnet_dev_port_cfg_change (vm, p, ed->port_cfg_change.change_req); |
| break; |
| case VNET_DEV_EVENT_CALL_OP: |
| log_debug (dev, "call op"); |
| rv = ed->call_op.op (vm, dev); |
| break; |
| case VNET_DEV_EVENT_CALL_OP_NO_RV: |
| log_debug (dev, "call op no rv"); |
| ed->call_op_no_rv.op (vm, dev); |
| break; |
| case VNET_DEV_EVENT_CALL_OP_NO_WAIT: |
| log_debug (dev, "call op no wait"); |
| ed->call_op_no_wait.op (vm, dev); |
| break; |
| case VNET_DEV_EVENT_CALL_PORT_OP: |
| log_debug (dev, "call port op"); |
| rv = ed->call_port_op.op (vm, ed->call_port_op.port); |
| break; |
| case VNET_DEV_EVENT_CALL_PORT_OP_NO_RV: |
| log_debug (dev, "call port op no rv"); |
| ed->call_port_op_no_rv.op (vm, ed->call_port_op_no_rv.port); |
| break; |
| case VNET_DEV_EVENT_CALL_PORT_OP_NO_WAIT: |
| log_debug (dev, "call port op no wait"); |
| ed->call_port_op_no_wait.op (vm, ed->call_port_op_no_wait.port); |
| break; |
| default: |
| ASSERT (0); |
| } |
| return rv; |
| } |
| |
| static uword |
| vnet_dev_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) |
| { |
| vnet_dev_main_t *dm = &vnet_dev_main; |
| vnet_dev_periodic_op_t *pop, *pops = 0; |
| f64 next = CLIB_F64_MAX; |
| vnet_dev_event_data_t *event_data = 0, *new_event_data, *ed; |
| |
| vnet_dev_t *dev = |
| *((vnet_dev_t **) vlib_node_get_runtime_data (vm, rt->node_index)); |
| |
| log_debug (dev, "process '%U' started", format_vlib_node_name, vm, |
| rt->node_index); |
| |
| while (dev->process_node_quit == 0) |
| { |
| uword event_type; |
| f64 now = vlib_time_now (vm); |
| |
| if (dev->process_node_periodic) |
| vlib_process_wait_for_event_or_clock (vm, next > now ? next - now : 0); |
| else |
| vlib_process_wait_for_event (vm); |
| |
| new_event_data = vlib_process_get_event_data (vm, &event_type); |
| |
| if (new_event_data) |
| { |
| vec_append (event_data, new_event_data); |
| vlib_process_put_event_data (vm, new_event_data); |
| |
| ASSERT (event_type == 0); |
| |
| vec_foreach (ed, event_data) |
| { |
| vnet_dev_rv_t rv; |
| rv = vnet_dev_process_one_event (vm, dev, ed); |
| if (ed->reply_needed) |
| vlib_process_signal_event (vm, ed->calling_process_index, |
| ed->event, rv); |
| } |
| vec_reset_length (event_data); |
| } |
| |
| next = CLIB_F64_MAX; |
| pool_foreach (pop, dev->periodic_ops) |
| { |
| if (pop->last_run + pop->interval < now) |
| { |
| vec_add1 (pops, *pop); |
| pop->last_run = now; |
| } |
| if (pop->last_run + pop->interval < next) |
| next = pop->last_run + pop->interval; |
| } |
| |
| vec_foreach (pop, pops) |
| { |
| switch (pop->type) |
| { |
| case VNET_DEV_PERIODIC_OP_TYPE_DEV: |
| pop->dev_op (vm, pop->dev); |
| break; |
| case VNET_DEV_PERIODIC_OP_TYPE_PORT: |
| pop->port_op (vm, pop->port); |
| break; |
| default: |
| ASSERT (0); |
| } |
| } |
| vec_reset_length (pops); |
| } |
| |
| log_debug (dev, "process '%U' quit", format_vlib_node_name, vm, |
| rt->node_index); |
| vlib_node_set_state (vm, rt->node_index, VLIB_NODE_STATE_DISABLED); |
| vlib_node_rename (vm, rt->node_index, "deleted-%u", rt->node_index); |
| |
| /* add node index to the freelist */ |
| vec_add1 (dm->free_process_node_indices, rt->node_index); |
| vec_free (pops); |
| vec_free (event_data); |
| return 0; |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_process_create (vlib_main_t *vm, vnet_dev_t *dev) |
| { |
| vnet_dev_main_t *dm = &vnet_dev_main; |
| vlib_node_t *n; |
| uword l; |
| |
| l = vec_len (dm->free_process_node_indices); |
| if (l > 0) |
| { |
| n = vlib_get_node (vm, dm->free_process_node_indices[l - 1]); |
| if (n->function != vnet_dev_process) |
| { |
| vlib_node_runtime_t *rt = vlib_node_get_runtime (vm, n->index); |
| n->function = vnet_dev_process; |
| rt->function = vnet_dev_process; |
| } |
| vlib_node_rename (vm, n->index, "%s-process", dev->device_id); |
| vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING); |
| vec_set_len (dm->free_process_node_indices, l - 1); |
| log_debug (dev, "process node '%U' (%u) reused", format_vlib_node_name, |
| vm, n->index, n->index); |
| } |
| else |
| { |
| vlib_node_registration_t r = { |
| .function = vnet_dev_process, |
| .type = VLIB_NODE_TYPE_PROCESS, |
| .process_log2_n_stack_bytes = 16, |
| .runtime_data_bytes = sizeof (void *), |
| }; |
| |
| vlib_register_node (vm, &r, "%s-process", dev->device_id); |
| |
| n = vlib_get_node (vm, r.index); |
| log_debug (dev, "process node '%U' (%u) created", format_vlib_node_name, |
| vm, r.index, r.index); |
| } |
| |
| dev->process_node_index = n->index; |
| *(vnet_dev_t **) vlib_node_get_runtime_data (vm, n->index) = dev; |
| vlib_start_process (vm, n->runtime_index); |
| |
| return VNET_DEV_OK; |
| } |
| |
| static void |
| vnet_dev_process_event_send (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_event_data_t ed) |
| { |
| vnet_dev_event_data_t *edp = vlib_process_signal_event_data ( |
| vm, dev->process_node_index, 0, 1, sizeof (ed)); |
| *edp = ed; |
| } |
| |
| static vnet_dev_rv_t |
| vnet_dev_process_event_send_and_wait (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_event_data_t ed) |
| { |
| uword event, *event_data = 0; |
| vnet_dev_rv_t rv; |
| |
| ed.calling_process_index = vlib_get_current_process_node_index (vm); |
| |
| if (ed.calling_process_index == dev->process_node_index) |
| return vnet_dev_process_one_event (vm, dev, &ed); |
| |
| ed.reply_needed = 1; |
| vnet_dev_process_event_send (vm, dev, ed); |
| vlib_process_wait_for_event_or_clock (vm, 5.0); |
| event = vlib_process_get_events (vm, &event_data); |
| if (event != ed.event) |
| { |
| log_err (dev, "%s", |
| event == VNET_DEV_EVENT_CLOCK ? |
| "timeout waiting for process node to respond" : |
| "unexpected event received"); |
| rv = VNET_DEV_ERR_PROCESS_REPLY; |
| } |
| else |
| rv = event_data[0]; |
| vec_free (event_data); |
| return rv; |
| } |
| |
| void |
| vnet_dev_process_quit (vlib_main_t *vm, vnet_dev_t *dev) |
| { |
| vnet_dev_event_data_t ed = { .event = VNET_DEV_EVENT_PROCESS_QUIT }; |
| vnet_dev_process_event_send_and_wait (vm, dev, ed); |
| } |
| |
| static int |
| _vnet_dev_poll_add (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_periodic_op_t pop) |
| { |
| const vnet_dev_event_data_t ed = { .event = VNET_DEV_EVENT_PERIODIC_START }; |
| vnet_dev_periodic_op_t *p; |
| |
| pool_foreach (p, dev->periodic_ops) |
| if (p->op == pop.op && p->arg == pop.arg) |
| return 0; |
| |
| pool_get_zero (dev->periodic_ops, p); |
| *p = pop; |
| if (pool_elts (dev->periodic_ops) == 1) |
| vnet_dev_process_event_send (vm, dev, ed); |
| return 1; |
| } |
| |
| static int |
| _vnet_dev_poll_remove (vlib_main_t *vm, vnet_dev_t *dev, void *op, void *arg) |
| { |
| const vnet_dev_event_data_t ed = { .event = VNET_DEV_EVENT_PERIODIC_STOP }; |
| vnet_dev_periodic_op_t *pop; |
| |
| pool_foreach (pop, dev->periodic_ops) |
| if (pop->op == op && pop->arg == arg) |
| { |
| pool_put (dev->periodic_ops, pop); |
| if (pool_elts (dev->periodic_ops) == 0) |
| vnet_dev_process_event_send (vm, dev, ed); |
| return 1; |
| } |
| return 0; |
| } |
| |
| void |
| vnet_dev_poll_dev_add (vlib_main_t *vm, vnet_dev_t *dev, f64 interval, |
| vnet_dev_op_no_rv_t *dev_op) |
| { |
| vnet_dev_periodic_op_t pop = { |
| .interval = interval, |
| .type = VNET_DEV_PERIODIC_OP_TYPE_DEV, |
| .dev_op = dev_op, |
| .dev = dev, |
| }; |
| |
| if (_vnet_dev_poll_add (vm, dev, pop) == 0) |
| log_warn (dev, "poll_dev_add: op already exists, not added"); |
| } |
| |
| void |
| vnet_dev_poll_dev_remove (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_op_no_rv_t *dev_op) |
| { |
| if (_vnet_dev_poll_remove (vm, dev, (void *) dev_op, (void *) dev) == 0) |
| log_warn (dev, "poll_dev_remove: op not found, not removed"); |
| } |
| |
| void |
| vnet_dev_poll_port_add (vlib_main_t *vm, vnet_dev_port_t *port, f64 interval, |
| vnet_dev_port_op_no_rv_t *port_op) |
| { |
| vnet_dev_t *dev = port->dev; |
| vnet_dev_periodic_op_t pop = { |
| .interval = interval, |
| .type = VNET_DEV_PERIODIC_OP_TYPE_PORT, |
| .port_op = port_op, |
| .port = port, |
| }; |
| |
| if (_vnet_dev_poll_add (vm, dev, pop) == 0) |
| log_warn (dev, "poll_port_add: op already exists, not added"); |
| } |
| |
| void |
| vnet_dev_poll_port_remove (vlib_main_t *vm, vnet_dev_port_t *port, |
| vnet_dev_port_op_no_rv_t *port_op) |
| { |
| vnet_dev_t *dev = port->dev; |
| if (_vnet_dev_poll_remove (vm, dev, (void *) port_op, (void *) port) == 0) |
| log_warn (dev, "poll_port_remove: op not found, not removed"); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_process_port_cfg_change_req (vlib_main_t *vm, vnet_dev_port_t *port, |
| vnet_dev_port_cfg_change_req_t *pccr) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_PORT_CONFIG_CHANGE_REQ, |
| .port_cfg_change = { |
| .port = port, |
| .change_req = pccr, |
| }, |
| }; |
| |
| return vnet_dev_process_event_send_and_wait (vm, port->dev, ed); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_process_call_op (vlib_main_t *vm, vnet_dev_t *dev, vnet_dev_op_t *op) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_CALL_OP, |
| .call_op.op = op, |
| }; |
| |
| return vnet_dev_process_event_send_and_wait (vm, dev, ed); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_process_call_op_no_rv (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_op_no_rv_t *op) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_CALL_OP_NO_RV, |
| .call_op_no_rv.op = op, |
| }; |
| |
| return vnet_dev_process_event_send_and_wait (vm, dev, ed); |
| } |
| |
| void |
| vnet_dev_process_call_op_no_wait (vlib_main_t *vm, vnet_dev_t *dev, |
| vnet_dev_op_no_rv_t *op) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_CALL_OP_NO_WAIT, |
| .call_op_no_rv.op = op, |
| }; |
| |
| vnet_dev_process_event_send (vm, dev, ed); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_process_call_port_op (vlib_main_t *vm, vnet_dev_port_t *port, |
| vnet_dev_port_op_t *op) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_CALL_PORT_OP, |
| .call_port_op = { .op = op, .port = port }, |
| }; |
| |
| return vnet_dev_process_event_send_and_wait (vm, port->dev, ed); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_process_call_port_op_no_rv (vlib_main_t *vm, vnet_dev_port_t *port, |
| vnet_dev_port_op_no_rv_t *op) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_CALL_PORT_OP_NO_RV, |
| .call_port_op_no_rv = { .op = op, .port = port }, |
| }; |
| |
| return vnet_dev_process_event_send_and_wait (vm, port->dev, ed); |
| } |
| |
| void |
| vnet_dev_process_call_port_op_no_wait (vlib_main_t *vm, vnet_dev_port_t *port, |
| vnet_dev_port_op_no_rv_t *op) |
| { |
| const vnet_dev_event_data_t ed = { |
| .event = VNET_DEV_EVENT_CALL_PORT_OP_NO_WAIT, |
| .call_port_op_no_wait = { .op = op, .port = port }, |
| }; |
| |
| vnet_dev_process_event_send (vm, port->dev, ed); |
| } |