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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
/** @file
*
* Buffer internals.
*
* A buffer consists of a single, contiguous area of memory, some of
* which is "filled" and the remainder of which is "free". The
* "filled" and "free" spaces are not necessarily contiguous.
*
* When a buffer is initialised via init_buffer(), it consists of a
* single free space. As data is added to the buffer via
* fill_buffer(), this free space decreases and can become fragmented.
*
* Each free block within a buffer starts with a "tail byte". If the
* tail byte is non-zero, this indicates that the free block is the
* tail of the buffer, i.e. occupies all the remaining space up to the
* end of the buffer. When the tail byte is non-zero, it indicates
* that a descriptor (a @c struct @c buffer_free_block) follows the
* tail byte. The descriptor describes the size of the free block and
* the address of the next free block.
*
* We cannot simply always start a free block with a descriptor,
* because it is conceivable that we will, at some point, encounter a
* situation in which the final free block of a buffer is too small to
* contain a descriptor. Consider a protocol with a blocksize of 512
* downloading a 1025-byte file into a 1025-byte buffer. Suppose that
* the first two blocks are received; we have now filled 1024 of the
* 1025 bytes in the buffer, and our only free block consists of the
* 1025th byte. Using a "tail byte" solves this problem.
*
*
* Note that the rather convoluted way of manipulating the buffer
* descriptors (using copy_{to,from}_phys rather than straightforward
* pointers) is needed to cope with operation as a PXE stack, when we
* may be running in real mode or 16-bit protected mode, and therefore
* cannot directly access arbitrary areas of memory using simple
* pointers.
*
*/
#include "stddef.h"
#include "string.h"
#include "io.h"
#include "errno.h"
#include "buffer.h"
/**
* Initialise a buffer.
*
* @v buffer The buffer to be initialised
* @ret None
* @err None
*
* Set @c buffer->start and @c buffer->end before calling init_buffer().
* init_buffer() will initialise the buffer to the state of being
* empty.
*
*/
void init_buffer ( struct buffer *buffer ) {
char tail = 1;
buffer->fill = 0;
if ( buffer->end != buffer->start )
copy_to_phys ( buffer->start, &tail, sizeof ( tail ) );
DBG ( "BUFFER [%x,%x) initialised\n", buffer->start, buffer->end );
}
/**
* Split a free block.
*
* @v desc A descriptor for the free block
* @v block Start address of the block
* @v split Address at which to split the block
* @ret None
* @err None
*
* Split a free block into two separate free blocks. If the split
* point lies outside the block, no action is taken; this is not an
* error.
*
* @b NOTE: It is the reponsibility of the caller to ensure that there
* is enough room in each of the two portions for a free block
* descriptor (a @c struct @c buffer_free_block, except in the case of
* a tail block which requires only a one byte descriptor). If the
* caller fails to do this, data corruption will occur.
*
* In practice, this means that the granularity at which blocks are
* split must be at least @c sizeof(struct @c buffer_free_block).
*
*/
static void split_free_block ( struct buffer_free_block *desc,
physaddr_t block, physaddr_t split ) {
/* If split point is before start of block, do nothing */
if ( split <= block )
return;
/* If split point is after end of block, do nothing */
if ( split >= desc->end )
return;
DBG ( "BUFFER splitting [%x,%x) -> [%x,%x) [%x,%x)\n",
block, desc->end, block, split, split, desc->end );
/* Create descriptor for new free block */
copy_to_phys ( split, &desc->tail, sizeof ( desc->tail ) );
if ( ! desc->tail )
copy_to_phys ( split, desc, sizeof ( *desc ) );
/* Update descriptor for old free block */
desc->tail = 0;
desc->next_free = split;
desc->end = split;
copy_to_phys ( block, desc, sizeof ( *desc ) );
}
/**
* Mark a free block as used.
*
* @v buffer The buffer containing the block
* @v desc A descriptor for the free block
* @v prev_block Address of the previous block
* @ret None
* @err None
*
* Marks a free block as used, i.e. removes it from the free list.
*
*/
static inline void unfree_block ( struct buffer *buffer,
struct buffer_free_block *desc,
physaddr_t prev_block ) {
struct buffer_free_block prev_desc;
/* If this is the first block, just update buffer->fill */
if ( ! prev_block ) {
DBG ( "BUFFER marking [%x,%x) as used\n",
buffer->start + buffer->fill, desc->end );
buffer->fill = desc->next_free - buffer->start;
return;
}
/* Get descriptor for previous block (which cannot be a tail block) */
copy_from_phys ( &prev_desc, prev_block, sizeof ( prev_desc ) );
DBG ( "BUFFER marking [%x,%x) as used\n",
prev_desc.next_free, desc->end );
/* Modify descriptor for previous block and write it back */
prev_desc.next_free = desc->next_free;
copy_to_phys ( prev_block, &prev_desc, sizeof ( prev_desc ) );
}
/**
* Write data into a buffer.
*
* @v buffer The buffer into which to write the data
* @v data The data to be written
* @v offset Offset within the buffer at which to write the data
* @v len Length of data to be written
* @ret True Data was successfully written
* @ret False Data was not written
* @err ENOMEM Buffer is too small to contain the data
*
* Writes a block of data into the buffer. The block need not be
* aligned to any particular boundary, or be of any particular size,
* and it may overlap blocks already in the buffer (i.e. duplicate
* calls to fill_buffer() are explicitly permitted).
*
* @c buffer->fill will be updated to indicate the fill level of the
* buffer, i.e. the offset to the first gap within the buffer. If the
* filesize is known (e.g. as with the SLAM protocol), you can test
* for end-of-file by checking for @c buffer->fill==filesize. If the
* filesize is not known, but there is a well-defined end-of-file test
* (e.g. as with the TFTP protocol), you can read @c buffer->fill to
* determine the final filesize. If blocks are known to be delivered
* in a strictly sequential order with no packet loss or duplication,
* then you can pass in @c offset==buffer->fill.
*
* @b NOTE: It is the caller's responsibility to ensure that the
* boundaries between data blocks are more than @c sizeof(struct @c
* buffer_free_block) apart. If this condition is not satisfied, data
* corruption will occur. (See split_free_block() for details.)
*
* In practice this is not a problem. Callers of fill_buffer() will
* be download protocols such as TFTP, and very few protocols have a
* block size smaller than @c sizeof(struct @c buffer_free_block).
*
*/
int fill_buffer ( struct buffer *buffer, const void *data,
off_t offset, size_t len ) {
struct buffer_free_block desc;
physaddr_t block, prev_block;
physaddr_t data_start, data_end;
/* Calculate start and end addresses of data */
data_start = buffer->start + offset;
data_end = data_start + len;
DBG ( "BUFFER [%x,%x) writing portion [%x,%x)\n",
buffer->start, buffer->end, data_start, data_end );
/* Check buffer bounds */
if ( data_end > buffer->end ) {
DBG ( "BUFFER [%x,%x) too small for data!\n",
buffer->start, buffer->end );
errno = ENOMEM;
return 0;
}
/* Iterate through the buffer's free blocks */
prev_block = 0;
block = buffer->start + buffer->fill;
while ( block < buffer->end ) {
/* Read block descriptor */
desc.next_free = buffer->end;
desc.end = buffer->end;
copy_from_phys ( &desc.tail, block, sizeof ( desc.tail ) );
if ( ! desc.tail )
copy_from_phys ( &desc, block, sizeof ( desc ) );
/* Split block at data start and end markers */
split_free_block ( &desc, block, data_start );
split_free_block ( &desc, block, data_end );
/* Block is now either completely contained by or
* completely outside the data area
*/
if ( ( block >= data_start ) && ( block < data_end ) ) {
/* Block is within the data area */
unfree_block ( buffer, &desc, prev_block );
copy_to_phys ( block, data + ( block - data_start ),
desc.end - block );
} else {
/* Block is outside the data area */
prev_block = block;
}
/* Move to next free block */
block = desc.next_free;
}
DBG ( "BUFFER [%x,%x) full up to %x\n",
buffer->start, buffer->end, buffer->start + buffer->fill );
return 1;
}
|