// SPDX-License-Identifier: GPL-2.0 /* * EdgeTPU support for dma-buf. * * Copyright (C) 2020 Google, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "edgetpu-device-group.h" #include "edgetpu-dmabuf.h" #include "edgetpu-dram.h" #include "edgetpu-internal.h" #include "edgetpu-mapping.h" #include "edgetpu-mmu.h" #include "edgetpu.h" /* * Records objects for mapping a dma-buf to an edgetpu_dev. */ struct dmabuf_map_entry { struct dma_buf_attachment *attachment; /* SG table returned by dma_buf_map_attachment() */ struct sg_table *sgt; /* * The SG table that shrunk and condensed from @sgt with region [0, size), where @size is * the size field in edgetpu_dmabuf_map which owns this entry. */ struct sg_table shrunk_sgt; }; /* * Records the mapping and other fields needed for mapping a dma-buf to a device * group. */ struct edgetpu_dmabuf_map { struct edgetpu_mapping map; u64 size; /* size of this mapping in bytes */ u32 mmu_flags; /* * The array length of @dmabufs is * * 1, for normal dmabuf mappings * * number of devices in @group, for bulk mappings */ struct dma_buf **dmabufs; /* * The length of array @entries will be * * 1, for non-mirrored normal mapping requests * * number of devices in @group, otherwise */ struct dmabuf_map_entry *entries; uint num_entries; }; /* * edgetpu implementation of DMA fence * * @fence: the base DMA fence * @lock: spinlock protecting updates to @fence * @timeline_name: name of the timeline associated with the fence * @group: owning device group * @etfence_list: global list of all edgetpu DMA fences * @group_list: list of DMA fences owned by the same group * * It is likely timelines will become a separate object in the future, * but for now there's a unique named timeline associated with each fence. */ struct edgetpu_dma_fence { struct dma_fence fence; spinlock_t lock; char timeline_name[EDGETPU_SYNC_TIMELINE_NAME_LEN]; struct edgetpu_device_group *group; struct list_head etfence_list; struct list_head group_list; }; /* List of all edgetpu fence objects for debugging. */ static LIST_HEAD(etfence_list_head); static DEFINE_SPINLOCK(etfence_list_lock); static const struct dma_fence_ops edgetpu_dma_fence_ops; static int etdev_add_translations(struct edgetpu_dev *etdev, tpu_addr_t tpu_addr, struct dmabuf_map_entry *entry, u32 mmu_flags, enum dma_data_direction dir, enum edgetpu_context_id ctx_id) { int prot = mmu_flag_to_iommu_prot(mmu_flags, etdev->dev, dir); uint i; u64 offset = 0; int ret; struct sg_table *sgt = &entry->shrunk_sgt; struct scatterlist *sg; for_each_sg(sgt->sgl, sg, sgt->nents, i) { ret = edgetpu_mmu_add_translation(etdev, tpu_addr + offset, sg_dma_address(sg), sg_dma_len(sg), prot, ctx_id); if (ret) goto rollback; offset += sg_dma_len(sg); } edgetpu_mmu_reserve(etdev, tpu_addr, offset); return 0; rollback: edgetpu_mmu_remove_translation(etdev, tpu_addr, offset, ctx_id); return ret; } static void etdev_remove_translations(struct edgetpu_dev *etdev, tpu_addr_t tpu_addr, struct dmabuf_map_entry *entry, enum edgetpu_context_id ctx_id) { uint i; u64 offset = 0; struct sg_table *sgt = &entry->shrunk_sgt; struct scatterlist *sg; for_each_sg(sgt->sgl, sg, sgt->nents, i) { edgetpu_mmu_remove_translation(etdev, tpu_addr + offset, sg_dma_len(sg), ctx_id); offset += sg_dma_len(sg); } edgetpu_mmu_free(etdev, tpu_addr, offset); } /* * Maps to the first entry in @dmap. * * If the first entry is not fetched (this could happen in bulk mappings), * a TPU VA is still allocated according to @dmap->mmu_flags but not mapped. * * Caller holds @group->lock. */ static int etdev_map_dmabuf(struct edgetpu_dev *etdev, struct edgetpu_dmabuf_map *dmap, tpu_addr_t *tpu_addr_p) { struct edgetpu_device_group *group = dmap->map.priv; const enum edgetpu_context_id ctx_id = edgetpu_group_context_id_locked(group); struct dmabuf_map_entry *entry = &dmap->entries[0]; tpu_addr_t tpu_addr; if (!entry->sgt) { /* * This could only happen in bulk mappings, when the dmabuf for * master device is not set (runtime passes EDGETPU_IGNORE_FD). */ tpu_addr = edgetpu_mmu_alloc(etdev, dmap->size, dmap->mmu_flags); if (!tpu_addr) return -ENOSPC; } else { tpu_addr = edgetpu_mmu_tpu_map_sgt(etdev, &entry->shrunk_sgt, dmap->map.dir, ctx_id, dmap->mmu_flags); if (!tpu_addr) return -ENOSPC; } *tpu_addr_p = tpu_addr; return 0; } /* * Reverts etdev_map_dmabuf(). * * Caller holds @group->lock. */ static void etdev_unmap_dmabuf(struct edgetpu_dev *etdev, struct edgetpu_dmabuf_map *dmap, tpu_addr_t tpu_addr) { struct edgetpu_device_group *group = dmap->map.priv; const enum edgetpu_context_id ctx_id = edgetpu_group_context_id_locked(group); struct dmabuf_map_entry *entry = &dmap->entries[0]; if (!entry->sgt) edgetpu_mmu_free(etdev, tpu_addr, dmap->size); else edgetpu_mmu_tpu_unmap_sgt(etdev, tpu_addr, &entry->shrunk_sgt, ctx_id); } /* * Handles mirrored mapping request. * * Caller holds @group->lock. */ static int group_map_dmabuf(struct edgetpu_device_group *group, struct edgetpu_dmabuf_map *dmap, tpu_addr_t *tpu_addr_p) { const enum edgetpu_context_id ctx_id = edgetpu_group_context_id_locked(group); struct edgetpu_dev *etdev = group->etdev; tpu_addr_t tpu_addr; uint i; int ret; ret = etdev_map_dmabuf(etdev, dmap, &tpu_addr); if (ret) return ret; for (i = 1; i < group->n_clients; i++) { etdev = edgetpu_device_group_nth_etdev(group, i); if (!dmap->entries[i].sgt) { edgetpu_mmu_reserve(etdev, tpu_addr, dmap->size); continue; } ret = etdev_add_translations(etdev, tpu_addr, &dmap->entries[i], dmap->mmu_flags, dmap->map.dir, ctx_id); if (ret) goto err_remove; } *tpu_addr_p = tpu_addr; return 0; err_remove: while (i > 1) { i--; etdev = edgetpu_device_group_nth_etdev(group, i); if (!dmap->entries[i].sgt) edgetpu_mmu_free(etdev, tpu_addr, dmap->size); else etdev_remove_translations(etdev, tpu_addr, &dmap->entries[i], ctx_id); } etdev_unmap_dmabuf(group->etdev, dmap, tpu_addr); return ret; } /* * Reverts group_map_dmabuf(). * * Caller holds @group->lock. */ static void group_unmap_dmabuf(struct edgetpu_device_group *group, struct edgetpu_dmabuf_map *dmap, tpu_addr_t tpu_addr) { const enum edgetpu_context_id ctx_id = edgetpu_group_context_id_locked(group); struct edgetpu_dev *etdev; uint i; for (i = 1; i < group->n_clients; i++) { etdev = edgetpu_device_group_nth_etdev(group, i); etdev_remove_translations(etdev, tpu_addr, &dmap->entries[i], ctx_id); } etdev_unmap_dmabuf(group->etdev, dmap, tpu_addr); } /* * Clean resources recorded in @dmap. * * Caller holds the lock of group (map->priv) and ensures the group is in * the finalized state. */ static void dmabuf_map_callback_release(struct edgetpu_mapping *map) { struct edgetpu_dmabuf_map *dmap = container_of(map, struct edgetpu_dmabuf_map, map); struct edgetpu_device_group *group = map->priv; const enum dma_data_direction dir = map->dir; tpu_addr_t tpu_addr = map->device_address; struct edgetpu_dev *etdev; uint i; if (tpu_addr) { if (IS_MIRRORED(map->flags)) { group_unmap_dmabuf(group, dmap, tpu_addr); } else { etdev = edgetpu_device_group_nth_etdev(group, map->die_index); etdev_unmap_dmabuf(etdev, dmap, tpu_addr); } } for (i = 0; i < dmap->num_entries; i++) { struct dmabuf_map_entry *entry = &dmap->entries[i]; sg_free_table(&entry->shrunk_sgt); if (entry->sgt) dma_buf_unmap_attachment(entry->attachment, entry->sgt, dir); if (entry->attachment) dma_buf_detach(dmap->dmabufs[0], entry->attachment); } dma_buf_put(dmap->dmabufs[0]); edgetpu_device_group_put(group); kfree(dmap->dmabufs); kfree(dmap->entries); kfree(dmap); } static void entry_show_dma_addrs(struct dmabuf_map_entry *entry, struct seq_file *s) { struct sg_table *sgt = &entry->shrunk_sgt; if (sgt->nents == 1) { seq_printf(s, "%pad\n", &sg_dma_address(sgt->sgl)); } else { uint i; struct scatterlist *sg = sgt->sgl; seq_puts(s, "["); for (i = 0; i < sgt->nents; i++) { if (i) seq_puts(s, ", "); seq_printf(s, "%pad", &sg_dma_address(sg)); sg = sg_next(sg); } seq_puts(s, "]\n"); } } static void dmabuf_map_callback_show(struct edgetpu_mapping *map, struct seq_file *s) { struct edgetpu_dmabuf_map *dmap = container_of(map, struct edgetpu_dmabuf_map, map); if (IS_MIRRORED(dmap->map.flags)) seq_printf(s, " <%s> mirrored: iova=%#llx pages=%llu %s", dmap->dmabufs[0]->exp_name, map->device_address, DIV_ROUND_UP(dmap->size, PAGE_SIZE), edgetpu_dma_dir_rw_s(map->dir)); else seq_printf(s, " <%s> die %u: iova=%#llx pages=%llu %s", dmap->dmabufs[0]->exp_name, map->die_index, map->device_address, DIV_ROUND_UP(dmap->size, PAGE_SIZE), edgetpu_dma_dir_rw_s(map->dir)); edgetpu_device_dram_dmabuf_info_show(dmap->dmabufs[0], s); seq_puts(s, " dma="); entry_show_dma_addrs(&dmap->entries[0], s); } /* * Allocates and properly sets fields of an edgetpu_dmabuf_map. * * Caller holds group->lock and checks @group is finalized. * * Returns the pointer on success, or NULL on failure. */ static struct edgetpu_dmabuf_map * alloc_dmabuf_map(struct edgetpu_device_group *group, edgetpu_map_flag_t flags) { struct edgetpu_dmabuf_map *dmap = kzalloc(sizeof(*dmap), GFP_KERNEL); struct edgetpu_mapping *map; uint n; if (!dmap) return NULL; if (IS_MIRRORED(flags)) n = group->n_clients; else n = 1; dmap->entries = kcalloc(n, sizeof(*dmap->entries), GFP_KERNEL); if (!dmap->entries) goto err_free; dmap->dmabufs = kcalloc(1, sizeof(*dmap->dmabufs), GFP_KERNEL); if (!dmap->dmabufs) goto err_free; dmap->num_entries = n; dmap->mmu_flags = map_to_mmu_flags(flags) | EDGETPU_MMU_DMABUF; map = &dmap->map; map->flags = flags; map->dir = flags & EDGETPU_MAP_DIR_MASK; map->release = dmabuf_map_callback_release; map->show = dmabuf_map_callback_show; map->priv = edgetpu_device_group_get(group); return dmap; err_free: kfree(dmap->dmabufs); kfree(dmap->entries); kfree(dmap); return NULL; } /* * Clean resources recorded in edgetpu_dmabuf_map, bulk version. * * Caller holds the lock of group (map->priv) and ensures the group is in * the finalized state. */ static void dmabuf_bulk_map_callback_release(struct edgetpu_mapping *map) { struct edgetpu_dmabuf_map *bmap = container_of(map, struct edgetpu_dmabuf_map, map); struct edgetpu_device_group *group = map->priv; const enum dma_data_direction dir = map->dir; const tpu_addr_t tpu_addr = map->device_address; int i; if (tpu_addr) group_unmap_dmabuf(group, bmap, tpu_addr); for (i = 0; i < group->n_clients; i++) { struct dmabuf_map_entry *entry = &bmap->entries[i]; sg_free_table(&entry->shrunk_sgt); if (entry->sgt) dma_buf_unmap_attachment(entry->attachment, entry->sgt, dir); if (entry->attachment) dma_buf_detach(bmap->dmabufs[i], entry->attachment); if (bmap->dmabufs[i]) dma_buf_put(bmap->dmabufs[i]); } edgetpu_device_group_put(group); kfree(bmap->dmabufs); kfree(bmap->entries); kfree(bmap); } static void dmabuf_bulk_map_callback_show(struct edgetpu_mapping *map, struct seq_file *s) { struct edgetpu_dmabuf_map *bmap = container_of(map, struct edgetpu_dmabuf_map, map); int i; seq_printf(s, " bulk: iova=%#llx pages=%llu %s\n", map->device_address, DIV_ROUND_UP(bmap->size, PAGE_SIZE), edgetpu_dma_dir_rw_s(map->dir)); for (i = 0; i < bmap->num_entries; i++) { if (!bmap->dmabufs[i]) { seq_printf(s, " %2d: ignored\n", i); continue; } seq_printf(s, " %2d: <%s>", i, bmap->dmabufs[i]->exp_name); edgetpu_device_dram_dmabuf_info_show(bmap->dmabufs[i], s); seq_puts(s, " dma="); entry_show_dma_addrs(&bmap->entries[i], s); } } /* * Allocates and properly sets fields of an edgetpu_dmabuf_map, bulk version. * * Caller holds group->lock and checks @group is finalized. * * Returns the pointer on success, or NULL on failure. */ static struct edgetpu_dmabuf_map * alloc_dmabuf_bulk_map(struct edgetpu_device_group *group, edgetpu_map_flag_t flags) { struct edgetpu_dmabuf_map *bmap = kzalloc(sizeof(*bmap), GFP_KERNEL); struct edgetpu_mapping *map; const uint n = group->n_clients; if (!bmap) return NULL; bmap->entries = kcalloc(n, sizeof(*bmap->entries), GFP_KERNEL); if (!bmap->entries) goto err_free; bmap->dmabufs = kcalloc(n, sizeof(*bmap->dmabufs), GFP_KERNEL); if (!bmap->dmabufs) goto err_free; /* mirroredness is ignored in bulk mappings */ bmap->mmu_flags = map_to_mmu_flags(flags & ~EDGETPU_MAP_NONMIRRORED) | EDGETPU_MMU_DMABUF | EDGETPU_MMU_VDG; bmap->num_entries = n; map = &bmap->map; map->die_index = ALL_DIES; map->flags = flags; map->dir = flags & EDGETPU_MAP_DIR_MASK; map->release = dmabuf_bulk_map_callback_release; map->show = dmabuf_bulk_map_callback_show; map->priv = edgetpu_device_group_get(group); return bmap; err_free: kfree(bmap->dmabufs); kfree(bmap->entries); kfree(bmap); return NULL; } /* * Duplicates @sgt in region [0, @size) to @out. * Only duplicates the "page" parts in @sgt, DMA addresses and lengths are not * considered. */ static int dup_sgt_in_region(struct sg_table *sgt, u64 size, struct sg_table *out) { uint n = 0; u64 cur_offset = 0; struct scatterlist *sg, *new_sg; int i; int ret; /* calculate the number of sg covered */ for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) { size_t pg_len = sg->length + sg->offset; n++; if (size <= cur_offset + pg_len) break; cur_offset += pg_len; } ret = sg_alloc_table(out, n, GFP_KERNEL); if (ret) return ret; cur_offset = 0; new_sg = out->sgl; for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) { size_t pg_len = sg->length + sg->offset; struct page *page = sg_page(sg); unsigned int len = pg_len; u64 remain_size = size - cur_offset; if (remain_size < pg_len) len -= pg_len - remain_size; sg_set_page(new_sg, page, len, 0); new_sg = sg_next(new_sg); if (size <= cur_offset + pg_len) break; cur_offset += pg_len; } return 0; } /* * Copy the DMA addresses and lengths in region [0, @size) from * @sgt to @out. * * The DMA addresses will be condensed when possible. */ static void shrink_sgt_dma_in_region(struct sg_table *sgt, u64 size, struct sg_table *out) { u64 cur_offset = 0; struct scatterlist *sg, *prv_sg = NULL, *cur_sg; cur_sg = out->sgl; out->nents = 0; for (sg = sgt->sgl; sg; cur_offset += sg_dma_len(sg), sg = sg_next(sg)) { u64 remain_size = size - cur_offset; dma_addr_t dma; size_t len; dma = sg_dma_address(sg); len = sg_dma_len(sg); if (remain_size < sg_dma_len(sg)) len -= sg_dma_len(sg) - remain_size; if (prv_sg && sg_dma_address(prv_sg) + sg_dma_len(prv_sg) == dma) { /* merge to previous sg */ sg_dma_len(prv_sg) += len; } else { sg_dma_address(cur_sg) = dma; sg_dma_len(cur_sg) = len; prv_sg = cur_sg; cur_sg = sg_next(cur_sg); out->nents++; } if (remain_size <= sg_dma_len(sg)) break; } } static int entry_set_shrunk_sgt(struct dmabuf_map_entry *entry, u64 size) { int ret; ret = dup_sgt_in_region(entry->sgt, size, &entry->shrunk_sgt); if (ret) return ret; shrink_sgt_dma_in_region(entry->sgt, size, &entry->shrunk_sgt); return 0; } /* * Performs dma_buf_attach + dma_buf_map_attachment of @dmabuf to @etdev, and * sets @entry per the attaching result. * * Fields of @entry will be set on success. */ static int etdev_attach_dmabuf_to_entry(struct edgetpu_dev *etdev, struct dma_buf *dmabuf, struct dmabuf_map_entry *entry, u64 size, enum dma_data_direction dir) { struct dma_buf_attachment *attachment; struct sg_table *sgt; int ret; attachment = dma_buf_attach(dmabuf, etdev->dev); if (IS_ERR(attachment)) return PTR_ERR(attachment); sgt = dma_buf_map_attachment(attachment, dir); if (IS_ERR(sgt)) { ret = PTR_ERR(sgt); goto err_detach; } entry->attachment = attachment; entry->sgt = sgt; ret = entry_set_shrunk_sgt(entry, size); if (ret) goto err_unmap; return 0; err_unmap: dma_buf_unmap_attachment(attachment, sgt, dir); err_detach: dma_buf_detach(dmabuf, attachment); entry->sgt = NULL; entry->attachment = NULL; return ret; } int edgetpu_map_dmabuf(struct edgetpu_device_group *group, struct edgetpu_map_dmabuf_ioctl *arg) { int ret = -EINVAL; struct dma_buf *dmabuf; edgetpu_map_flag_t flags = arg->flags; u64 size; const enum dma_data_direction dir = map_flag_to_host_dma_dir(flags); struct edgetpu_dev *etdev; struct edgetpu_dmabuf_map *dmap; tpu_addr_t tpu_addr; uint i; if (!valid_dma_direction(dir)) { etdev_dbg(group->etdev, "%s: invalid direction %d\n", __func__, dir); return -EINVAL; } dmabuf = dma_buf_get(arg->dmabuf_fd); if (IS_ERR(dmabuf)) { etdev_dbg(group->etdev, "%s: dma_buf_get returns %ld\n", __func__, PTR_ERR(dmabuf)); return PTR_ERR(dmabuf); } mutex_lock(&group->lock); if (!edgetpu_device_group_is_finalized(group)) { ret = edgetpu_group_errno(group); etdev_dbg(group->etdev, "%s: edgetpu_device_group_is_finalized returns %d\n", __func__, ret); goto err_unlock_group; } dmap = alloc_dmabuf_map(group, flags); if (!dmap) { ret = -ENOMEM; goto err_unlock_group; } get_dma_buf(dmabuf); dmap->dmabufs[0] = dmabuf; dmap->map.map_size = dmap->size = size = dmabuf->size; if (IS_MIRRORED(flags)) { for (i = 0; i < group->n_clients; i++) { etdev = edgetpu_device_group_nth_etdev(group, i); ret = etdev_attach_dmabuf_to_entry(etdev, dmabuf, &dmap->entries[i], size, dir); if (ret) { etdev_dbg(group->etdev, "%s: etdev_attach_dmabuf_to_entry returns %d\n", __func__, ret); goto err_release_map; } } ret = group_map_dmabuf(group, dmap, &tpu_addr); if (ret) { etdev_dbg(group->etdev, "%s: group_map_dmabuf returns %d\n", __func__, ret); goto err_release_map; } dmap->map.die_index = ALL_DIES; } else { etdev = edgetpu_device_group_nth_etdev(group, arg->die_index); if (!etdev) { etdev_dbg(group->etdev, "%s: edgetpu_device_group_nth_etdev returns NULL\n", __func__); ret = -EINVAL; goto err_release_map; } ret = etdev_attach_dmabuf_to_entry(etdev, dmabuf, &dmap->entries[0], size, dir); if (ret) { etdev_dbg(group->etdev, "%s: etdev_attach_dmabuf_to_entry returns %d\n", __func__, ret); goto err_release_map; } ret = etdev_map_dmabuf(etdev, dmap, &tpu_addr); if (ret) { etdev_dbg(group->etdev, "%s: etdev_map_dmabuf returns %d\n", __func__, ret); goto err_release_map; } dmap->map.die_index = arg->die_index; } dmap->map.device_address = tpu_addr; ret = edgetpu_mapping_add(&group->dmabuf_mappings, &dmap->map); if (ret) { etdev_dbg(group->etdev, "%s: edgetpu_mapping_add returns %d\n", __func__, ret); goto err_release_map; } arg->device_address = tpu_addr; mutex_unlock(&group->lock); dma_buf_put(dmabuf); return 0; err_release_map: /* also releases entries if they are set */ dmabuf_map_callback_release(&dmap->map); err_unlock_group: mutex_unlock(&group->lock); dma_buf_put(dmabuf); return ret; } int edgetpu_unmap_dmabuf(struct edgetpu_device_group *group, u32 die_index, tpu_addr_t tpu_addr) { struct edgetpu_mapping_root *mappings = &group->dmabuf_mappings; struct edgetpu_mapping *map; int ret = -EINVAL; mutex_lock(&group->lock); /* allows unmapping on errored groups */ if (!edgetpu_device_group_is_finalized(group) && !edgetpu_device_group_is_errored(group)) { ret = -EINVAL; goto out_unlock; } edgetpu_mapping_lock(mappings); map = edgetpu_mapping_find_locked(mappings, die_index, tpu_addr); if (!map) map = edgetpu_mapping_find_locked(mappings, ALL_DIES, tpu_addr); /* the mapping is not found */ if (!map) { edgetpu_mapping_unlock(mappings); goto out_unlock; } edgetpu_mapping_unlink(mappings, map); edgetpu_mapping_unlock(mappings); /* use the callback to handle both normal and bulk requests */ map->release(map); ret = 0; out_unlock: mutex_unlock(&group->lock); return ret; } int edgetpu_map_bulk_dmabuf(struct edgetpu_device_group *group, struct edgetpu_map_bulk_dmabuf_ioctl *arg) { const enum dma_data_direction dir = map_flag_to_host_dma_dir(arg->flags); int ret = -EINVAL; struct edgetpu_dmabuf_map *bmap; struct dma_buf *dmabuf; struct edgetpu_dev *etdev; tpu_addr_t tpu_addr; int i; if (!valid_dma_direction(dir) || arg->size == 0) return -EINVAL; mutex_lock(&group->lock); if (!edgetpu_device_group_is_finalized(group)) { ret = edgetpu_group_errno(group); goto err_unlock_group; } /* checks not all FDs are ignored */ for (i = 0; i < group->n_clients; i++) if (arg->dmabuf_fds[i] != EDGETPU_IGNORE_FD) break; if (i == group->n_clients) goto err_unlock_group; bmap = alloc_dmabuf_bulk_map(group, arg->flags); if (!bmap) { ret = -ENOMEM; goto err_unlock_group; } for (i = 0; i < group->n_clients; i++) { if (arg->dmabuf_fds[i] != EDGETPU_IGNORE_FD) { dmabuf = dma_buf_get(arg->dmabuf_fds[i]); if (IS_ERR(dmabuf)) { ret = PTR_ERR(dmabuf); goto err_release_bmap; } if (arg->size > dmabuf->size) goto err_release_bmap; bmap->dmabufs[i] = dmabuf; } } bmap->size = PAGE_ALIGN(arg->size); for (i = 0; i < group->n_clients; i++) { if (!bmap->dmabufs[i]) continue; etdev = edgetpu_device_group_nth_etdev(group, i); ret = etdev_attach_dmabuf_to_entry(etdev, bmap->dmabufs[i], &bmap->entries[i], bmap->size, dir); if (ret) goto err_release_bmap; } ret = group_map_dmabuf(group, bmap, &tpu_addr); if (ret) goto err_release_bmap; bmap->map.device_address = tpu_addr; ret = edgetpu_mapping_add(&group->dmabuf_mappings, &bmap->map); if (ret) goto err_release_bmap; arg->device_address = tpu_addr; mutex_unlock(&group->lock); return 0; err_release_bmap: /* also releases entries if they are set */ dmabuf_bulk_map_callback_release(&bmap->map); err_unlock_group: mutex_unlock(&group->lock); return ret; } /* reverts edgetpu_map_bulk_dmabuf */ int edgetpu_unmap_bulk_dmabuf(struct edgetpu_device_group *group, tpu_addr_t tpu_addr) { return edgetpu_unmap_dmabuf(group, ALL_DIES, tpu_addr); } static struct edgetpu_dma_fence *to_etfence(struct dma_fence *fence) { struct edgetpu_dma_fence *etfence; etfence = container_of(fence, struct edgetpu_dma_fence, fence); if (fence->ops != &edgetpu_dma_fence_ops) return NULL; return etfence; } static const char *edgetpu_dma_fence_get_driver_name(struct dma_fence *fence) { return "edgetpu"; } static const char *edgetpu_dma_fence_get_timeline_name(struct dma_fence *fence) { struct edgetpu_dma_fence *etfence = to_etfence(fence); return etfence->timeline_name; } static void edgetpu_dma_fence_release(struct dma_fence *fence) { struct edgetpu_dma_fence *etfence = to_etfence(fence); struct edgetpu_device_group *group; unsigned long flags; if (!etfence) return; spin_lock_irqsave(&etfence_list_lock, flags); list_del(&etfence->etfence_list); spin_unlock_irqrestore(&etfence_list_lock, flags); /* group might not yet be set if error at init time. */ group = etfence->group; if (group) { mutex_lock(&group->lock); list_del(&etfence->group_list); mutex_unlock(&group->lock); /* Release this fence's reference on the owning group. */ edgetpu_device_group_put(group); } kfree(etfence); } static bool edgetpu_dma_fence_enable_signaling(struct dma_fence *fence) { return true; } static const struct dma_fence_ops edgetpu_dma_fence_ops = { .get_driver_name = edgetpu_dma_fence_get_driver_name, .get_timeline_name = edgetpu_dma_fence_get_timeline_name, .wait = dma_fence_default_wait, .enable_signaling = edgetpu_dma_fence_enable_signaling, .release = edgetpu_dma_fence_release, }; /* the data type of fence->seqno is u64 in 5.1 */ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0) #define SEQ_FMT "%u" #else #define SEQ_FMT "%llu" #endif int edgetpu_sync_fence_create(struct edgetpu_device_group *group, struct edgetpu_create_sync_fence_data *datap) { int fd = get_unused_fd_flags(O_CLOEXEC); int ret; struct edgetpu_dma_fence *etfence; struct sync_file *sync_file; unsigned long flags; if (fd < 0) return fd; etfence = kzalloc(sizeof(*etfence), GFP_KERNEL); if (!etfence) { ret = -ENOMEM; goto err_put_fd; } spin_lock_init(&etfence->lock); /* * If sync_file_create() fails, fence release is called on dma_fence_put(). A valid * list_head is needed for list_del(). */ INIT_LIST_HEAD(&etfence->etfence_list); INIT_LIST_HEAD(&etfence->group_list); memcpy(&etfence->timeline_name, &datap->timeline_name, EDGETPU_SYNC_TIMELINE_NAME_LEN - 1); dma_fence_init(&etfence->fence, &edgetpu_dma_fence_ops, &etfence->lock, dma_fence_context_alloc(1), datap->seqno); sync_file = sync_file_create(&etfence->fence); dma_fence_put(&etfence->fence); if (!sync_file) { ret = -ENOMEM; /* doesn't need kfree(etfence) here: dma_fence_put does it for us */ goto err_put_fd; } spin_lock_irqsave(&etfence_list_lock, flags); list_add_tail(&etfence->etfence_list, &etfence_list_head); spin_unlock_irqrestore(&etfence_list_lock, flags); etfence->group = edgetpu_device_group_get(group); mutex_lock(&group->lock); list_add_tail(&etfence->group_list, &group->dma_fence_list); mutex_unlock(&group->lock); fd_install(fd, sync_file->file); datap->fence = fd; return 0; err_put_fd: put_unused_fd(fd); return ret; } static int _edgetpu_sync_fence_signal(struct dma_fence *fence, int errno, bool ignore_signaled) { int ret; spin_lock_irq(fence->lock); /* don't signal fence twice */ if (unlikely(test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))) { ret = ignore_signaled ? 0 : -EINVAL; goto out_unlock; } pr_debug("%s: %s-%s%llu-" SEQ_FMT " errno=%d\n", __func__, fence->ops->get_driver_name(fence), fence->ops->get_timeline_name(fence), fence->context, fence->seqno, errno); if (errno) dma_fence_set_error(fence, errno); ret = dma_fence_signal_locked(fence); out_unlock: spin_unlock_irq(fence->lock); return ret; } int edgetpu_sync_fence_signal(struct edgetpu_signal_sync_fence_data *datap) { struct dma_fence *fence; int errno; int ret; errno = datap->error; if (errno > 0) errno = -errno; if (errno < -MAX_ERRNO) return -EINVAL; fence = sync_file_get_fence(datap->fence); if (!fence) return -EINVAL; ret = _edgetpu_sync_fence_signal(fence, errno, false); dma_fence_put(fence); return ret; } /* Caller holds group lock. */ void edgetpu_sync_fence_group_shutdown(struct edgetpu_device_group *group) { struct list_head *pos; int ret; lockdep_assert_held(&group->lock); list_for_each(pos, &group->dma_fence_list) { struct edgetpu_dma_fence *etfence = container_of(pos, struct edgetpu_dma_fence, group_list); struct dma_fence *fence = &etfence->fence; ret = _edgetpu_sync_fence_signal(fence, -EPIPE, true); if (ret) etdev_warn(group->etdev, "error %d signaling fence %s-%s %llu-" SEQ_FMT, ret, fence->ops->get_driver_name(fence), fence->ops->get_timeline_name(fence), fence->context, fence->seqno); } } int edgetpu_sync_fence_status(struct edgetpu_sync_fence_status *datap) { struct dma_fence *fence; fence = sync_file_get_fence(datap->fence); if (!fence) return -EINVAL; datap->status = dma_fence_get_status(fence); dma_fence_put(fence); return 0; } static const char *sync_status_str(int status) { if (status < 0) return "error"; if (status > 0) return "signaled"; return "active"; } int edgetpu_sync_fence_debugfs_show(struct seq_file *s, void *unused) { struct list_head *pos; spin_lock_irq(&etfence_list_lock); list_for_each(pos, &etfence_list_head) { struct edgetpu_dma_fence *etfence = container_of(pos, struct edgetpu_dma_fence, etfence_list); struct dma_fence *fence = &etfence->fence; spin_lock_irq(&etfence->lock); seq_printf(s, "%s-%s %llu-" SEQ_FMT " %s", fence->ops->get_driver_name(fence), fence->ops->get_timeline_name(fence), fence->context, fence->seqno, sync_status_str(dma_fence_get_status_locked(fence))); if (test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags)) { struct timespec64 ts64 = ktime_to_timespec64(fence->timestamp); seq_printf(s, " @%lld.%09ld", (s64)ts64.tv_sec, ts64.tv_nsec); } if (fence->error) seq_printf(s, " err=%d", fence->error); seq_printf(s, " group=%u\n", etfence->group->workload_id); spin_unlock_irq(&etfence->lock); } spin_unlock_irq(&etfence_list_lock); return 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) MODULE_IMPORT_NS(DMA_BUF); #endif