| /* SPDX-License-Identifier: Apache-2.0 |
| * Copyright (c) 2023 Cisco Systems, Inc. |
| */ |
| |
| #include "vppinfra/pool.h" |
| #include <vnet/vnet.h> |
| #include <vnet/ethernet/ethernet.h> |
| #include <vnet/dev/dev.h> |
| #include <vnet/dev/counters.h> |
| #include <vnet/dev/log.h> |
| #include <vnet/dev/api.h> |
| |
| VLIB_REGISTER_LOG_CLASS (dev_log, static) = { |
| .class_name = "dev", |
| .subclass_name = "api", |
| }; |
| |
| static int |
| _vnet_dev_queue_size_validate (u32 size, vnet_dev_queue_config_t c) |
| { |
| if (size < c.min_size) |
| return 0; |
| if (size > c.max_size) |
| return 0; |
| if (c.size_is_power_of_two && count_set_bits (size) != 1) |
| return 0; |
| if (c.multiplier && size % c.multiplier) |
| return 0; |
| |
| return 1; |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_api_attach (vlib_main_t *vm, vnet_dev_api_attach_args_t *args) |
| { |
| vnet_dev_main_t *dm = &vnet_dev_main; |
| vnet_dev_t *dev = 0; |
| vnet_dev_rv_t rv = VNET_DEV_OK; |
| vnet_dev_bus_t *bus; |
| vnet_dev_driver_t *driver; |
| void *bus_dev_info = 0; |
| u8 *dev_desc = 0; |
| |
| log_debug (0, "%s driver %s flags '%U' args '%v'", args->device_id, |
| args->driver_name, format_vnet_dev_flags, &args->flags, |
| args->args); |
| |
| if (vnet_dev_by_id (args->device_id)) |
| return VNET_DEV_ERR_ALREADY_IN_USE; |
| |
| bus = vnet_dev_find_device_bus (vm, args->device_id); |
| if (!bus) |
| { |
| log_err (dev, "unknown bus"); |
| rv = VNET_DEV_ERR_INVALID_BUS; |
| goto done; |
| } |
| |
| bus_dev_info = vnet_dev_get_device_info (vm, args->device_id); |
| if (!bus_dev_info) |
| { |
| log_err (dev, "invalid or unsupported device id"); |
| rv = VNET_DEV_ERR_INVALID_DEVICE_ID; |
| goto done; |
| } |
| |
| vec_foreach (driver, dm->drivers) |
| { |
| if (args->driver_name[0] && |
| strcmp (args->driver_name, driver->registration->name)) |
| continue; |
| if (driver->ops.probe && |
| (dev_desc = driver->ops.probe (vm, bus->index, bus_dev_info))) |
| break; |
| } |
| |
| if (!dev_desc) |
| { |
| log_err (dev, "driver not available for %s", args->device_id); |
| rv = VNET_DEV_ERR_DRIVER_NOT_AVAILABLE; |
| goto done; |
| } |
| |
| dev = vnet_dev_alloc (vm, args->device_id, driver); |
| if (!dev) |
| { |
| log_err (dev, "dev alloc failed for %s", args->device_id); |
| rv = VNET_DEV_ERR_BUG; |
| goto done; |
| } |
| dev->description = dev_desc; |
| |
| for (vnet_dev_arg_t *a = driver->registration->args; |
| a->type != VNET_DEV_ARG_END; a++) |
| vec_add1 (dev->args, *a); |
| |
| if (args->args) |
| { |
| if ((rv = vnet_dev_arg_parse (vm, dev, dev->args, args->args)) != |
| VNET_DEV_OK) |
| goto done; |
| } |
| |
| if ((args->flags.e & VNET_DEV_F_NO_STATS) == 0) |
| dev->poll_stats = 1; |
| |
| log_debug (0, "found '%v'", dev->description); |
| |
| rv = vnet_dev_process_call_op (vm, dev, vnet_dev_init); |
| |
| done: |
| if (bus_dev_info) |
| bus->ops.free_device_info (vm, bus_dev_info); |
| |
| if (rv != VNET_DEV_OK && dev) |
| vnet_dev_process_call_op_no_rv (vm, dev, vnet_dev_free); |
| |
| return rv; |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_api_detach (vlib_main_t *vm, vnet_dev_api_detach_args_t *args) |
| { |
| vnet_dev_t *dev = vnet_dev_by_id (args->device_id); |
| |
| log_debug (dev, "detach"); |
| |
| if (dev) |
| return vnet_dev_process_call_op_no_rv (vm, dev, vnet_dev_detach); |
| |
| return VNET_DEV_ERR_NOT_FOUND; |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_api_reset (vlib_main_t *vm, vnet_dev_api_reset_args_t *args) |
| { |
| vnet_dev_t *dev = vnet_dev_by_id (args->device_id); |
| |
| log_debug (dev, "detach"); |
| |
| if (!dev) |
| return VNET_DEV_ERR_NOT_FOUND; |
| |
| if (dev->ops.reset) |
| return VNET_DEV_ERR_NOT_SUPPORTED; |
| |
| return vnet_dev_process_call_op (vm, dev, vnet_dev_reset); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_api_create_port_if (vlib_main_t *vm, |
| vnet_dev_api_create_port_if_args_t *args) |
| { |
| vnet_dev_t *dev = vnet_dev_by_id (args->device_id); |
| vnet_dev_port_t *port = 0; |
| u16 n_threads = vlib_get_n_threads (); |
| int default_is_intr_mode; |
| vnet_dev_rv_t rv; |
| |
| log_debug (dev, |
| "create_port_if: device '%s' port %u intf_name '%s' num_rx_q %u " |
| "num_tx_q %u rx_q_sz %u tx_q_sz %u, flags '%U' args '%v'", |
| args->device_id, args->port_id, args->intf_name, |
| args->num_rx_queues, args->num_tx_queues, args->rx_queue_size, |
| args->tx_queue_size, format_vnet_dev_port_flags, &args->flags, |
| args->args); |
| |
| if (dev == 0) |
| return VNET_DEV_ERR_NOT_FOUND; |
| |
| foreach_vnet_dev_port (p, dev) |
| if (p->port_id == args->port_id) |
| { |
| port = p; |
| break; |
| } |
| |
| if (!port) |
| return VNET_DEV_ERR_INVALID_DEVICE_ID; |
| |
| if (port->interface_created) |
| return VNET_DEV_ERR_ALREADY_EXISTS; |
| |
| if (args->args) |
| { |
| rv = vnet_dev_arg_parse (vm, dev, port->args, args->args); |
| if (rv != VNET_DEV_OK) |
| return rv; |
| } |
| |
| default_is_intr_mode = (args->flags.e & VNET_DEV_PORT_F_INTERRUPT_MODE) != 0; |
| if (default_is_intr_mode && port->attr.caps.interrupt_mode == 0) |
| { |
| log_err (dev, "interrupt mode requested and port doesn't support it"); |
| return VNET_DEV_ERR_NOT_SUPPORTED; |
| } |
| |
| if (args->num_rx_queues) |
| { |
| if (args->num_rx_queues > port->attr.max_rx_queues) |
| return VNET_DEV_ERR_INVALID_NUM_RX_QUEUES; |
| port->intf.num_rx_queues = args->num_rx_queues; |
| } |
| else |
| port->intf.num_rx_queues = clib_min (port->attr.max_tx_queues, 1); |
| |
| if (args->num_tx_queues) |
| { |
| if (args->num_tx_queues > port->attr.max_tx_queues) |
| return VNET_DEV_ERR_INVALID_NUM_TX_QUEUES; |
| port->intf.num_tx_queues = args->num_tx_queues; |
| } |
| else |
| port->intf.num_tx_queues = clib_min (port->attr.max_tx_queues, n_threads); |
| |
| if (args->rx_queue_size) |
| { |
| if (!_vnet_dev_queue_size_validate (args->rx_queue_size, |
| port->rx_queue_config)) |
| return VNET_DEV_ERR_INVALID_RX_QUEUE_SIZE; |
| port->intf.rxq_sz = args->rx_queue_size; |
| } |
| else |
| port->intf.rxq_sz = port->rx_queue_config.default_size; |
| |
| if (args->tx_queue_size) |
| { |
| if (!_vnet_dev_queue_size_validate (args->tx_queue_size, |
| port->tx_queue_config)) |
| return VNET_DEV_ERR_INVALID_TX_QUEUE_SIZE; |
| port->intf.txq_sz = args->tx_queue_size; |
| } |
| else |
| port->intf.txq_sz = port->tx_queue_config.default_size; |
| |
| clib_memcpy (port->intf.name, args->intf_name, sizeof (port->intf.name)); |
| port->intf.default_is_intr_mode = default_is_intr_mode; |
| |
| return vnet_dev_process_call_port_op (vm, port, vnet_dev_port_if_create); |
| } |
| |
| vnet_dev_rv_t |
| vnet_dev_api_remove_port_if (vlib_main_t *vm, |
| vnet_dev_api_remove_port_if_args_t *args) |
| { |
| vnet_dev_main_t *dm = &vnet_dev_main; |
| vnet_main_t *vnm = vnet_get_main (); |
| vnet_sw_interface_t *si; |
| vnet_hw_interface_t *hi; |
| vnet_dev_port_t *port; |
| |
| si = vnet_get_sw_interface_or_null (vnm, args->sw_if_index); |
| if (!si) |
| return VNET_DEV_ERR_UNKNOWN_INTERFACE; |
| |
| hi = vnet_get_hw_interface_or_null (vnm, si->hw_if_index); |
| if (!hi) |
| return VNET_DEV_ERR_UNKNOWN_INTERFACE; |
| |
| if (pool_is_free_index (dm->ports_by_dev_instance, hi->dev_instance)) |
| return VNET_DEV_ERR_UNKNOWN_INTERFACE; |
| |
| port = vnet_dev_get_port_from_dev_instance (hi->dev_instance); |
| |
| if (port->intf.hw_if_index != si->hw_if_index) |
| return VNET_DEV_ERR_UNKNOWN_INTERFACE; |
| |
| return vnet_dev_process_call_port_op (vm, port, vnet_dev_port_if_remove); |
| } |