diff options
Diffstat (limited to 'drivers/edgetpu/edgetpu-device-group.c')
-rw-r--r-- | drivers/edgetpu/edgetpu-device-group.c | 215 |
1 files changed, 138 insertions, 77 deletions
diff --git a/drivers/edgetpu/edgetpu-device-group.c b/drivers/edgetpu/edgetpu-device-group.c index 29b84c7..3b2e6fc 100644 --- a/drivers/edgetpu/edgetpu-device-group.c +++ b/drivers/edgetpu/edgetpu-device-group.c @@ -60,6 +60,24 @@ struct edgetpu_host_map { struct sg_table *sg_tables; }; +/* + * A helper structure for the return value of find_sg_to_sync(). + */ +struct sglist_to_sync { + struct scatterlist *sg; + int nelems; + /* + * The SG that has its length modified by find_sg_to_sync(). + * Can be NULL, which means no SG's length was modified. + */ + struct scatterlist *last_sg; + /* + * find_sg_to_sync() will temporarily change the length of @last_sg. + * This is used to restore the length. + */ + unsigned int orig_length; +}; + #ifdef EDGETPU_HAS_MCP /* parameter to be used in async KCI jobs */ @@ -86,7 +104,7 @@ static int edgetpu_kci_leave_group_worker(struct kci_worker_param *param) struct edgetpu_dev *etdev = edgetpu_device_group_nth_etdev(group, i); etdev_dbg(etdev, "%s: leave group %u", __func__, group->workload_id); - edgetpu_kci_update_usage(etdev); + edgetpu_kci_update_usage_async(etdev); edgetpu_kci_leave_group(etdev->kci); return 0; } @@ -101,16 +119,11 @@ static int edgetpu_group_kci_open_device(struct edgetpu_device_group *group) if (edgetpu_group_mailbox_detached_locked(group)) return 0; mailbox_id = edgetpu_group_context_id_locked(group); - ret = edgetpu_kci_open_device(group->etdev->kci, BIT(mailbox_id)); - /* - * This should only happen when the FW hasn't driven this KCI, log once - * to prevent log storm. - */ + ret = edgetpu_mailbox_activate(group->etdev, BIT(mailbox_id)); if (ret) - etdev_warn_once(group->etdev, "Open device failed with %d", - ret); + etdev_err(group->etdev, "activate mailbox failed with %d", ret); atomic_inc(&group->etdev->job_count); - return 0; + return ret; } static void edgetpu_group_kci_close_device(struct edgetpu_device_group *group) @@ -121,15 +134,10 @@ static void edgetpu_group_kci_close_device(struct edgetpu_device_group *group) if (edgetpu_group_mailbox_detached_locked(group)) return; mailbox_id = edgetpu_group_context_id_locked(group); - ret = edgetpu_kci_close_device(group->etdev->kci, BIT(mailbox_id)); - - /* - * This should only happen when the FW hasn't driven this KCI, log once - * to prevent log storm. - */ + ret = edgetpu_mailbox_deactivate(group->etdev, BIT(mailbox_id)); if (ret) - etdev_warn_once(group->etdev, "Close device failed with %d", - ret); + etdev_err(group->etdev, "deactivate mailbox failed with %d", + ret); return; } @@ -147,7 +155,7 @@ static void edgetpu_group_kci_close_device(struct edgetpu_device_group *group) static void edgetpu_device_group_kci_leave(struct edgetpu_device_group *group) { #ifdef EDGETPU_HAS_MULTI_GROUPS - edgetpu_kci_update_usage(group->etdev); + edgetpu_kci_update_usage_async(group->etdev); return edgetpu_group_kci_close_device(group); #else /* !EDGETPU_HAS_MULTI_GROUPS */ struct kci_worker_param *params = @@ -347,6 +355,12 @@ static void do_detach_mailbox_locked(struct edgetpu_device_group *group) group->context_id = EDGETPU_CONTEXT_INVALID; } +static inline bool is_finalized_or_errored(struct edgetpu_device_group *group) +{ + return edgetpu_device_group_is_finalized(group) || + edgetpu_device_group_is_errored(group); +} + int edgetpu_group_set_eventfd(struct edgetpu_device_group *group, uint event_id, int eventfd) { @@ -423,7 +437,7 @@ static void edgetpu_device_group_release(struct edgetpu_device_group *group) struct edgetpu_dev *etdev; edgetpu_group_clear_events(group); - if (edgetpu_device_group_is_finalized(group)) { + if (is_finalized_or_errored(group)) { for (i = 0; i < group->n_clients; i++) { etdev = edgetpu_device_group_nth_etdev(group, i); edgetpu_sw_wdt_dec_active_ref(etdev); @@ -550,8 +564,7 @@ static bool edgetpu_in_any_group_locked(struct edgetpu_dev *etdev) return etdev->n_groups; } -/* caller must hold the client's etdev state_lock. */ -void edgetpu_device_group_leave_locked(struct edgetpu_client *client) +void edgetpu_device_group_leave(struct edgetpu_client *client) { struct edgetpu_device_group *group; struct edgetpu_list_group *l; @@ -573,7 +586,7 @@ void edgetpu_device_group_leave_locked(struct edgetpu_client *client) if (edgetpu_device_group_is_waiting(group)) { if (edgetpu_device_group_leader(group) == client) will_disband = true; - } else if (edgetpu_device_group_is_finalized(group)) { + } else if (is_finalized_or_errored(group)) { will_disband = true; } @@ -608,18 +621,6 @@ void edgetpu_device_group_leave_locked(struct edgetpu_client *client) mutex_unlock(&client->etdev->groups_lock); } -void edgetpu_device_group_leave(struct edgetpu_client *client) -{ - mutex_lock(&client->etdev->state_lock); - /* - * The only chance that the state is not GOOD here is the wdt timeout - * action is working. Let that worker perform the group leaving. - */ - if (client->etdev->state == ETDEV_STATE_GOOD) - edgetpu_device_group_leave_locked(client); - mutex_unlock(&client->etdev->state_lock); -} - struct edgetpu_device_group * edgetpu_device_group_alloc(struct edgetpu_client *client, const struct edgetpu_mailbox_attr *attr) @@ -684,7 +685,7 @@ edgetpu_device_group_alloc(struct edgetpu_client *client, return group; error_leave_group: - edgetpu_device_group_leave_locked(client); + edgetpu_device_group_leave(client); error_put_group: edgetpu_device_group_put(group); error: @@ -762,7 +763,7 @@ int edgetpu_device_group_finalize(struct edgetpu_device_group *group) mutex_lock(&group->lock); /* do nothing if the group is finalized */ - if (edgetpu_device_group_is_finalized(group)) + if (is_finalized_or_errored(group)) goto err_unlock; if (!edgetpu_device_group_is_waiting(group)) { @@ -823,6 +824,7 @@ int edgetpu_device_group_finalize(struct edgetpu_device_group *group) edgetpu_usr_init_group(group); + /* send KCI only if the device is powered on */ if (edgetpu_wakelock_count_locked(leader->wakelock)) { ret = edgetpu_device_group_kci_finalized(group); if (ret) @@ -1085,9 +1087,9 @@ static void edgetpu_host_map_show(struct edgetpu_mapping *map, */ static struct page **edgetpu_pin_user_pages(struct edgetpu_device_group *group, struct edgetpu_map_ioctl *arg, - uint *pnum_pages) + uint *pnum_pages, bool *preadonly) { - u64 host_addr = arg->host_address; + u64 host_addr = untagged_addr(arg->host_address); u64 size = arg->size; const enum dma_data_direction dir = arg->flags & EDGETPU_MAP_DIR_MASK; uint num_pages; @@ -1096,12 +1098,14 @@ static struct page **edgetpu_pin_user_pages(struct edgetpu_device_group *group, struct page **pages; int i; int ret; + struct vm_area_struct *vma; + unsigned int foll_flags = FOLL_LONGTERM | FOLL_WRITE; if (size == 0) return ERR_PTR(-EINVAL); offset = host_addr & (PAGE_SIZE - 1); /* overflow check */ - if (unlikely(size + offset < size)) + if (unlikely((size + offset) / PAGE_SIZE >= UINT_MAX - 1 || size + offset < size)) return ERR_PTR(-ENOMEM); num_pages = (size + offset) / PAGE_SIZE; if ((size + offset) % PAGE_SIZE) @@ -1118,10 +1122,20 @@ static struct page **edgetpu_pin_user_pages(struct edgetpu_device_group *group, return ERR_PTR(-ENOMEM); /* - * DMA Buffers appear to be always dirty, so mark pages as always writeable + * The host pages might be read-only and could fail if we attempt to pin + * it with FOLL_WRITE. + * default to read/write if find_extend_vma returns NULL */ - ret = pin_user_pages_fast(host_addr & PAGE_MASK, num_pages, - FOLL_WRITE | FOLL_LONGTERM, pages); + vma = find_extend_vma(current->mm, host_addr & PAGE_MASK); + if (vma && !(vma->vm_flags & VM_WRITE)) { + foll_flags &= ~FOLL_WRITE; + *preadonly = true; + } else { + *preadonly = false; + } + + ret = pin_user_pages_fast(host_addr & PAGE_MASK, num_pages, foll_flags, + pages); if (ret < 0) { etdev_dbg(etdev, "get user pages failed %u:%pK-%u: %d", group->workload_id, (void *)host_addr, num_pages, @@ -1232,32 +1246,60 @@ error: } /* - * Find the scatterlist covering range [start, end). + * Finds the scatterlist covering range [start, end). + * + * The found SG and number of elements will be stored in @sglist. + * + * To ensure the returned SG list strictly locates in range [start, end), the + * last SG's length is shrunk. Therefore caller must call + * restore_sg_after_sync(@sglist) after the DMA sync is performed. * - * Returns NULL if: - * - @start is larger than the whole SG table + * @sglist->nelems == 0 means the target range exceeds the whole SG table. */ -static struct scatterlist *find_sg_within(const struct sg_table *sgt, u64 start, - u64 end, int *nelems) +static void find_sg_to_sync(const struct sg_table *sgt, u64 start, u64 end, + struct sglist_to_sync *sglist) { - struct scatterlist *sg, *sg_to_sync = NULL; + struct scatterlist *sg; size_t cur_offset = 0; int i; - *nelems = 0; + sglist->sg = NULL; + sglist->nelems = 0; + sglist->last_sg = NULL; + if (unlikely(end == 0)) + return; for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) { - if (end <= cur_offset) - break; if (cur_offset <= start && start < cur_offset + sg->length) - sg_to_sync = sg; - if (sg_to_sync) - (*nelems)++; + sglist->sg = sg; + if (sglist->sg) + ++sglist->nelems; cur_offset += sg->length; + if (end <= cur_offset) { + sglist->last_sg = sg; + sglist->orig_length = sg->length; + /* + * To let the returned SG list have exact length as + * [start, end). + */ + sg->length -= cur_offset - end; + break; + } } +} - return sg_to_sync; +static void restore_sg_after_sync(struct sglist_to_sync *sglist) +{ + if (!sglist->last_sg) + return; + sglist->last_sg->length = sglist->orig_length; } +/* + * Performs DMA sync of the mapping with region [offset, offset + size). + * + * Caller holds mapping's lock, to prevent @hmap being modified / removed by + * other processes. + */ static int group_sync_host_map(struct edgetpu_device_group *group, struct edgetpu_host_map *hmap, u64 offset, u64 size, enum dma_data_direction dir, @@ -1268,29 +1310,32 @@ static int group_sync_host_map(struct edgetpu_device_group *group, for_cpu ? dma_sync_sg_for_cpu : dma_sync_sg_for_device; struct edgetpu_dev *etdev; struct sg_table *sgt; - struct scatterlist *sg; + struct sglist_to_sync sglist; int i; - int nelems; sgt = &hmap->map.sgt; - sg = find_sg_within(sgt, offset, end, &nelems); - if (!sg) + find_sg_to_sync(sgt, offset, end, &sglist); + if (!sglist.nelems) return -EINVAL; + if (IS_MIRRORED(hmap->map.flags)) + etdev = group->etdev; + else + etdev = edgetpu_device_group_nth_etdev(group, + hmap->map.die_index); + sync(etdev->dev, sglist.sg, sglist.nelems, dir); + restore_sg_after_sync(&sglist); + if (IS_MIRRORED(hmap->map.flags)) { - sync(group->etdev->dev, sg, nelems, dir); for (i = 1; i < group->n_clients; i++) { etdev = edgetpu_device_group_nth_etdev(group, i); - sg = find_sg_within(&hmap->sg_tables[i], offset, end, - &nelems); - if (WARN_ON(!sg)) + find_sg_to_sync(&hmap->sg_tables[i], offset, end, + &sglist); + if (WARN_ON(!sglist.sg)) return -EINVAL; - sync(etdev->dev, sg, nelems, dir); + sync(etdev->dev, sglist.sg, sglist.nelems, dir); + restore_sg_after_sync(&sglist); } - } else { - etdev = edgetpu_device_group_nth_etdev(group, - hmap->map.die_index); - sync(etdev->dev, sg, nelems, dir); } return 0; @@ -1310,18 +1355,24 @@ int edgetpu_device_group_map(struct edgetpu_device_group *group, enum edgetpu_context_id context_id; const u32 mmu_flags = map_to_mmu_flags(flags) | EDGETPU_MMU_HOST; int i; + bool readonly; if (!valid_dma_direction(flags & EDGETPU_MAP_DIR_MASK)) return -EINVAL; /* Pin user pages before holding any lock. */ - pages = edgetpu_pin_user_pages(group, arg, &num_pages); + pages = edgetpu_pin_user_pages(group, arg, &num_pages, &readonly); if (IS_ERR(pages)) return PTR_ERR(pages); + /* If the host pages are read-only, fallback to use DMA_TO_DEVICE. */ + if (readonly) { + flags &= ~EDGETPU_MAP_DIR_MASK; + flags |= EDGETPU_MAP_DMA_TO_DEVICE; + } mutex_lock(&group->lock); context_id = edgetpu_group_context_id_locked(group); if (!edgetpu_device_group_is_finalized(group)) { - ret = -EINVAL; + ret = edgetpu_group_errno(group); goto error; } if (!IS_MIRRORED(flags)) { @@ -1397,7 +1448,7 @@ int edgetpu_device_group_unmap(struct edgetpu_device_group *group, mutex_lock(&group->lock); if (!edgetpu_device_group_is_finalized(group)) { - ret = -EINVAL; + ret = edgetpu_group_errno(group); goto unlock_group; } @@ -1442,7 +1493,7 @@ int edgetpu_device_group_sync_buffer(struct edgetpu_device_group *group, mutex_lock(&group->lock); if (!edgetpu_device_group_is_finalized(group)) { - ret = -EINVAL; + ret = edgetpu_group_errno(group); goto unlock_group; } @@ -1481,6 +1532,9 @@ void edgetpu_group_mappings_show(struct edgetpu_device_group *group, case EDGETPU_DEVICE_GROUP_WAITING: case EDGETPU_DEVICE_GROUP_FINALIZED: break; + case EDGETPU_DEVICE_GROUP_ERRORED: + seq_puts(s, " (errored)"); + break; case EDGETPU_DEVICE_GROUP_DISBANDED: seq_puts(s, ": disbanded\n"); return; @@ -1526,7 +1580,7 @@ int edgetpu_mmap_csr(struct edgetpu_device_group *group, mutex_lock(&group->lock); if (!edgetpu_group_finalized_and_attached(group)) { - ret = -EINVAL; + ret = edgetpu_group_errno(group); goto out; } @@ -1553,7 +1607,7 @@ int edgetpu_mmap_queue(struct edgetpu_device_group *group, mutex_lock(&group->lock); if (!edgetpu_group_finalized_and_attached(group)) { - ret = -EINVAL; + ret = edgetpu_group_errno(group); goto out; } @@ -1601,8 +1655,12 @@ void edgetpu_group_detach_mailbox_locked(struct edgetpu_device_group *group) void edgetpu_group_close_and_detach_mailbox(struct edgetpu_device_group *group) { mutex_lock(&group->lock); - /* only a finalized group may have mailbox attached */ - if (edgetpu_device_group_is_finalized(group)) { + /* + * Only a finalized group may have mailbox attached. + * + * Detaching mailbox for an errored group is also fine. + */ + if (is_finalized_or_errored(group)) { edgetpu_group_kci_close_device(group); edgetpu_group_detach_mailbox_locked(group); } @@ -1623,7 +1681,10 @@ int edgetpu_group_attach_and_open_mailbox(struct edgetpu_device_group *group) int ret = 0; mutex_lock(&group->lock); - /* only attaching mailbox for finalized groups */ + /* + * Only attaching mailbox for finalized groups. + * Don't attach mailbox for errored groups. + */ if (edgetpu_device_group_is_finalized(group)) { ret = edgetpu_group_attach_mailbox_locked(group); if (!ret) |