/**************************************************************************** * drivers/virtio/virtio-pci.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include "virtio-pci.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define VIRTIO_PCI_WORK_DELAY \ USEC2TICK(CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD) /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static FAR void *virtio_pci_alloc_buf(FAR struct virtio_device *vdev, size_t size, size_t align); static void virtio_pci_free_buf(FAR struct virtio_device *vdev, FAR void *buf); static int virtio_pci_probe(FAR struct pci_device_s *dev); static void virtio_pci_remove(FAR struct pci_device_s *dev); static int virtio_pci_create_virtqueue(FAR struct virtio_pci_device_s *vpdev, unsigned int i, FAR const char *name, vq_callback callback); /**************************************************************************** * Private Data ****************************************************************************/ static const struct pci_device_id_s g_virtio_pci_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_ANY_ID) }, { 0, } }; static struct pci_driver_s g_virtio_pci_drv = { g_virtio_pci_id_table, virtio_pci_probe, virtio_pci_remove }; static const struct virtio_memory_ops g_virtio_pci_mmops = { virtio_pci_alloc_buf, /* Alloc */ virtio_pci_free_buf, /* Free */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: virtio_pci_vq_callback ****************************************************************************/ static void virtio_pci_vq_callback(FAR struct virtio_pci_device_s *vpdev) { FAR struct virtio_vring_info *vrings_info = vpdev->vdev.vrings_info; FAR struct virtqueue *vq; unsigned int i; for (i = 0; i < vpdev->vdev.vrings_num; i++) { vq = vrings_info[i].vq; if (vq->vq_used_cons_idx != vq->vq_ring.used->idx && vq->callback != NULL) { vq->callback(vq); } } } #if CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD <= 0 /**************************************************************************** * Name: virtio_pci_interrupt ****************************************************************************/ static int virtio_pci_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct virtio_pci_device_s *vpdev = arg; virtio_pci_vq_callback(vpdev); return OK; } /**************************************************************************** * Name: virtio_pci_config_changed ****************************************************************************/ static int virtio_pci_config_changed(int irq, FAR void *context, FAR void *arg) { /* TODO: not support config changed notification */ return OK; } #else /**************************************************************************** * Name: virtio_pci_wdog ****************************************************************************/ static void virtio_pci_wdog(wdparm_t arg) { FAR struct virtio_pci_device_s *vpdev = (FAR struct virtio_pci_device_s *)arg; virtio_pci_vq_callback(vpdev); wd_start(&vpdev->wdog, VIRTIO_PCI_WORK_DELAY, virtio_pci_wdog, (wdparm_t)vpdev); } #endif /**************************************************************************** * Name: virtio_pci_alloc_buf ****************************************************************************/ static FAR void *virtio_pci_alloc_buf(FAR struct virtio_device *vdev, size_t size, size_t align) { return kmm_memalign(align, size); } /**************************************************************************** * Name: virtio_pci_free_buf ****************************************************************************/ static void virtio_pci_free_buf(FAR struct virtio_device *vdev, FAR void *buf) { return kmm_free(buf); } /**************************************************************************** * Name: virtio_pci_probe ****************************************************************************/ static int virtio_pci_probe(FAR struct pci_device_s *dev) { FAR struct virtio_pci_device_s *vpdev; FAR struct virtio_device *vdev; int ret; /* We only own devices >= 0x1000 and <= 0x107f: leave the rest. */ if (dev->device < 0x1000 || dev->device > 0x107f) { pcierr("Pci device id err, id=%d\n", dev->device); return -ENODEV; } vpdev = kmm_zalloc(sizeof(*vpdev)); if (vpdev == NULL) { vrterr("No enough memory\n"); return -ENOMEM; } dev->priv = vpdev; vpdev->dev = dev; vdev = &vpdev->vdev; vdev->mmops = &g_virtio_pci_mmops; /* Virtio device initialize */ metal_io_init(&vpdev->shm_io, NULL, &vpdev->shm_phy, SIZE_MAX, UINT_MAX, 0, metal_io_get_ops()); ret = pci_enable_device(dev); if (ret < 0) { vrterr("Enable virtio pci device failed, ret=%d\n", ret); goto err; } pci_set_master(dev); ret = virtio_pci_modern_probe(dev); if (ret == -ENODEV) { ret = virtio_pci_legacy_probe(dev); if (ret < 0) { vrterr("Virtio pci legacy probe failed\n"); goto err_with_enable; } } #if CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD <= 0 /* Irq init */ ret = pci_alloc_irq(vpdev->dev, vpdev->irq, VIRTIO_PCI_INT_NUM); if (ret != VIRTIO_PCI_INT_NUM) { vrterr("Failed to allocate MSI, ret=%d\n", ret); goto err_with_enable; } vrtinfo("Interrupt mode: attaching MSI %d to %p, %d to %p\n", vpdev->irq[VIRTIO_PCI_INT_CFG], virtio_pci_config_changed, vpdev->irq[VIRTIO_PCI_INT_VQ], virtio_pci_interrupt); ret = pci_connect_irq(vpdev->dev, vpdev->irq, VIRTIO_PCI_INT_NUM); if (ret < 0) { vrterr("Failed to connect MSI %d\n", ret); goto err_with_irq; } irq_attach(vpdev->irq[VIRTIO_PCI_INT_CFG], virtio_pci_config_changed, vpdev); irq_attach(vpdev->irq[VIRTIO_PCI_INT_VQ], virtio_pci_interrupt, vpdev); #else vrtinfo("Polling mode\n"); #endif virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_RESET); virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_ACK); ret = virtio_register_device(&vpdev->vdev); if (ret < 0) { vrterr("Register virtio device failed, ret=%d\n", ret); goto err_with_attach; } return ret; err_with_attach: #if CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD <= 0 irq_detach(vpdev->irq[VIRTIO_PCI_INT_CFG]); irq_detach(vpdev->irq[VIRTIO_PCI_INT_VQ]); err_with_irq: pci_release_irq(vpdev->dev, vpdev->irq, VIRTIO_PCI_INT_NUM); #endif err_with_enable: pci_clear_master(dev); pci_disable_device(dev); err: kmm_free(vpdev); return ret; } /**************************************************************************** * Name: virtio_pci_remove ****************************************************************************/ static void virtio_pci_remove(FAR struct pci_device_s *dev) { FAR struct virtio_pci_device_s *vpdev = dev->priv; virtio_unregister_device(&vpdev->vdev); #if CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD <= 0 irq_detach(vpdev->irq[VIRTIO_PCI_INT_CFG]); irq_detach(vpdev->irq[VIRTIO_PCI_INT_VQ]); pci_release_irq(vpdev->dev, vpdev->irq, VIRTIO_PCI_INT_NUM); #endif pci_clear_master(dev); pci_disable_device(dev); kmm_free(vpdev); } /**************************************************************************** * Name: virtio_pci_create_virtqueue ****************************************************************************/ static int virtio_pci_create_virtqueue(FAR struct virtio_pci_device_s *vpdev, unsigned int i, FAR const char *name, vq_callback callback) { FAR struct virtio_device *vdev = &vpdev->vdev; FAR struct virtio_vring_info *vrinfo; FAR struct vring_alloc_info *vralloc; FAR struct virtqueue *vq; int vringsize; int ret; /* Alloc virtqueue and init the vring info and vring alloc info */ vrinfo = &vdev->vrings_info[i]; vralloc = &vrinfo->info; vralloc->num_descs = vpdev->ops->get_queue_len(vpdev, i); vq = virtqueue_allocate(vralloc->num_descs); if (vq == NULL) { vrterr("Virtqueue_allocate failed\n"); return -ENOMEM; } /* Init the vring info and vring alloc info */ vrinfo->vq = vq; vrinfo->io = &vpdev->shm_io; vralloc->align = VIRTIO_PCI_VRING_ALIGN; vringsize = vring_size(vralloc->num_descs, VIRTIO_PCI_VRING_ALIGN); vralloc->vaddr = virtio_zalloc_buf(vdev, vringsize, VIRTIO_PCI_VRING_ALIGN); if (vralloc->vaddr == NULL) { vrterr("Vring alloc failed\n"); return -ENOMEM; } /* Initialize the virtio queue */ ret = virtqueue_create(vdev, i, name, vralloc, callback, vdev->func->notify, vq); if (ret < 0) { vrterr("Virtqueue create error, ret=%d\n", ret); return ret; } virtqueue_set_shmem_io(vq, &vpdev->shm_io); /* Set the pci virtqueue register, active vq, enable vq */ ret = vpdev->ops->create_virtqueue(vpdev, vq); if (ret < 0) { vrterr("Virtio_pci_config_virtqueue failed, ret=%d\n", ret); return ret; } return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: virtio_pci_create_virtqueues ****************************************************************************/ int virtio_pci_create_virtqueues(FAR struct virtio_device *vdev, unsigned int flags, unsigned int nvqs, FAR const char *names[], vq_callback callbacks[], FAR void *callback_args[]) { FAR struct virtio_pci_device_s *vpdev = (FAR struct virtio_pci_device_s *)vdev; unsigned int i; int ret = OK; /* Alloc vring info */ vdev->vrings_num = nvqs; vdev->vrings_info = kmm_zalloc(sizeof(struct virtio_vring_info) * nvqs); if (vdev->vrings_info == NULL) { vrterr("Alloc vrings info failed\n"); return -ENOMEM; } /* Alloc and init the virtqueue */ for (i = 0; i < nvqs; i++) { ret = virtio_pci_create_virtqueue(vpdev, i, names[i], callbacks[i]); if (ret < 0) { goto err; } } #if CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD <= 0 ret = vpdev->ops->config_vector(vpdev, true); if (ret < 0) { vrterr("Config vector failed, vector=%u\n", ret); goto err; } up_enable_irq(vpdev->irq[VIRTIO_PCI_INT_CFG]); up_enable_irq(vpdev->irq[VIRTIO_PCI_INT_VQ]); #else ret = wd_start(&vpdev->wdog, VIRTIO_PCI_WORK_DELAY, virtio_pci_wdog, (wdparm_t)vpdev); if (ret < 0) { pcierr("Wd_start failed: %d\n", ret); goto err; } #endif return ret; err: virtio_pci_delete_virtqueues(vdev); return ret; } /**************************************************************************** * Name: virtio_pci_delete_virtqueues ****************************************************************************/ void virtio_pci_delete_virtqueues(FAR struct virtio_device *vdev) { FAR struct virtio_pci_device_s *vpdev = (FAR struct virtio_pci_device_s *)vdev; FAR struct virtio_vring_info *vrinfo; unsigned int i; #if CONFIG_DRIVERS_VIRTIO_PCI_POLLING_PERIOD <= 0 up_disable_irq(vpdev->irq[VIRTIO_PCI_INT_CFG]); up_disable_irq(vpdev->irq[VIRTIO_PCI_INT_VQ]); vpdev->ops->config_vector(vpdev, false); #else wd_cancel(&vpdev->wdog); #endif /* Free the memory */ if (vdev->vrings_info != NULL) { for (i = 0; i < vdev->vrings_num; i++) { vrinfo = &vdev->vrings_info[i]; vpdev->ops->delete_virtqueue(vdev, i); /* Free the vring buffer and virtqueue */ if (vrinfo->info.vaddr != NULL) { virtio_free_buf(vdev, vrinfo->info.vaddr); } if (vrinfo->vq != NULL) { virtqueue_free(vrinfo->vq); } } kmm_free(vdev->vrings_info); } } /**************************************************************************** * Name: virtio_pci_negotiate_features ****************************************************************************/ uint64_t virtio_pci_negotiate_features(FAR struct virtio_device *vdev, uint64_t features) { features = features & vdev->func->get_features(vdev); vdev->func->set_features(vdev, features); return features; } /**************************************************************************** * Name: virtio_pci_reset_device ****************************************************************************/ void virtio_pci_reset_device(FAR struct virtio_device *vdev) { vdev->func->set_features(vdev, VIRTIO_CONFIG_STATUS_RESET); } /**************************************************************************** * Name: register_virtio_pci_driver ****************************************************************************/ int register_virtio_pci_driver(void) { return pci_register_driver(&g_virtio_pci_drv); }