package mirage-net-xen

  1. Overview
  2. Docs

Source file shared_page_pool.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
(*
 * Copyright (c) 2015 Thomas Leonard
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

open Lwt.Infix

module Gntref = Xen_os.Xen.Gntref
module Export = Xen_os.Xen.Export

let return = Lwt.return

let max_pages = 256

type block = {
  id : Cstruct.uint16;
  gref : Gntref.t;
  data : Cstruct.t;
}
type t = {
  grant : Gntref.t -> Io_page.t -> unit;
  mutable next_id : Cstruct.uint16;
  mutable blocks : block list;
  mutable in_use : int;
  mutable shutdown : bool;
  avail : unit Lwt_condition.t;  (* Fires when free list becomes non-empty *)
}

let page_size = Io_page.round_to_page_size 1
let block_size = page_size / 2

let make grant = { next_id = 0; grant; blocks = []; shutdown = false; in_use = 0; avail = Lwt_condition.create () }

let shutdown t =
  t.shutdown <- true;
  Lwt_condition.broadcast t.avail ();   (* Wake anyone who's still waiting for free pages *)
  if t.in_use = 0 then (
    t.blocks |> List.iter (fun {id = _; gref; data} ->
      if data.Cstruct.off = 0 then (
        Lwt.async (fun () -> Export.end_access ~release_ref:true gref)
      )
    );
    t.blocks <- []
  )
  (* Otherwise, shutdown gets called again when in_use becomes 0 *)

let alloc t =
  let page = Io_page.get 1 in
  (* (the Xen version of caml_alloc_pages clears the page, so we don't have to) *)
  Export.get () >>= fun gnt ->
  t.grant gnt page;
  return (gnt, Io_page.to_cstruct page)

let put t block =
  let was_empty = (t.blocks = []) in
  t.blocks <- block :: t.blocks;
  t.in_use <- t.in_use - 1;
  if was_empty then Lwt_condition.broadcast t.avail ();
  if t.in_use = 0 && t.shutdown then shutdown t

let use_block t fn block =
  let {id; gref; data} = block in
  t.in_use <- t.in_use + 1;
  Lwt.try_bind
    (fun () -> fn ~id gref data)
    (fun (_, release as result) ->
      Lwt.on_termination release (fun () -> put t block);
      return result
    )
    (fun ex -> put t block; Lwt.fail ex)

let rec use t fn =
  if t.shutdown then
    failwith "Shared_page_pool.use after shutdown";
  match t.blocks with
  | [] when t.next_id >= max_pages ->
      Lwt_condition.wait t.avail >>= fun () -> use t fn
  | [] ->
      (* Frames normally fit within 2048 bytes, so we split each page in half. *)
      alloc t >>= fun (gref, page) ->
      let b1 = Cstruct.sub page 0 block_size in
      let b2 = Cstruct.shift page block_size in
      let id1 = t.next_id in
      let id2 = t.next_id + 1 in
      t.next_id <- t.next_id + 2;
      t.blocks <- {id = id2; gref; data = b2} :: t.blocks;
      Lwt_condition.broadcast t.avail ();
      use_block t fn {id = id1; gref; data = b1}
  | hd :: tl ->
      t.blocks <- tl;
      use_block t fn hd

let blocks_needed bytes =
  (bytes + block_size - 1) / block_size
OCaml

Innovation. Community. Security.