Make "struct buffer"s reusable between sessions.
[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 ) {
36         char tail = 1;
37
38         buffer->fill = 0;
39         if ( buffer->end != buffer->start )
40                 copy_to_phys ( buffer->start, &tail, sizeof ( tail ) );
41
42         DBG ( "BUFFER [%x,%x) initialised\n", buffer->start, buffer->end );
43 }
44
45 /*
46  * Split a free block
47  *
48  */
49 static void split_free_block ( struct buffer_free_block *desc,
50                                physaddr_t block, physaddr_t split ) {
51         /* If split point is before start of block, do nothing */
52         if ( split <= block )
53                 return;
54
55         /* If split point is after end of block, do nothing */
56         if ( split >= desc->end )
57                 return;
58
59         DBG ( "BUFFER splitting [%x,%x) -> [%x,%x) [%x,%x)\n",
60               block, desc->end, block, split, split, desc->end );
61
62         /* Create descriptor for new free block */
63         copy_to_phys ( split, &desc->tail, sizeof ( desc->tail ) );
64         if ( ! desc->tail )
65                 copy_to_phys ( split, desc, sizeof ( *desc ) );
66
67         /* Update descriptor for old free block */
68         desc->tail = 0;
69         desc->next_free = split;
70         desc->end = split;
71         copy_to_phys ( block, desc, sizeof ( *desc ) );
72 }
73
74 /*
75  * Mark a free block as used
76  *
77  */
78 static inline void unfree_block ( struct buffer *buffer,
79                                   struct buffer_free_block *desc,
80                                   physaddr_t prev_block ) {
81         struct buffer_free_block prev_desc;
82         
83         /* If this is the first block, just update buffer->fill */
84         if ( ! prev_block ) {
85                 DBG ( "BUFFER marking [%x,%x) as used\n",
86                       buffer->start + buffer->fill, desc->end );
87                 buffer->fill = desc->next_free - buffer->start;
88                 return;
89         }
90
91         /* Get descriptor for previous block (which cannot be a tail block) */
92         copy_from_phys ( &prev_desc, prev_block, sizeof ( prev_desc ) );
93
94         DBG ( "BUFFER marking [%x,%x) as used\n",
95               prev_desc.next_free, desc->end );
96
97         /* Modify descriptor for previous block and write it back */
98         prev_desc.next_free = desc->next_free;
99         copy_to_phys ( prev_block, &prev_desc, sizeof ( prev_desc ) );
100 }
101
102 /*
103  * Write data into a buffer
104  *
105  * It is the caller's responsibility to ensure that the boundaries
106  * between data blocks are more than sizeof(struct buffer_free_block)
107  * apart.  If this condition is not satisfied, data corruption will
108  * occur.
109  *
110  * Returns 1 for success, 0 for failure (e.g. buffer too small).
111  */
112 int fill_buffer ( struct buffer *buffer, void *data,
113                   off_t offset, size_t len ) {
114         struct buffer_free_block desc;
115         physaddr_t block, prev_block;
116         physaddr_t data_start, data_end;
117
118         /* Calculate start and end addresses of data */
119         data_start = buffer->start + offset;
120         data_end = data_start + len;
121         DBG ( "BUFFER [%x,%x) writing portion [%x,%x)\n",
122               buffer->start, buffer->end, data_start, data_end );
123
124         /* Check buffer bounds */
125         if ( data_end > buffer->end ) {
126                 DBG ( "BUFFER [%x,%x) too small for data!\n",
127                       buffer->start, buffer->end );
128                 return 0;
129         }
130
131         /* Iterate through the buffer's free blocks */
132         prev_block = 0;
133         block = buffer->start + buffer->fill;
134         while ( block < buffer->end ) {
135                 /* Read block descriptor */
136                 desc.next_free = buffer->end;
137                 desc.end = buffer->end;
138                 copy_from_phys ( &desc.tail, block, sizeof ( desc.tail ) );
139                 if ( ! desc.tail )
140                         copy_from_phys ( &desc, block, sizeof ( desc ) );
141
142                 /* Split block at data start and end markers */
143                 split_free_block ( &desc, block, data_start );
144                 split_free_block ( &desc, block, data_end );
145
146                 /* Block is now either completely contained by or
147                  * completely outside the data area
148                  */
149                 if ( ( block >= data_start ) && ( block < data_end ) ) {
150                         /* Block is within the data area */
151                         unfree_block ( buffer, &desc, prev_block );
152                         copy_to_phys ( block, data + ( block - data_start ),
153                                        desc.end - block );
154                 } else {
155                         /* Block is outside the data area */
156                         prev_block = block;
157                 }
158
159                 /* Move to next free block */
160                 block = desc.next_free;
161         }
162
163         DBG ( "BUFFER [%x,%x) full up to %x\n",
164               buffer->start, buffer->end, buffer->start + buffer->fill );
165
166         return 1;
167 }