Added explanatory comment.
[people/xl0/gpxe.git] / src / core / buffer.c
1 /*
2  * Routines for filling a buffer with data received piecemeal, where
3  * the size of the data is not necessarily known in advance.
4  *
5  * Some protocols do not provide a mechanism for us to know the size
6  * of the file before we happen to receive a particular block
7  * (e.g. the final block in an MTFTP transfer).  In addition, some
8  * protocols (all the multicast protocols plus any TCP-based protocol)
9  * can, in theory, provide the data in any order.
10  *
11  * Rather than requiring each protocol to implement its own equivalent
12  * of "dd" to arrange the data into well-sized pieces before handing
13  * off to the image loader, we provide these generic buffer functions
14  * which assemble a file into a single contiguous block.  The whole
15  * block is then passed to the image loader.
16  *
17  *
18  * Note that the rather convoluted way of manipulating the buffer
19  * descriptors (using copy_{to,from}_phys rather than straightforward
20  * pointers) is needed to cope with operation as a PXE stack, when we
21  * may be running in real mode or 16-bit protected mode, and therefore
22  * cannot directly access arbitrary areas of memory.
23  *
24  */
25
26 #include "stddef.h"
27 #include "string.h"
28 #include "io.h"
29 #include "buffer.h"
30
31 /*
32  * Initialise a buffer
33  *
34  */
35 void init_buffer ( struct buffer *buffer, physaddr_t start, size_t len ) {
36         buffer->start = start;
37         buffer->end = start + len;
38         buffer->first_free = start;
39
40         if ( len ) {
41                 char tail = 1;
42                 copy_to_phys ( start, &tail, sizeof ( tail ) );
43         }
44 }
45
46 /*
47  * Split a free block
48  *
49  */
50 static void split_free_block ( struct buffer_free_block *desc,
51                                physaddr_t block, physaddr_t split ) {
52         /* If split point is before start of block, do nothing */
53         if ( split <= block )
54                 return;
55
56         /* If split point is after end of block, do nothing */
57         if ( split >= desc->end )
58                 return;
59
60         /* Create descriptor for new free block */
61         copy_to_phys ( split, &desc->tail, sizeof ( desc->tail ) );
62         if ( ! desc->tail )
63                 copy_to_phys ( split, desc, sizeof ( *desc ) );
64
65         /* Update descriptor for old free block */
66         desc->tail = 0;
67         desc->next_free = split;
68         desc->end = split;
69         copy_to_phys ( block, desc, sizeof ( *desc ) );
70 }
71
72 /*
73  * Mark a free block as used
74  *
75  */
76 static inline void unfree_block ( struct buffer *buffer,
77                                   struct buffer_free_block *desc,
78                                   physaddr_t prev_block ) {
79         struct buffer_free_block prev_desc;
80         
81         /* If this is the first block, just update first_free */
82         if ( ! prev_block ) {
83                 buffer->first_free = desc->next_free;
84                 return;
85         }
86
87         /* Get descriptor for previous block (which cannot be a tail block) */
88         copy_from_phys ( &prev_desc, prev_block, sizeof ( prev_desc ) );
89
90         /* Modify descriptor for previous block and write it back */
91         prev_desc.next_free = desc->next_free;
92         copy_to_phys ( prev_block, &prev_desc, sizeof ( prev_desc ) );
93 }
94
95 /*
96  * Write data into a buffer
97  *
98  * It is the caller's responsibility to ensure that the boundaries
99  * between data blocks are more than sizeof(struct buffer_free_block)
100  * apart.  If this condition is not satisfied, data corruption will
101  * occur.
102  *
103  * Returns the offset to the first gap in the buffer.  (When the
104  * buffer is full, returns the offset to the byte past the end of the
105  * buffer.)
106  *
107  */
108 off_t fill_buffer ( struct buffer *buffer, void *data,
109                     off_t offset, size_t len ) {
110         struct buffer_free_block desc;
111         physaddr_t block, prev_block;
112         physaddr_t data_start, data_end;
113
114         /* Calculate start and end addresses of data */
115         data_start = buffer->start + offset;
116         data_end = data_start + len;
117
118         /* Iterate through the buffer's free blocks */
119         prev_block = 0;
120         block = buffer->first_free;
121         while ( block < buffer->end ) {
122                 /* Read block descriptor */
123                 desc.next_free = buffer->end;
124                 desc.end = buffer->end;
125                 copy_from_phys ( &desc.tail, block, sizeof ( desc.tail ) );
126                 if ( ! desc.tail )
127                         copy_from_phys ( &desc, block, sizeof ( desc ) );
128
129                 /* Split block at data start and end markers */
130                 split_free_block ( &desc, block, data_start );
131                 split_free_block ( &desc, block, data_end );
132
133                 /* Block is now either completely contained by or
134                  * completely outside the data area
135                  */
136                 if ( ( block >= data_start ) && ( block <= data_end ) ) {
137                         /* Block is within the data area */
138                         unfree_block ( buffer, &desc, prev_block );
139                         copy_to_phys ( block, data + ( block - data_start ),
140                                        desc.end - block );
141                 } else {
142                         /* Block is outside the data area */
143                         prev_block = block;
144                 }
145
146                 /* Move to next free block */
147                 block = desc.next_free;
148         }
149
150         return ( buffer->first_free - buffer->start );
151 }