SPLIT AND MERGE(9)

πŸ“¦ version 7.3.1, in nixpkgs unstable

πŸ’– Split and Merge πŸ’–

okay bestie, this is Split and Merge, section 9 of the Kernel API Manual, and it's giving β€œGPU VA manager does mapping algebra” in the most iconic way. ✨

Besides its capability to manage and represent a GPU VA space, the GPU VA manager also provides functions to let drm_gpuvm calculate a sequence of operations to satisfy a given map or unmap request. No cap.

So the DRM GPU VA manager provides an algorithm implementing splitting and merging of existing GPU VA mappings with the ones that are requested to be mapped or unmapped. This feature is required by the Vulkan API to implement Vulkan 'Sparse Memory Bindings' β€” drivers' UAPIs often refer to this as VM BIND. 🫢

Drivers can call drm_gpuvm_sm_map to receive a sequence of callbacks containing map, unmap and remap operations for a given newly requested mapping. That sequence of callbacks represents the set of operations to execute in order to integrate the new mapping cleanly into the current state of the GPU VA space. πŸ’…

Depending on how the new GPU VA mapping intersects with the existing mappings of the GPU VA space, the drm_gpuvm_ops callbacks contain an arbitrary amount of unmap operations, a maximum of two remap operations and a single map operation. The caller might receive no callback at all if no operation is required, e.g. if the requested mapping already exists in the exact same way. iconic no-op behavior, fr fr.

The single map operation represents the original map operation requested by the caller.

drm_gpuva_op_unmap contains a 'keep' field, which indicates whether the drm_gpuva to unmap is physically contiguous with the original mapping request. Optionally, if 'keep' is set, drivers may keep the actual page table entries for this drm_gpuva, adding the missing page table entries only and update the drm_gpuvm's view of things accordingly. πŸŽ€

Drivers may do the same optimization, namely delta page table updates, also for remap operations. This is possible since drm_gpuva_op_remap consists of one unmap operation and one or two map operations, such that drivers can derive the page table update delta accordingly.

Note that there can't be more than two existing mappings to split up β€” one at the beginning and one at the end of the new mapping β€” hence there is a maximum of two remap operations. πŸ“Œ

Analogous to drm_gpuvm_sm_map, drm_gpuvm_sm_unmap uses drm_gpuvm_ops to call back into the driver in order to unmap a range of GPU VA space. The logic behind this function is way simpler though: for all existing mappings enclosed by the given range, unmap operations are created. For mappings which are only partially located within the given range, remap operations are created such that those mappings are split up and re-mapped partially. πŸ¦‹

As an alternative to drm_gpuvm_sm_map and drm_gpuvm_sm_unmap, drm_gpuvm_sm_map_ops_create and drm_gpuvm_sm_unmap_ops_create can be used to directly obtain an instance of struct drm_gpuva_ops containing a list of drm_gpuva_op, which can be iterated with drm_gpuva_for_each_op. This list contains the drm_gpuva_ops analogous to the callbacks one would receive when calling drm_gpuvm_sm_map or drm_gpuvm_sm_unmap. While this way requires more memory (to allocate the drm_gpuva_ops), it provides drivers a way to iterate the drm_gpuva_op multiple times, e.g. once in a context where memory allocations are possible (e.g. to allocate GPU page tables) and once in the dma-fence signalling critical path. ✨

To update the drm_gpuvm's view of the GPU VA space, drm_gpuva_insert and drm_gpuva_remove may be used. These functions can safely be used from drm_gpuvm_ops callbacks originating from drm_gpuvm_sm_map or drm_gpuvm_sm_unmap. However, it might be more convenient to use the provided helper functions drm_gpuva_map, drm_gpuva_remap and drm_gpuva_unmap instead. πŸ’–

The following diagram depicts the basic relationships of existing GPU VA mappings, a newly requested mapping and the resulting mappings as implemented by drm_gpuvm_sm_map β€” it doesn't cover any arbitrary combinations of these. It's a little art gallery of mapping glow-ups, ngl. πŸ–ΌοΈβœ¨

1. Requested mapping is identical β€” replace it, but indicate the backing PTEs could be kept πŸŽ€


          :

                 0     a     1
            old: |-----------| (bo_offset=n)

                 0     a     1
            req: |-----------| (bo_offset=n)

                 0     a     1
            new: |-----------| (bo_offset=n)

2. Requested mapping is identical, except for the BO offset β€” hence replace the mapping πŸ’«


          :

                 0     a     1
            old: |-----------| (bo_offset=n)

                 0     a     1
            req: |-----------| (bo_offset=m)

                 0     a     1
            new: |-----------| (bo_offset=m)

3. Requested mapping is identical, except for the backing BO β€” hence replace the mapping πŸ¦‹


          :

                 0     a     1
            old: |-----------| (bo_offset=n)

                 0     b     1
            req: |-----------| (bo_offset=n)

                 0     b     1
            new: |-----------| (bo_offset=n)

4. Existent mapping is a left aligned subset of the requested one β€” hence replace the existing one 🌸


          :

                 0  a  1
            old: |-----|       (bo_offset=n)

                 0     a     2
            req: |-----------| (bo_offset=n)

                 0     a     2
            new: |-----------| (bo_offset=n)
note: We expect to see the same result for a request with a different BO and/or non-contiguous BO offset. 😌

5. Requested mapping's range is a left aligned subset of the existing one, but backed by a different BO β€” hence map the requested mapping and split the existing one, adjusting its BO offset πŸ’…


          :

                 0     a     2
            old: |-----------| (bo_offset=n)

                 0  b  1
            req: |-----|       (bo_offset=n)

                 0  b  1  a' 2
            new: |-----|-----| (b.bo_offset=n, a.bo_offset=n+1)
note: We expect to see the same result for a request with a different BO and/or non-contiguous BO offset. 😌

6. Existent mapping is a superset of the requested mapping β€” split it up, but indicate that the backing PTEs could be kept ✨


          :

                 0     a     2
            old: |-----------| (bo_offset=n)

                 0  a  1
            req: |-----|       (bo_offset=n)

                 0  a  1  a' 2
            new: |-----|-----| (a.bo_offset=n, a'.bo_offset=n+1)

7. Requested mapping's range is a right aligned subset of the existing one, but backed by a different BO β€” hence map the requested mapping and split the existing one, without adjusting the BO offset πŸ’–


          :

                 0     a     2
            old: |-----------| (bo_offset=n)

                       1  b  2
            req:       |-----| (bo_offset=m)

                 0  a  1  b  2
            new: |-----|-----| (a.bo_offset=n,b.bo_offset=m)

8. Existent mapping is a superset of the requested mapping β€” split it up, but indicate that the backing PTEs could be kept 🫢


          :

                  0     a     2
            old: |-----------| (bo_offset=n)

                       1  a  2
            req:       |-----| (bo_offset=n+1)

                 0  a' 1  a  2
            new: |-----|-----| (a'.bo_offset=n, a.bo_offset=n+1)

9. Existent mapping is overlapped at the end by the requested mapping backed by a different BO β€” hence map the requested mapping and split up the existing one, without adjusting the BO offset πŸ”₯


          :

                 0     a     2
            old: |-----------|       (bo_offset=n)

                       1     b     3
            req:       |-----------| (bo_offset=m)

                 0  a  1     b     3
            new: |-----|-----------| (a.bo_offset=n,b.bo_offset=m)

10. Existent mapping is overlapped by the requested mapping, both having the same backing BO with a contiguous offset β€” indicate the backing PTEs of the old mapping could be kept πŸ‘‘


           :

                  0     a     2
             old: |-----------|       (bo_offset=n)

                        1     a     3
             req:       |-----------| (bo_offset=n+1)

                  0  a' 1     a     3
             new: |-----|-----------| (a'.bo_offset=n, a.bo_offset=n+1)

11. Requested mapping's range is a centered subset of the existing one having a different backing BO β€” hence map the requested mapping and split up the existing one in two mappings, adjusting the BO offset of the right one accordingly 🧚


           :

                  0        a        3
             old: |-----------------| (bo_offset=n)

                        1  b  2
             req:       |-----|       (bo_offset=m)

                  0  a  1  b  2  a' 3
             new: |-----|-----|-----| (a.bo_offset=n,b.bo_offset=m,a'.bo_offset=n+2)

12. Requested mapping is a contiguous subset of the existing one β€” split it up, but indicate that the backing PTEs could be kept 🌈


           :

                  0        a        3
             old: |-----------------| (bo_offset=n)

                        1  a  2
             req:       |-----|       (bo_offset=n+1)

                  0  a' 1  a  2 a'' 3
             old: |-----|-----|-----| (a'.bo_offset=n, a.bo_offset=n+1, a''.bo_offset=n+2)

13. Existent mapping is a right aligned subset of the requested one β€” hence replace the existing one ⭐


           :

                        1  a  2
             old:       |-----| (bo_offset=n+1)

                  0     a     2
             req: |-----------| (bo_offset=n)

                  0     a     2
             new: |-----------| (bo_offset=n)
note: We expect to see the same result for a request with a different bo and/or non-contiguous bo_offset. πŸ’…

14. Existent mapping is a centered subset of the requested one β€” hence replace the existing one πŸ’œ


           :

                        1  a  2
             old:       |-----| (bo_offset=n+1)

                  0        a       3
             req: |----------------| (bo_offset=n)

                  0        a       3
             new: |----------------| (bo_offset=n)
note: We expect to see the same result for a request with a different bo and/or non-contiguous bo_offset. πŸ’…

15. Existent mappings is overlapped at the beginning by the requested mapping backed by a different BO β€” hence map the requested mapping and split up the existing one, adjusting its BO offset accordingly πŸ“


           :

                        1     a     3
             old:       |-----------| (bo_offset=n)

                  0     b     2
             req: |-----------|       (bo_offset=m)

                  0     b     2  a' 3
             new: |-----------|-----| (b.bo_offset=m,a.bo_offset=n+2)

✨ SEE ALSO ✨

okay so like, this is the part where I hand you the whole friend group 😌 every single one of these is somebody you're gonna want in your contacts, no cap. Start with the actual source file and the big-picture pages, then we get into the individual besties.

  • ./drivers/gpu/drm/drm_gpuvm.c β€” the kernel file, the mothership, the main character herself πŸ’…
  • Overview(9) β€” the vibe-setting origin story, read this first fr
  • Madvise Logic - Splitting and Traversal(9) β€” literally the whole reason this page exists
  • Locking(9) β€” the rules about who gets to touch what and when, please respect them πŸ™
  • Examples(9) β€” actual working code instead of me just telling you things

🧚 the vm_bo list helpers (the little goblins)

🧟 bo lifecycle & zombie behaviour

🩷 range + reservation setup

πŸ” prepare & lock era

βœ… validate & fences

πŸ¦‹ the bo grab-bag (create / put / find)

🌸 raw gpuva operations (no thoughts, just insert and remove)

πŸ”Ž finding things + empty intervals

πŸŽ€ map / remap / unmap trio

πŸ‘‘ split & merge, the actual headline acts

🫢 ops create / free squad


bestie that's 49 functions plus the kernel file and four overview pages. if you only remember one thing: read Overview(9) before you go poking at anything in here, okay? iykyk πŸ’–

and that's a wrap on part 2!! πŸŽ€ this whole thing lives under the source path ./drivers/gpu/drm, dated October 2026, and it's the Split and Merge(9) manual page β€” go forth and merge responsibly, my love βœ¨πŸ¦‹πŸ‘‘

πŸ“¦ where this page comes from