]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[iobuf] Allocate I/O buffer descriptor separately to conserve aligned memory
authorMichael Brown <mcb30@ipxe.org>
Sun, 8 Jul 2012 16:14:08 +0000 (17:14 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sun, 8 Jul 2012 16:54:53 +0000 (17:54 +0100)
I/O buffers are allocated on aligned boundaries.  The I/O buffer
descriptor (the struct io_buffer) is currently attached to the end of
the I/O buffer.  When the size of the buffer is close to its
alignment, this can waste large amounts of aligned memory.

For example, a network card using 2048-byte receive buffers will end
up allocating 2072 bytes on a 2048-byte boundary.  This effectively
wastes 50% of the available memory.

Improve the situation by allocating the descriptor separately from the
main I/O buffer if inline allocation would cause the total allocated
size to cross the alignment boundary.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/core/iobuf.c

index 3dfaf18c2f12d8d25b621c13af0344a4c52ab619..0c61306e53954ae0f218433e4de10baa45122d0d 100644 (file)
@@ -40,7 +40,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
  * @c IOBUF_SIZE.
  */
 struct io_buffer * alloc_iob ( size_t len ) {
-       struct io_buffer *iobuf = NULL;
+       struct io_buffer *iobuf;
        size_t align;
        void *data;
 
@@ -57,14 +57,37 @@ struct io_buffer * alloc_iob ( size_t len ) {
         */
        align = ( 1 << fls ( len - 1 ) );
 
-       /* Allocate memory for buffer plus descriptor */
-       data = malloc_dma ( len + sizeof ( *iobuf ), align );
-       if ( ! data )
-               return NULL;
+       /* Allocate buffer plus descriptor as a single unit, unless
+        * doing so will push the total size over the alignment
+        * boundary.
+        */
+       if ( ( len + sizeof ( *iobuf ) ) <= align ) {
+
+               /* Allocate memory for buffer plus descriptor */
+               data = malloc_dma ( len + sizeof ( *iobuf ), align );
+               if ( ! data )
+                       return NULL;
+               iobuf = ( data + len );
+
+       } else {
+
+               /* Allocate memory for buffer */
+               data = malloc_dma ( len, align );
+               if ( ! data )
+                       return NULL;
 
-       iobuf = ( struct io_buffer * ) ( data + len );
+               /* Allocate memory for descriptor */
+               iobuf = malloc ( sizeof ( *iobuf ) );
+               if ( ! iobuf ) {
+                       free_dma ( data, len );
+                       return NULL;
+               }
+       }
+
+       /* Populate descriptor */
        iobuf->head = iobuf->data = iobuf->tail = data;
-       iobuf->end = iobuf;
+       iobuf->end = ( data + len );
+
        return iobuf;
 }
 
@@ -75,12 +98,29 @@ struct io_buffer * alloc_iob ( size_t len ) {
  * @v iobuf    I/O buffer
  */
 void free_iob ( struct io_buffer *iobuf ) {
-       if ( iobuf ) {
-               assert ( iobuf->head <= iobuf->data );
-               assert ( iobuf->data <= iobuf->tail );
-               assert ( iobuf->tail <= iobuf->end );
-               free_dma ( iobuf->head,
-                          ( iobuf->end - iobuf->head ) + sizeof ( *iobuf ) );
+       size_t len;
+
+       /* Allow free_iob(NULL) to be valid */
+       if ( ! iobuf )
+               return;
+
+       /* Sanity checks */
+       assert ( iobuf->head <= iobuf->data );
+       assert ( iobuf->data <= iobuf->tail );
+       assert ( iobuf->tail <= iobuf->end );
+
+       /* Free buffer */
+       len = ( iobuf->end - iobuf->head );
+       if ( iobuf->end == iobuf ) {
+
+               /* Descriptor is inline */
+               free_dma ( iobuf->head, ( len + sizeof ( *iobuf ) ) );
+
+       } else {
+
+               /* Descriptor is detached */
+               free_dma ( iobuf->head, len );
+               free ( iobuf );
        }
 }