Modified to use physical addresses, and to not assume that we can directly
[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
19 #include "stddef.h"
20 #include "string.h"
21 #include "io.h"
22 #include "buffer.h"
23
24 /*
25  * Initialise a buffer
26  *
27  */
28 void init_buffer ( struct buffer *buffer, physaddr_t start, size_t len ) {
29         buffer->start = start;
30         buffer->end = start + len;
31         buffer->first_free = start;
32
33         if ( len ) {
34                 char tail = 1;
35                 copy_to_phys ( start, &tail, sizeof ( tail ) );
36         }
37 }
38
39 /*
40  * Split a free block
41  *
42  */
43 static void split_free_block ( struct buffer_free_block *desc,
44                                physaddr_t block, physaddr_t split ) {
45         /* If split point is before start of block, do nothing */
46         if ( split <= block )
47                 return;
48
49         /* If split point is after end of block, do nothing */
50         if ( split >= desc->end )
51                 return;
52
53         /* Create descriptor for new free block */
54         copy_to_phys ( split, &desc->tail, sizeof ( desc->tail ) );
55         if ( ! desc->tail )
56                 copy_to_phys ( split, desc, sizeof ( *desc ) );
57
58         /* Update descriptor for old free block */
59         desc->tail = 0;
60         desc->next_free = split;
61         desc->end = split;
62         copy_to_phys ( block, desc, sizeof ( *desc ) );
63 }
64
65 /*
66  * Mark a free block as used
67  *
68  */
69 static inline void unfree_block ( struct buffer *buffer,
70                                   struct buffer_free_block *desc,
71                                   physaddr_t prev_block ) {
72         struct buffer_free_block prev_desc;
73         
74         /* If this is the first block, just update first_free */
75         if ( ! prev_block ) {
76                 buffer->first_free = desc->next_free;
77                 return;
78         }
79
80         /* Get descriptor for previous block (which cannot be a tail block) */
81         copy_from_phys ( &prev_desc, prev_block, sizeof ( prev_desc ) );
82
83         /* Modify descriptor for previous block and write it back */
84         prev_desc.next_free = desc->next_free;
85         copy_to_phys ( prev_block, &prev_desc, sizeof ( prev_desc ) );
86 }
87
88 /*
89  * Write data into a buffer
90  *
91  * It is the caller's responsibility to ensure that the boundaries
92  * between data blocks are more than sizeof(struct buffer_free_block)
93  * apart.  If this condition is not satisfied, data corruption will
94  * occur.
95  *
96  * Returns the offset to the first gap in the buffer.  (When the
97  * buffer is full, returns the offset to the byte past the end of the
98  * buffer.)
99  *
100  */
101 off_t fill_buffer ( struct buffer *buffer, void *data,
102                     off_t offset, size_t len ) {
103         struct buffer_free_block desc;
104         physaddr_t block, prev_block;
105         physaddr_t data_start, data_end;
106
107         /* Calculate start and end addresses of data */
108         data_start = buffer->start + offset;
109         data_end = data_start + len;
110
111         /* Iterate through the buffer's free blocks */
112         prev_block = 0;
113         block = buffer->first_free;
114         while ( block < buffer->end ) {
115                 /* Read block descriptor */
116                 desc.next_free = buffer->end;
117                 desc.end = buffer->end;
118                 copy_from_phys ( &desc.tail, block, sizeof ( desc.tail ) );
119                 if ( ! desc.tail )
120                         copy_from_phys ( &desc, block, sizeof ( desc ) );
121
122                 /* Split block at data start and end markers */
123                 split_free_block ( &desc, block, data_start );
124                 split_free_block ( &desc, block, data_end );
125
126                 /* Block is now either completely contained by or
127                  * completely outside the data area
128                  */
129                 if ( ( block >= data_start ) && ( block <= data_end ) ) {
130                         /* Block is within the data area */
131                         unfree_block ( buffer, &desc, prev_block );
132                         copy_to_phys ( block, data + ( block - data_start ),
133                                        desc.end - block );
134                 } else {
135                         /* Block is outside the data area */
136                         prev_block = block;
137                 }
138
139                 /* Move to next free block */
140                 block = desc.next_free;
141         }
142
143         return ( buffer->first_free - buffer->start );
144 }