1
0

split_virtqueue.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. package virtqueue
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "github.com/slackhq/nebula/overlay/eventfd"
  8. "golang.org/x/sys/unix"
  9. )
  10. // SplitQueue is a virtqueue that consists of several parts, where each part is
  11. // writeable by either the driver or the device, but not both.
  12. type SplitQueue struct {
  13. // size is the size of the queue.
  14. size int
  15. // buf is the underlying memory used for the queue.
  16. buf []byte
  17. descriptorTable *DescriptorTable
  18. availableRing *AvailableRing
  19. usedRing *UsedRing
  20. // kickEventFD is used to signal the device when descriptor chains were
  21. // added to the available ring.
  22. kickEventFD eventfd.EventFD
  23. // callEventFD is used by the device to signal when it has used descriptor
  24. // chains and put them in the used ring.
  25. callEventFD eventfd.EventFD
  26. // stop is used by [SplitQueue.Close] to cancel the goroutine that handles
  27. // used buffer notifications. It blocks until the goroutine ended.
  28. stop func() error
  29. itemSize int
  30. epoll eventfd.Epoll
  31. more int
  32. }
  33. // NewSplitQueue allocates a new [SplitQueue] in memory. The given queue size
  34. // specifies the number of entries/buffers the queue can hold. This also affects
  35. // the memory consumption.
  36. func NewSplitQueue(queueSize int, itemSize int) (_ *SplitQueue, err error) {
  37. if err = CheckQueueSize(queueSize); err != nil {
  38. return nil, err
  39. }
  40. if itemSize%os.Getpagesize() != 0 {
  41. return nil, errors.New("split queue size must be multiple of os.Getpagesize()")
  42. }
  43. sq := SplitQueue{
  44. size: queueSize,
  45. itemSize: itemSize,
  46. }
  47. // Clean up a partially initialized queue when something fails.
  48. defer func() {
  49. if err != nil {
  50. _ = sq.Close()
  51. }
  52. }()
  53. // There are multiple ways for how the memory for the virtqueue could be
  54. // allocated. We could use Go native structs with arrays inside them, but
  55. // this wouldn't allow us to make the queue size configurable. And including
  56. // a slice in the Go structs wouldn't work, because this would just put the
  57. // Go slice descriptor into the memory region which the virtio device will
  58. // not understand.
  59. // Additionally, Go does not allow us to ensure a correct alignment of the
  60. // parts of the virtqueue, as it is required by the virtio specification.
  61. //
  62. // To resolve this, let's just allocate the memory manually by allocating
  63. // one or more memory pages, depending on the queue size. Making the
  64. // virtqueue start at the beginning of a page is not strictly necessary, as
  65. // the virtio specification does not require it to be continuous in the
  66. // physical memory of the host (e.g. the vhost implementation in the kernel
  67. // always uses copy_from_user to access it), but this makes it very easy to
  68. // guarantee the alignment. Also, it is not required for the virtqueue parts
  69. // to be in the same memory region, as we pass separate pointers to them to
  70. // the device, but this design just makes things easier to implement.
  71. //
  72. // One added benefit of allocating the memory manually is, that we have full
  73. // control over its lifetime and don't risk the garbage collector to collect
  74. // our valuable structures while the device still works with them.
  75. // The descriptor table is at the start of the page, so alignment is not an
  76. // issue here.
  77. descriptorTableStart := 0
  78. descriptorTableEnd := descriptorTableStart + descriptorTableSize(queueSize)
  79. availableRingStart := align(descriptorTableEnd, availableRingAlignment)
  80. availableRingEnd := availableRingStart + availableRingSize(queueSize)
  81. usedRingStart := align(availableRingEnd, usedRingAlignment)
  82. usedRingEnd := usedRingStart + usedRingSize(queueSize)
  83. sq.buf, err = unix.Mmap(-1, 0, usedRingEnd,
  84. unix.PROT_READ|unix.PROT_WRITE,
  85. unix.MAP_PRIVATE|unix.MAP_ANONYMOUS)
  86. if err != nil {
  87. return nil, fmt.Errorf("allocate virtqueue buffer: %w", err)
  88. }
  89. sq.descriptorTable = newDescriptorTable(queueSize, sq.buf[descriptorTableStart:descriptorTableEnd], sq.itemSize)
  90. sq.availableRing = newAvailableRing(queueSize, sq.buf[availableRingStart:availableRingEnd])
  91. sq.usedRing = newUsedRing(queueSize, sq.buf[usedRingStart:usedRingEnd])
  92. sq.kickEventFD, err = eventfd.New()
  93. if err != nil {
  94. return nil, fmt.Errorf("create kick event file descriptor: %w", err)
  95. }
  96. sq.callEventFD, err = eventfd.New()
  97. if err != nil {
  98. return nil, fmt.Errorf("create call event file descriptor: %w", err)
  99. }
  100. if err = sq.descriptorTable.initializeDescriptors(); err != nil {
  101. return nil, fmt.Errorf("initialize descriptors: %w", err)
  102. }
  103. sq.epoll, err = eventfd.NewEpoll()
  104. if err != nil {
  105. return nil, err
  106. }
  107. err = sq.epoll.AddEvent(sq.callEventFD.FD())
  108. if err != nil {
  109. return nil, err
  110. }
  111. // Consume used buffer notifications in the background.
  112. sq.stop = sq.startConsumeUsedRing()
  113. return &sq, nil
  114. }
  115. // Size returns the size of this queue, which is the number of entries/buffers
  116. // this queue can hold.
  117. func (sq *SplitQueue) Size() int {
  118. return sq.size
  119. }
  120. // DescriptorTable returns the [DescriptorTable] behind this queue.
  121. func (sq *SplitQueue) DescriptorTable() *DescriptorTable {
  122. return sq.descriptorTable
  123. }
  124. // AvailableRing returns the [AvailableRing] behind this queue.
  125. func (sq *SplitQueue) AvailableRing() *AvailableRing {
  126. return sq.availableRing
  127. }
  128. // UsedRing returns the [UsedRing] behind this queue.
  129. func (sq *SplitQueue) UsedRing() *UsedRing {
  130. return sq.usedRing
  131. }
  132. // KickEventFD returns the kick event file descriptor behind this queue.
  133. // The returned file descriptor should be used with great care to not interfere
  134. // with this implementation.
  135. func (sq *SplitQueue) KickEventFD() int {
  136. return sq.kickEventFD.FD()
  137. }
  138. // CallEventFD returns the call event file descriptor behind this queue.
  139. // The returned file descriptor should be used with great care to not interfere
  140. // with this implementation.
  141. func (sq *SplitQueue) CallEventFD() int {
  142. return sq.callEventFD.FD()
  143. }
  144. // startConsumeUsedRing starts a goroutine that runs [consumeUsedRing].
  145. // A function is returned that can be used to gracefully cancel it. todo rename
  146. func (sq *SplitQueue) startConsumeUsedRing() func() error {
  147. return func() error {
  148. // The goroutine blocks until it receives a signal on the event file
  149. // descriptor, so it will never notice the context being canceled.
  150. // To resolve this, we can just produce a fake-signal ourselves to wake
  151. // it up.
  152. if err := sq.callEventFD.Kick(); err != nil {
  153. return fmt.Errorf("wake up goroutine: %w", err)
  154. }
  155. return nil
  156. }
  157. }
  158. func (sq *SplitQueue) TakeSingle(ctx context.Context) (uint16, error) {
  159. var n int
  160. var err error
  161. for ctx.Err() == nil {
  162. out, ok := sq.usedRing.takeOne()
  163. if ok {
  164. return out, nil
  165. }
  166. // Wait for a signal from the device.
  167. if n, err = sq.epoll.Block(); err != nil {
  168. return 0, fmt.Errorf("wait: %w", err)
  169. }
  170. if n > 0 {
  171. out, ok = sq.usedRing.takeOne()
  172. if ok {
  173. _ = sq.epoll.Clear() //???
  174. return out, nil
  175. } else {
  176. continue //???
  177. }
  178. }
  179. }
  180. return 0, ctx.Err()
  181. }
  182. func (sq *SplitQueue) BlockAndGetHeadsCapped(ctx context.Context, maxToTake int) ([]UsedElement, error) {
  183. var n int
  184. var err error
  185. for ctx.Err() == nil {
  186. //we have leftovers in the fridge
  187. if sq.more > 0 {
  188. stillNeedToTake, out := sq.usedRing.take(maxToTake)
  189. sq.more = stillNeedToTake
  190. return out, nil
  191. }
  192. //look inside the fridge
  193. stillNeedToTake, out := sq.usedRing.take(maxToTake)
  194. if len(out) > 0 {
  195. sq.more = stillNeedToTake
  196. return out, nil
  197. }
  198. //fridge is empty I guess
  199. // Wait for a signal from the device.
  200. if n, err = sq.epoll.Block(); err != nil {
  201. return nil, fmt.Errorf("wait: %w", err)
  202. }
  203. if n > 0 {
  204. _ = sq.epoll.Clear() //???
  205. stillNeedToTake, out = sq.usedRing.take(maxToTake)
  206. sq.more = stillNeedToTake
  207. return out, nil
  208. }
  209. }
  210. return nil, ctx.Err()
  211. }
  212. // OfferDescriptorChain offers a descriptor chain to the device which contains a
  213. // number of device-readable buffers (out buffers) and device-writable buffers
  214. // (in buffers).
  215. //
  216. // All buffers in the outBuffers slice will be concatenated by chaining
  217. // descriptors, one for each buffer in the slice. When a buffer is too large to
  218. // fit into a single descriptor (limited by the system's page size), it will be
  219. // split up into multiple descriptors within the chain.
  220. // When numInBuffers is greater than zero, the given number of device-writable
  221. // descriptors will be appended to the end of the chain, each referencing a
  222. // whole memory page (see [os.Getpagesize]).
  223. //
  224. // When the queue is full and no more descriptor chains can be added, a wrapped
  225. // [ErrNotEnoughFreeDescriptors] will be returned. If you set waitFree to true,
  226. // this method will handle this error and will block instead until there are
  227. // enough free descriptors again.
  228. //
  229. // After defining the descriptor chain in the [DescriptorTable], the index of
  230. // the head of the chain will be made available to the device using the
  231. // [AvailableRing] and will be returned by this method.
  232. // Callers should read from the [SplitQueue.UsedDescriptorChains] channel to be
  233. // notified when the descriptor chain was used by the device and should free the
  234. // used descriptor chains again using [SplitQueue.FreeDescriptorChain] when
  235. // they're done with them. When this does not happen, the queue will run full
  236. // and any further calls to [SplitQueue.OfferDescriptorChain] will stall.
  237. func (sq *SplitQueue) OfferInDescriptorChains() (uint16, error) {
  238. // Create a descriptor chain for the given buffers.
  239. var (
  240. head uint16
  241. err error
  242. )
  243. for {
  244. head, err = sq.descriptorTable.createDescriptorForInputs()
  245. if err == nil {
  246. break
  247. }
  248. // I don't wanna use errors.Is, it's slow
  249. //goland:noinspection GoDirectComparisonOfErrors
  250. if err == ErrNotEnoughFreeDescriptors {
  251. return 0, err
  252. } else {
  253. return 0, fmt.Errorf("create descriptor chain: %w", err)
  254. }
  255. }
  256. // Make the descriptor chain available to the device.
  257. sq.availableRing.offerSingle(head)
  258. // Notify the device to make it process the updated available ring.
  259. if err := sq.kickEventFD.Kick(); err != nil {
  260. return head, fmt.Errorf("notify device: %w", err)
  261. }
  262. return head, nil
  263. }
  264. // GetDescriptorChain returns the device-readable buffers (out buffers) and
  265. // device-writable buffers (in buffers) of the descriptor chain with the given
  266. // head index.
  267. // The head index must be one that was returned by a previous call to
  268. // [SplitQueue.OfferDescriptorChain] and the descriptor chain must not have been
  269. // freed yet.
  270. //
  271. // Be careful to only access the returned buffer slices when the device is no
  272. // longer using them. They must not be accessed after
  273. // [SplitQueue.FreeDescriptorChain] has been called.
  274. func (sq *SplitQueue) GetDescriptorChain(head uint16) (outBuffers, inBuffers [][]byte, err error) {
  275. return sq.descriptorTable.getDescriptorChain(head)
  276. }
  277. func (sq *SplitQueue) GetDescriptorItem(head uint16) ([]byte, error) {
  278. sq.descriptorTable.descriptors[head].length = uint32(sq.descriptorTable.itemSize)
  279. return sq.descriptorTable.getDescriptorItem(head)
  280. }
  281. func (sq *SplitQueue) GetDescriptorInbuffers(head uint16, inBuffers *[][]byte) error {
  282. return sq.descriptorTable.getDescriptorInbuffers(head, inBuffers)
  283. }
  284. // FreeDescriptorChain frees the descriptor chain with the given head index.
  285. // The head index must be one that was returned by a previous call to
  286. // [SplitQueue.OfferDescriptorChain] and the descriptor chain must not have been
  287. // freed yet.
  288. //
  289. // This creates new room in the queue which can be used by following
  290. // [SplitQueue.OfferDescriptorChain] calls.
  291. // When there are outstanding calls for [SplitQueue.OfferDescriptorChain] that
  292. // are waiting for free room in the queue, they may become unblocked by this.
  293. func (sq *SplitQueue) FreeDescriptorChain(head uint16) error {
  294. //not called under lock
  295. if err := sq.descriptorTable.freeDescriptorChain(head); err != nil {
  296. return fmt.Errorf("free: %w", err)
  297. }
  298. return nil
  299. }
  300. func (sq *SplitQueue) SetDescSize(head uint16, sz int) {
  301. //not called under lock
  302. sq.descriptorTable.descriptors[int(head)].length = uint32(sz)
  303. }
  304. func (sq *SplitQueue) OfferDescriptorChains(chains []uint16, kick bool) error {
  305. //todo not doing this may break eventually?
  306. //not called under lock
  307. //if err := sq.descriptorTable.freeDescriptorChain(head); err != nil {
  308. // return fmt.Errorf("free: %w", err)
  309. //}
  310. // Make the descriptor chain available to the device.
  311. sq.availableRing.offer(chains)
  312. // Notify the device to make it process the updated available ring.
  313. if kick {
  314. return sq.Kick()
  315. }
  316. return nil
  317. }
  318. func (sq *SplitQueue) Kick() error {
  319. if err := sq.kickEventFD.Kick(); err != nil {
  320. return fmt.Errorf("notify device: %w", err)
  321. }
  322. return nil
  323. }
  324. // Close releases all resources used for this queue.
  325. // The implementation will try to release as many resources as possible and
  326. // collect potential errors before returning them.
  327. func (sq *SplitQueue) Close() error {
  328. var errs []error
  329. if sq.stop != nil {
  330. // This has to happen before the event file descriptors may be closed.
  331. if err := sq.stop(); err != nil {
  332. errs = append(errs, fmt.Errorf("stop consume used ring: %w", err))
  333. }
  334. // Make sure that this code block is executed only once.
  335. sq.stop = nil
  336. }
  337. if err := sq.kickEventFD.Close(); err != nil {
  338. errs = append(errs, fmt.Errorf("close kick event file descriptor: %w", err))
  339. }
  340. if err := sq.callEventFD.Close(); err != nil {
  341. errs = append(errs, fmt.Errorf("close call event file descriptor: %w", err))
  342. }
  343. if err := sq.descriptorTable.releaseBuffers(); err != nil {
  344. errs = append(errs, fmt.Errorf("release descriptor buffers: %w", err))
  345. }
  346. if sq.buf != nil {
  347. if err := unix.Munmap(sq.buf); err == nil {
  348. sq.buf = nil
  349. } else {
  350. errs = append(errs, fmt.Errorf("unmap virtqueue buffer: %w", err))
  351. }
  352. }
  353. return errors.Join(errs...)
  354. }
  355. func align(index, alignment int) int {
  356. remainder := index % alignment
  357. if remainder == 0 {
  358. return index
  359. }
  360. return index + alignment - remainder
  361. }