diff options
author | 2024-12-18 20:33:53 +0200 | |
---|---|---|
committer | 2024-12-18 21:36:52 +0200 | |
commit | 6ca0cc5287f10cb34be0057f5444f6d5e749b9dd (patch) | |
tree | 5cbd69691abf02407c997c4861ba7fc5861c898a | |
parent | 061ee070b409025af7e775c5ba9199673efed0bf (diff) |
Update virtio-loopback driver - Add priority feature
Add scheduling mechanism for virtio interrupts and
messages. This mechanism can be used to prioritize
one virtio-device's requests over the other by
setting the args "credits=" and "priority=" into
the adapter's arguments.
Change-Id: I5d375e6c6b3fce50c1f6659d2eeb388a2555e0ad
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
-rw-r--r-- | virtio_loopback_device.c | 654 | ||||
-rw-r--r-- | virtio_loopback_driver.c | 110 | ||||
-rw-r--r-- | virtio_loopback_driver.h | 48 |
3 files changed, 704 insertions, 108 deletions
diff --git a/virtio_loopback_device.c b/virtio_loopback_device.c index 0c1c326..0604afd 100644 --- a/virtio_loopback_device.c +++ b/virtio_loopback_device.c @@ -149,41 +149,6 @@ static void print_neg_flag(uint64_t neg_flag, bool read) } } -/* - * Print the pdev: - * - *static void print_virtio_pdev(struct platform_device *pdev) - *{ - * int i; - * - * pr_info("Print the pdev:\n"); - * pr_info("\t.name = %s\n", pdev->name); - * pr_info("\t.id = %d\n", pdev->id); - * pr_info("\t.num_resources = %d\n", pdev->num_resources); - * - * for (i=0; i < pdev->num_resources; i++) { - * pr_info("\t.num_resource = %d\n", i); - * pr_info("\t\t.start = 0x%llx\n", pdev->resource[i].start); - * pr_info("\t\t.end = 0x%llx\n", pdev->resource[i].end); - * pr_info("\t\t.flags = 0x%lx\n", pdev->resource[i].flags); - * } - *} - * - *Result: - * - * .name = a003e00.virtio_loopback - * .id = -1 - * .num_resources = 2 - * .num_resource = 0 - * .start = 0xa003e00 - * .end = 0xa003fff - * .flags = 0x200 - * .num_resource = 1 - * .start = 0x2c - * .end = 0x2c - * .flags = 0x401 - */ - /* function declaration */ static uint64_t read_adapter(uint64_t fn_id, uint64_t size, struct device_data *dev_data); @@ -393,15 +358,13 @@ static void notify_work_handler(struct work_struct *work) spin_unlock(&vl_dev->notify_q_lock); } -/* The notify function used when creating a virtqueue */ -static bool vl_notify(struct virtqueue *vq) +static bool trigger_notification(struct virtqueue *vq) { struct virtio_loopback_device *vl_dev = to_virtio_loopback_device(vq->vdev); struct eventfd_ctx **vq_notifiers = vl_dev->data->vq_data.vq_notifiers; bool vq_notifiers_enabled = vl_dev->data->vq_data.vq_notifiers_enabled; - struct notify_data *data; - int ret = 1; + int ret; if (vq_notifiers_enabled && (vq_notifiers[vq->index])) { /* Notify directly vhost-user-device bypassing the adapter */ @@ -411,19 +374,6 @@ static bool vl_notify(struct virtqueue *vq) eventfd_signal(vq_notifiers[vq->index], 1); #endif } else { - /* Create the new node */ - data = kmalloc(sizeof(struct notify_data), GFP_ATOMIC); - if (!data) - return false; - - data->index = vq->index; - INIT_LIST_HEAD(&data->list); - - /* Add in the notify_list, which should be protected! */ - spin_lock(&vl_dev->notify_q_lock); - list_add_tail(&data->list, &vl_dev->notify_list); - spin_unlock(&vl_dev->notify_q_lock); - /* Schedule the element */ while (ret) { /* @@ -441,36 +391,564 @@ static bool vl_notify(struct virtqueue *vq) return true; } -/* the interrupt function used when receiving an IRQ */ -bool vl_interrupt(struct virtio_loopback_device *vl_dev, int irq) +/* Notify work handling function */ +static void trigger_dev_notif(struct virtio_loopback_device *vl_dev) { - struct device_data *data = vl_dev->data; + struct notify_data *entry, *tmp; + uint32_t index; struct virtio_loopback_vq_info *info; - unsigned long status; - /* - * Read and acknowledge interrupts - * - * Those two operations should be executed without any - * intermediate status change. - */ - status = read_adapter(VIRTIO_MMIO_INTERRUPT_STATUS, 4, data); - write_adapter(status, VIRTIO_MMIO_INTERRUPT_ACK, 4, data); + if (atomic_read(&vl_dev->data->avail_notifs) == 0) + return; + + spin_lock(&vl_dev->notify_q_lock); + list_for_each_entry_safe(entry, tmp, &vl_dev->notify_list, list) { + index = entry->index; + list_del(&entry->list); + kfree(entry); + spin_unlock(&vl_dev->notify_q_lock); - if (unlikely(status & VIRTIO_MMIO_INT_CONFIG)) - virtio_config_changed(&vl_dev->vdev); + /* Decrease atomically the notification counters */ + atomic_dec(&vl_dev->data->avail_notifs); + atomic_dec(&loopback_devices.pending_notifs); - if (likely(status & VIRTIO_MMIO_INT_VRING)) { - spin_lock(&vl_dev->lock); + /* Find which is the corresponing vq and trigger the notification */ list_for_each_entry(info, &vl_dev->virtqueues, node) { - (void)vring_interrupt(irq, info->vq); + if (info->vq->index == index) { + (void)trigger_notification(info->vq); + /* Decrease the notification handlers */ + return; + } } - spin_unlock(&vl_dev->lock); + spin_lock(&vl_dev->notify_q_lock); } + spin_unlock(&vl_dev->notify_q_lock); +} + +static bool available_notifications(void) +{ + return atomic_read(&loopback_devices.pending_notifs) > 0; +} + +static void set_dev_credits(struct virtio_loopback_device *vl_dev, int64_t remaining_credits) +{ + if (remaining_credits > 0) { + if (remaining_credits > vl_dev->data->vdev_data->init_notif_credits) + atomic_set(&vl_dev->data->notif_credits, vl_dev->data->vdev_data->init_notif_credits); + else + atomic_set(&vl_dev->data->notif_credits, (uint32_t)remaining_credits); + } else { + atomic_set(&vl_dev->data->notif_credits, 0); + } +} + +static void reset_credits(struct virtio_loopback_device *vl_dev) +{ + /* Update timestamp & available credits */ + vl_dev->data->served_timestamp = ktime_get(); + set_dev_credits(vl_dev, vl_dev->data->vdev_data->init_notif_credits); +} + +static uint32_t read_dev_credits(struct virtio_loopback_device *vl_dev) +{ + return atomic_read(&vl_dev->data->notif_credits); +} + +static uint32_t read_dev_notifs(struct virtio_loopback_device *vl_dev) +{ + return atomic_read(&vl_dev->data->avail_notifs); +} + +static struct virtio_loopback_device_node *head_elem(void) +{ + struct virtio_loopback_device_node *device; + + spin_lock(&loopback_devices.running_lock); + device = list_first_entry_or_null( + &loopback_devices.virtio_devices_list, + struct virtio_loopback_device_node, + node); + spin_unlock(&loopback_devices.running_lock); + return device; +} + +static struct virtio_loopback_device_node * + next_elem(struct virtio_loopback_device_node *device) +{ + int ret; + + device = list_next_entry(device, node); + + /* If reached the list head, wrap around to the beginning */ + spin_lock(&loopback_devices.running_lock); + ret = list_entry_is_head(device, &loopback_devices.virtio_devices_list, node); + spin_unlock(&loopback_devices.running_lock); + + if (ret) + device = head_elem(); + + return device; +} + +bool add_dev_to_list(uint32_t array_dev_pos) +{ + struct virtio_loopback_device_node *dev_node; + + /* Add this device to a global list */ + dev_node = kmalloc(sizeof(struct virtio_loopback_device_node), GFP_ATOMIC); + if (!dev_node) + return false; + + /* TODO: Check the next line */ + dev_node->vq_index = array_dev_pos; + INIT_LIST_HEAD(&dev_node->node); + atomic_set(&dev_node->is_deleted, 0); + + spin_lock(&loopback_devices.running_lock); + list_add_tail(&dev_node->node, &loopback_devices.virtio_devices_list); + spin_unlock(&loopback_devices.running_lock); return true; } +static bool is_dev_deleted(struct virtio_loopback_device_node *device) +{ + return atomic_read(&device->is_deleted) == 1; +} + +static void del_dev_from_list(struct virtio_loopback_device_node *device) +{ + spin_lock(&loopback_devices.running_lock); + list_del(&device->node); + spin_unlock(&loopback_devices.running_lock); + kfree(device); +} + +/* + * void clean_dev_notifs_inters(struct virtio_loopback_device_node *device) + * { + * struct notify_data *entry, *tmp; + * struct virtio_loopback_device *vl_dev = loopback_devices.devices[device->vq_index]; + * int i, avail_inters = atomic_read(&vl_dev->data->avail_inters); + * + * spin_lock(&vl_dev->notify_q_lock); + * list_for_each_entry_safe(entry, tmp, &vl_dev->notify_list, list) { + * atomic_dec(&vl_dev->data->avail_notifs); + * atomic_dec(&loopback_devices.pending_notifs); + * } + * spin_unlock(&vl_dev->notify_q_lock); + * + * for (i = 0; i < avail_inters; i++) { + * atomic_dec(&vl_dev->data->avail_inters); + * atomic_dec(&loopback_devices.pending_inters); + * } + * } + */ + +void note_dev_deletion(struct virtio_loopback_device *vl_dev) +{ + struct virtio_loopback_device_node *device, *temp = NULL; + + spin_lock(&loopback_devices.running_lock); + list_for_each_entry(device, &loopback_devices.virtio_devices_list, node) { + if (vl_dev == loopback_devices.devices[device->vq_index]) { + temp = device; + break; + } + } + spin_unlock(&loopback_devices.running_lock); + + if (temp) + atomic_set(&device->is_deleted, 1); +} + +/* + * void clean_deleted_devs(void) + * { + * struct virtio_loopback_device_node *temp = NULL; + * + * spin_lock(&loopback_devices.running_lock); + * list_for_each_entry_safe(device, temp, &loopback_devices.virtio_devices_list, node) { + * if (is_dev_deleted(device)) { + * list_del(&device->node); + * kfree(device); + * } + * } + * spin_unlock(&loopback_devices.running_lock); + * } + */ + +static void clean_all_devs(void) +{ + struct virtio_loopback_device_node *device = NULL, *temp = NULL; + + spin_lock(&loopback_devices.running_lock); + list_for_each_entry_safe(device, temp, &loopback_devices.virtio_devices_list, node) { + list_del(&device->node); + kfree(device); + } + spin_unlock(&loopback_devices.running_lock); +} + +/* + * static bool is_node_in_list(struct virtio_loopback_device_node *device) + * { + * struct virtio_loopback_device_node *temp; + * bool ret = false; + * + * rcu_read_lock(); + * list_for_each_entry_rcu(temp, &loopback_devices.virtio_devices_list, node) { + * if (temp == device) { + * ret = true; + * break; + * } + * } + * rcu_read_unlock(); + * + * return ret; + * } + */ + +static bool available_interrupts(void) +{ + return atomic_read(&loopback_devices.pending_inters) > 0; +} + +static uint32_t read_dev_inters(struct virtio_loopback_device *vl_dev) +{ + return atomic_read(&vl_dev->data->avail_inters); +} + +static uint32_t highest_active_priority_notifs(void) +{ + struct virtio_loopback_device_node *device; + struct virtio_loopback_device *vl_dev; + uint32_t max_priority = 0; + + spin_lock(&loopback_devices.running_lock); + list_for_each_entry(device, &loopback_devices.virtio_devices_list, node) { + if (is_dev_deleted(device)) + continue; + vl_dev = loopback_devices.devices[device->vq_index]; + if ((read_dev_notifs(vl_dev) > 0) || (read_dev_inters(vl_dev) > 0)) + if (vl_dev->data->priority_group > max_priority) + max_priority = vl_dev->data->priority_group; + } + spin_unlock(&loopback_devices.running_lock); + + return max_priority; +} + +static void update_highest_active_prior_notifs(void) +{ + uint32_t current_highest_priority = highest_active_priority_notifs(); + + atomic_set(&loopback_devices.highest_active_prior_notifs, current_highest_priority); +} + +static bool dev_highest_prior_notifs(struct virtio_loopback_device *vl_dev) +{ + return vl_dev->data->priority_group >= + atomic_read(&loopback_devices.highest_active_prior_notifs); +} + +static uint64_t read_dev_served_timestamp(struct virtio_loopback_device *vl_dev) +{ + return vl_dev->data->served_timestamp; +} + +static bool oldest_active_dev_in_group(struct virtio_loopback_device *curr_vl_dev) +{ + struct virtio_loopback_device_node *device; + struct virtio_loopback_device *vl_dev; + uint64_t oldest_active_dev_time = (uint64_t)ktime_get(); + + spin_lock(&loopback_devices.running_lock); + list_for_each_entry(device, &loopback_devices.virtio_devices_list, node) { + if (is_dev_deleted(device)) + continue; + vl_dev = loopback_devices.devices[device->vq_index]; + /* Iterate only on active devices */ + if ((read_dev_notifs(vl_dev) > 0) || (read_dev_inters(vl_dev) > 0)) + /* Iterate only on active devices the same group */ + if ((vl_dev->data->priority_group == curr_vl_dev->data->priority_group) && + (read_dev_served_timestamp(vl_dev) < oldest_active_dev_time)) + /* Save the oldest timestamp of a device aligned with above critirias */ + oldest_active_dev_time = read_dev_served_timestamp(vl_dev); + } + spin_unlock(&loopback_devices.running_lock); + + return oldest_active_dev_time == read_dev_served_timestamp(curr_vl_dev); +} + +/* the interrupt function used when receiving an IRQ */ +static bool vl_interrupt(struct virtio_loopback_device *vl_dev, int irq) +{ + struct virtio_loopback_vq_info *info; + + spin_lock(&vl_dev->lock); + list_for_each_entry(info, &vl_dev->virtqueues, node) { + (void)vring_interrupt(irq, info->vq); + } + spin_unlock(&vl_dev->lock); + + return true; +} + +/* + * Pseudo algorith: with groups (implementation 1) + * + * For dev in dev_list + * + * if dev->priority != active_list_highest_prior or + * dev_idle or + * dev_older_in_group() + * go next + * + * while(time(dev_credits) { + * trigger_notifications + * } + * + * update_highest_priority() + * + */ + +/* + * Pseudo algorith: with groups (implementation 2) + * + * idle_list_dev = dev_1, dev_2, ... , dev_n + * active_list_dev = null + * active_list_highest_prior = 'A' + * + * for dev in active_list_dev + * + * if dev->priority != active_list_highest_prior or + * dev_older_in_group() + * go next + * + * while(time(cred_dev)) + * trigger_notifications + * + * remove(dev, active_list_dev) + * add(dev, idle_list_dev) + * update_highest_priority() + * + */ + +int notif_sched_func(void *data) +{ + struct virtio_loopback_device *vl_dev; + struct virtio_loopback_device_node *device = NULL, *temp = NULL; + ktime_t starting_time, deadline; + + /* Wait the first notification */ + while (!available_notifications() && !kthread_should_stop()) { + wait_event_timeout( + loopback_devices.wq_notifs_inters, + available_notifications() || kthread_should_stop(), + 100 * HZ); + } + + if (kthread_should_stop()) + goto sched_exit; + + device = head_elem(); + if (unlikely(!device)) { + pr_err("Device list is empty - exit\n"); + return 1; + } + + while (!kthread_should_stop()) { + if ((available_notifications() || + available_interrupts()) && + !list_empty(&loopback_devices.virtio_devices_list)) { + + if (is_dev_deleted(device)) { + temp = device; + device = next_elem(device); + del_dev_from_list(temp); + continue; + } + + vl_dev = loopback_devices.devices[device->vq_index]; + + pr_debug("Available notifs: %u\n", atomic_read(&loopback_devices.pending_notifs)); + pr_debug("Available inters: %u\n", atomic_read(&loopback_devices.pending_inters)); + pr_debug("Device %lu avail credits: %u, avail notifications %u, avail_inters: %u\n", + vl_dev->data->vdev_data->init_notif_credits, + read_dev_credits(vl_dev), + read_dev_inters(vl_dev), + read_dev_notifs(vl_dev)); + + /* + * We need to go to the next device if: + * a) Current device does not have available notifications AND + * current device does not have available interrupts + * b) There is another pending device with higher priority + * c) There is another pending device in the same group + * which has not been served for longer time. + */ + + if (((read_dev_notifs(vl_dev) == 0) && + (read_dev_inters(vl_dev) == 0)) || + (!dev_highest_prior_notifs(vl_dev)) || + (!oldest_active_dev_in_group(vl_dev))) { + device = next_elem(device); + continue; + } + + pr_debug("Run Device %lu\n", + vl_dev->data->vdev_data->init_notif_credits); + + /* + * Keep the active highest priority in a variable + * and continue triggering notications only if the + * devices has priority equal or bigger then the highest. + * This helps to give control to the device with + * highest priority immediatly without waiting the + * running device to complete it turn. + */ + starting_time = ktime_get(); + deadline = ktime_add_ms(starting_time, read_dev_credits(vl_dev)); + while (ktime_before(starting_time, deadline) && + !kthread_should_stop() && + dev_highest_prior_notifs(vl_dev)) { + if (read_dev_notifs(vl_dev) > 0) { + trigger_dev_notif(vl_dev); + } else if (read_dev_inters(vl_dev) > 0) { + atomic_dec(&vl_dev->data->avail_inters); + atomic_dec(&loopback_devices.pending_inters); + + vl_interrupt(vl_dev, 0); + } else { + /* Give some time for the current device */ + wait_event_timeout( + vl_dev->wq_notifs_inters, + (read_dev_notifs(vl_dev) > 0) || + (read_dev_inters(vl_dev) > 0) || + kthread_should_stop(), + msecs_to_jiffies(5)); + } + /* Update currnet time */ + starting_time = ktime_get(); + } + + /* + * If the device has not consumed its entire time, + * save the remaining credits for later usage. + */ + set_dev_credits(vl_dev, ktime_ms_delta(deadline, starting_time)); + if (read_dev_credits(vl_dev) == 0) + reset_credits(vl_dev); + + device = next_elem(device); + update_highest_active_prior_notifs(); + + } else { + wait_event_timeout( + loopback_devices.wq_notifs_inters, + ((available_notifications() || available_interrupts()) && + !list_empty(&loopback_devices.virtio_devices_list)) || + kthread_should_stop(), + 100 * HZ); + } + } + +sched_exit: + pr_info("Clean any remaining devices\n"); + clean_all_devs(); + + pr_info("Exiting notification thread\n"); + return 0; +} + +/* The notify function used when creating a virtqueue */ +static bool vl_notify(struct virtqueue *vq) +{ + struct virtio_loopback_device *vl_dev = + to_virtio_loopback_device(vq->vdev); + struct notify_data *data; + + pr_debug("VIRTIO_NOTIFY\n"); + + /* Create the new node */ + data = kmalloc(sizeof(struct notify_data), GFP_ATOMIC); + if (!data) + return false; + + data->index = vq->index; + INIT_LIST_HEAD(&data->list); + + /* Add in the notify_list, which should be protected! */ + spin_lock(&vl_dev->notify_q_lock); + list_add_tail(&data->list, &vl_dev->notify_list); + spin_unlock(&vl_dev->notify_q_lock); + + pr_debug("Add notification for Device %lu avail credits: %u, avail notifications %u\n", + vl_dev->data->vdev_data->init_notif_credits, + read_dev_credits(vl_dev), + read_dev_notifs(vl_dev)); + + /* + * If device has priorities enabled, add the notification into + * the list and leave the notification thread to schedule it + * when this is appropriate. + */ + if (vl_dev->data->vdev_data->priority_enabled) { + pr_debug("WAKEUP notification list\n"); + + spin_lock(&vl_dev->notify_q_lock); + if (vl_dev->data->priority_group > + atomic_read(&loopback_devices.highest_active_prior_notifs)) + atomic_set(&loopback_devices.highest_active_prior_notifs, + vl_dev->data->priority_group); + spin_unlock(&vl_dev->notify_q_lock); + + /* Update atomically the notification counters */ + atomic_inc(&vl_dev->data->avail_notifs); + atomic_inc(&loopback_devices.pending_notifs); + + wake_up(&vl_dev->wq_notifs_inters); + wake_up(&loopback_devices.wq_notifs_inters); + + return true; + } else { + return trigger_notification(vq); + } +} + + +/* the interrupt function used when receiving an IRQ */ +bool register_interrupt(struct virtio_loopback_device *vl_dev, int irq) +{ + if (vl_dev->data->vdev_data->priority_enabled) { + + pr_debug("Add notification for Device %lu avail credits: %u, avail inters %u\n", + vl_dev->data->vdev_data->init_notif_credits, + read_dev_credits(vl_dev), + read_dev_inters(vl_dev)); + + spin_lock(&vl_dev->notify_q_lock); + if (vl_dev->data->priority_group > + atomic_read(&loopback_devices.highest_active_prior_notifs)) + atomic_set(&loopback_devices.highest_active_prior_notifs, + vl_dev->data->priority_group); + spin_unlock(&vl_dev->notify_q_lock); + + atomic_inc(&vl_dev->data->avail_inters); + atomic_inc(&loopback_devices.pending_inters); + + pr_debug("WAKEUP interrupt list\n"); + wake_up(&vl_dev->wq_notifs_inters); + wake_up(&loopback_devices.wq_notifs_inters); + + return true; + } else { + return vl_interrupt(vl_dev, irq); + } +} + + static void vl_del_vq(struct virtqueue *vq) { struct virtio_loopback_device *vl_dev = @@ -745,6 +1223,9 @@ static void virtio_loopback_release_dev(struct device *_d) struct virtio_loopback_device *vl_dev = to_virtio_loopback_device(vdev); struct platform_device *pdev = vl_dev->pdev; + pr_debug("virtio_loopback_release_dev\n"); + + /* Deallocte platform data */ devm_kfree(&pdev->dev, vl_dev); } @@ -831,13 +1312,16 @@ static int virtio_loopback_probe(struct platform_device *pdev) vl_dev->pdev = pdev; INIT_LIST_HEAD(&vl_dev->virtqueues); spin_lock_init(&vl_dev->lock); - /* Initialize the workqueue */ + + /* Initialize the notifications related data structures */ vl_dev->notify_workqueue = create_singlethread_workqueue("notify_workqueue"); INIT_WORK(&vl_dev->notify_work, notify_work_handler); INIT_LIST_HEAD(&vl_dev->notify_list); spin_lock_init(&vl_dev->notify_q_lock); + init_waitqueue_head(&vl_dev->wq_notifs_inters); + /* Set platform data */ platform_set_drvdata(pdev, vl_dev); /* Insert new entry data */ @@ -848,12 +1332,20 @@ out: } #if LINUX_VERSION_CODE > KERNEL_VERSION(6, 10, 8) -void virtio_loopback_remove(struct platform_device *pdev) +static void virtio_loopback_remove(struct platform_device *pdev) #else -int virtio_loopback_remove(struct platform_device *pdev) +static int virtio_loopback_remove(struct platform_device *pdev) #endif { - struct virtio_loopback_device *vl_dev = platform_get_drvdata(pdev); + struct virtio_loopback_device *vl_dev; + + pr_debug("virtio_loopback_remove\n"); + vl_dev = platform_get_drvdata(pdev); + + if (vl_dev->data == NULL) { + pr_debug("Dev already deallocated\n"); + return 0; + } /* Destroy the notify workqueue */ flush_workqueue(vl_dev->notify_workqueue); @@ -862,10 +1354,16 @@ int virtio_loopback_remove(struct platform_device *pdev) if (vl_dev->data) { unregister_virtio_device(&vl_dev->vdev); pr_info("unregister_virtio_device!\n"); - /* Proceed to de-activating the data for this entry */ - vl_dev->data = NULL; } + /* Subsequently free the device data */ + free_page((unsigned long)vl_dev->data->info->data); + kfree(vl_dev->data->info); + eventfd_ctx_put(vl_dev->data->efd_ctx); + vl_dev->data->efd_ctx = NULL; + kfree(vl_dev->data); + vl_dev->data = NULL; + #if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 10, 8) return 0; #endif @@ -920,7 +1418,7 @@ static uint64_t read_adapter(uint64_t fn_id, uint64_t size, atomic_read(&((struct virtio_neg *)(dev_data->info->data))->done) != 1) wait_event_timeout(dev_data->wq, atomic_read(&((struct virtio_neg *)(dev_data->info->data))->done) == 1, - 1 * HZ); + 100 * HZ); result = ((struct virtio_neg *)(dev_data->info->data))->data; @@ -967,7 +1465,7 @@ static void write_adapter(uint64_t data, uint64_t fn_id, uint64_t size, atomic_read(&((struct virtio_neg *)(dev_data->info->data))->done) != 1) wait_event_timeout(dev_data->wq, atomic_read(&((struct virtio_neg *)(dev_data->info->data))->done) == 1, - 1 * HZ); + 100 * HZ); mutex_unlock(&(dev_data)->read_write_lock); } diff --git a/virtio_loopback_driver.c b/virtio_loopback_driver.c index 4284f0a..0fb3ceb 100644 --- a/virtio_loopback_driver.c +++ b/virtio_loopback_driver.c @@ -37,8 +37,8 @@ MODULE_LICENSE("GPL"); /* The global data for the loopback */ -static struct loopback_device_data loopback_data; -static struct loopback_devices_array loopback_devices; +struct loopback_device_data loopback_data; +struct loopback_devices_array loopback_devices; /* * This function registers all mmap calls done by the user-space into an array @@ -414,7 +414,8 @@ int insert_entry_data(struct virtio_loopback_device *vl_dev, int id) /* Helper function to mark an entry as active */ static struct virtio_loopback_device * -activate_entry_data(struct device_data *data, uint32_t curr_dev_id) +activate_entry_data(struct file_priv_data *file_data, + uint32_t curr_dev_id) { struct virtio_loopback_device *vl_dev = NULL; @@ -422,7 +423,26 @@ activate_entry_data(struct device_data *data, uint32_t curr_dev_id) if (curr_dev_id < MAX_PDEV) { /* Find and store the data */ vl_dev = loopback_devices.devices[curr_dev_id]; - vl_dev->data = data; + vl_dev->data = file_data->dev_data; + vl_dev->data->vdev_data = &file_data->device_info; + + /* Add this device to a global list */ + if (!add_dev_to_list(curr_dev_id)) + return NULL; + + /* Set credits & last served timestamp */ + atomic_set(&vl_dev->data->notif_credits, + vl_dev->data->vdev_data->init_notif_credits); + vl_dev->data->served_timestamp = ktime_get(); + + /* Set available notifs */ + atomic_set(&vl_dev->data->avail_notifs, 0); + + /* Set device group */ + vl_dev->data->priority_group = vl_dev->data->vdev_data->priority_group; + + /* Set available interupts */ + atomic_set(&vl_dev->data->avail_inters, 0); } return vl_dev; @@ -435,7 +455,7 @@ static int start_loopback(struct file_priv_data *file_data, int ret; /* Activate the entry */ - vl_dev = activate_entry_data(file_data->dev_data, curr_dev_id); + vl_dev = activate_entry_data(file_data, curr_dev_id); if (vl_dev) { file_data->vl_dev_irq = vl_dev; /* Register the activated vl_dev in the system */ @@ -547,6 +567,7 @@ static long loopback_ioctl(struct file *file, unsigned int cmd, sizeof(struct virtio_device_info_struct))) return -EFAULT; + pr_crit("Priority: %lu\n", file_data->device_info.init_notif_credits); /* Read and increase that value atomically */ curr_avail_dev_id = atomic_add_return(1, &loopback_devices.device_num) - 1; @@ -573,13 +594,7 @@ static long loopback_ioctl(struct file *file, unsigned int cmd, case IRQ: if (copy_from_user(&irq, (int *) arg, sizeof(int))) return -EFAULT; - /* - * Both of the interrupt ways work but a) is more stable - * and b) has better performance: - * a) vl_interrupt(NULL); - * b) queue_work(interrupt_workqueue, &async_interrupt); - */ - vl_interrupt(file_data->vl_dev_irq, irq); + register_interrupt(file_data->vl_dev_irq, irq); break; case SHARE_VQS: if (copy_from_user(&queue_sel, (uint32_t *) arg, @@ -687,6 +702,19 @@ error_kmalloc: return -ENOMEM; } +static int start_notif_thread(void) +{ + loopback_data.notif_thread = kthread_run(notif_sched_func, NULL, + "notif_thread"); + if (IS_ERR(loopback_data.notif_thread)) { + pr_err("Failed to create kernel thread\n"); + return PTR_ERR(loopback_data.notif_thread); + } + + pr_info("Kernel notif thread started successfully\n"); + return 0; +} + static int loopback_release(struct inode *inode, struct file *file) { struct file_priv_data *file_data = @@ -696,31 +724,35 @@ static int loopback_release(struct inode *inode, struct file *file) struct mmap_data *mm_data = (struct mmap_data *)file_data->mm_data; pr_info("Releasing the device\n"); + + /* Unregister from the list */ + note_dev_deletion(file_data->vl_dev_irq); + /* * This makes the read/write do not wait * for the virtio-loopback-adapter if * the last has closed the fd */ dev_data->valid_eventfd = false; + /* Active entry found */ if (file_data->vl_dev_irq) { pr_debug("About to cancel the work\n"); + /* TODO: Move this into virtio_loopback_remove */ /* Cancel any pending work */ cancel_work_sync(&file_data->vl_dev_irq->notify_work); /* Continue with the vl_dev unregister */ - virtio_loopback_driver.remove(file_data->vl_dev_irq->pdev); + platform_device_unregister(file_data->vl_dev_irq->pdev); file_data->vl_dev_irq = NULL; } - /* Subsequently free the dev_data */ - free_page((unsigned long)dev_data->info->data); - kfree(dev_data->info); - eventfd_ctx_put(dev_data->efd_ctx); - dev_data->efd_ctx = NULL; - kfree(dev_data); - file_data->dev_data = NULL; + + /* Proceed to de-activating the data for this entry */ + dev_data = NULL; + /* Continue with the mm_data */ kfree(mm_data); file_data->mm_data = NULL; + /* Last, free the private data */ kfree(file_data); file->private_data = NULL; @@ -773,22 +805,42 @@ static int __init loopback_init(void) for (i = 0; i < MAX_PDEV; i++) init_completion(&loopback_devices.reg_vl_dev_completion[i]); - return 0; + /* Init loopback device list */ + INIT_LIST_HEAD(&loopback_devices.virtio_devices_list); + + /* Init notification / interrupt wait queue */ + init_waitqueue_head(&loopback_devices.wq_notifs_inters); + + /* Init spinlock for when device is running */ + spin_lock_init(&loopback_devices.running_lock); + + /* Init pending notifications counter */ + atomic_set(&loopback_devices.pending_notifs, 0); + + /* Init pending interrupts counter */ + atomic_set(&loopback_devices.pending_inters, 0); + + /* Init current highest notifications priority */ + atomic_set(&loopback_devices.highest_active_prior_notifs, 0); + + /* Start nofication thread */ + return start_notif_thread(); } static void __exit loopback_exit(void) { - int i; - uint32_t max_used_device_num = - atomic_read(&loopback_devices.device_num); + int ret; pr_info("Exit virtio_loopback driver!\n"); - /* Unregister loopback devices */ - for (i = 0; i < max_used_device_num; i++) - if (loopback_devices.devices[i]) - platform_device_unregister( - loopback_devices.devices[i]->pdev); + /* Wait for notification / interrupt thread to stop */ + if (loopback_data.notif_thread) { + ret = kthread_stop(loopback_data.notif_thread); + if (ret) { + pr_err("Kernel notif thread returned error: %d\n" + , ret); + } + } /* Unregister virtio_loopback_transport */ platform_driver_unregister(&virtio_loopback_driver); diff --git a/virtio_loopback_driver.h b/virtio_loopback_driver.h index 57e2ce5..3e02222 100644 --- a/virtio_loopback_driver.h +++ b/virtio_loopback_driver.h @@ -103,6 +103,9 @@ struct virtio_device_info_struct { unsigned long version; unsigned long device_id; unsigned long vendor; + bool priority_enabled; + unsigned long init_notif_credits; + unsigned long priority_group; }; struct virtio_neg { @@ -162,16 +165,35 @@ struct device_data { bool valid_eventfd; /* vq data */ struct vq_data vq_data; + + /* virtio device data */ + struct virtio_device_info_struct *vdev_data; + bool priority_enabled; + uint32_t priority_group; + atomic_t notif_credits; + atomic_t avail_notifs; + atomic_t avail_inters; + uint64_t served_timestamp; }; /* Data describing each entry of the driver */ struct loopback_devices_array { /* Array of probed devices */ struct virtio_loopback_device *devices[MAX_PDEV]; + /* list of the devices */ + struct list_head virtio_devices_list; /* Number of available devices */ atomic_t device_num; /* Registration completion */ struct completion reg_vl_dev_completion[MAX_PDEV]; + /* Counter for all devices pending notifications */ + atomic_t highest_active_prior_notifs; + atomic_t pending_notifs; + atomic_t pending_inters; + wait_queue_head_t wq_notifs_inters; + + /* Spin lock for removing the device */ + spinlock_t running_lock; }; /* Data concealed in the file private pointer */ @@ -209,6 +231,9 @@ struct virtio_loopback_device { spinlock_t notify_q_lock; struct list_head notify_list; struct work_struct notify_work; + + /* Notification waitqueue */ + wait_queue_head_t wq_notifs_inters; }; struct virtio_loopback_vq_info { @@ -218,12 +243,27 @@ struct virtio_loopback_vq_info { struct list_head node; }; +struct virtio_loopback_device_node { + /* the actual virtqueue */ + uint32_t vq_index; + atomic_t is_deleted; + /* the list node for the virtqueues list */ + struct list_head node; + struct rcu_head rcu; +}; + /* Notify data*/ struct notify_data { uint32_t index; struct list_head list; }; +/* Interrupt data*/ +struct interrupt_data { + uint32_t index; + struct list_head list; +}; + /* Shared data structure between driver and user-space application */ struct mmap_info { void *data; @@ -246,6 +286,7 @@ struct loopback_device_data { /* sysfs class structure */ struct class *class; struct cdev cdev; + struct task_struct *notif_thread; }; /* Global variables */ @@ -254,6 +295,11 @@ extern struct platform_driver virtio_loopback_driver; /* Global functions */ int insert_entry_data(struct virtio_loopback_device *vl_dev, int id); int loopback_register_virtio_dev(struct virtio_loopback_device *vl_dev); -bool vl_interrupt(struct virtio_loopback_device *vl_dev, int irq); +bool register_interrupt(struct virtio_loopback_device *vl_dev, int irq); +int notif_sched_func(void *data); +bool add_dev_to_list(uint32_t array_dev_pos); +void note_dev_deletion(struct virtio_loopback_device *vl_dev); +extern struct loopback_devices_array loopback_devices; +extern struct loopback_device_data loopback_data; #endif /* __LOOPBACK_H__ */ |