Files
ipxe/src/core/iobuf.c

314 lines
7.6 KiB
C
Raw Normal View History

/*
* Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
#include <stdint.h>
#include <strings.h>
2007-07-24 17:11:31 +01:00
#include <errno.h>
#include <ipxe/malloc.h>
#include <ipxe/iobuf.h>
/** @file
*
* I/O buffers
*
*/
/**
* Allocate I/O buffer with specified alignment and offset
*
* @v len Required length of buffer
* @v align Physical alignment
* @v offset Offset from physical alignment
* @ret iobuf I/O buffer, or NULL if none available
*
* @c align will be rounded up to the nearest power of two.
*/
struct io_buffer * alloc_iob_raw ( size_t len, size_t align, size_t offset ) {
struct io_buffer *iobuf;
size_t headroom;
size_t tailroom;
size_t padding;
size_t threshold;
unsigned int align_log2;
void *data;
/* Round up requested alignment and calculate initial headroom
* and tailroom to ensure that no cachelines are shared
* between I/O buffer data and other data structures.
*/
if ( align < IOB_ZLEN )
align = IOB_ZLEN;
headroom = ( offset & ( IOB_ZLEN - 1 ) );
tailroom = ( ( - len - offset ) & ( IOB_ZLEN - 1 ) );
padding = ( headroom + tailroom );
offset -= headroom;
len += padding;
if ( len < padding )
return NULL;
/* Round up alignment to the nearest power of two, avoiding
* a potentially undefined shift operation.
*/
align_log2 = fls ( align - 1 );
if ( align_log2 >= ( 8 * sizeof ( align ) ) )
return NULL;
align = ( 1UL << align_log2 );
/* Calculate length threshold */
assert ( align >= sizeof ( *iobuf ) );
threshold = ( align - sizeof ( *iobuf ) );
/* Allocate buffer plus an inline descriptor as a single unit,
* unless doing so would push the total size over the
* alignment boundary.
*/
if ( len <= threshold ) {
/* Allocate memory for buffer plus descriptor */
data = malloc_phys_offset ( len + sizeof ( *iobuf ), align,
offset );
if ( ! data )
return NULL;
iobuf = ( data + len );
} else {
/* Allocate memory for buffer */
data = malloc_phys_offset ( len, align, offset );
if ( ! data )
return NULL;
/* Allocate memory for descriptor */
iobuf = malloc ( sizeof ( *iobuf ) );
if ( ! iobuf ) {
free_phys ( data, len );
return NULL;
}
}
/* Populate descriptor */
[dma] Move I/O buffer DMA operations to iobuf.h Include a potential DMA mapping within the definition of an I/O buffer, and move all I/O buffer DMA mapping functions from dma.h to iobuf.h. This avoids the need for drivers to maintain a separate list of DMA mappings for each I/O buffer that they may handle. Network device drivers typically do not keep track of transmit I/O buffers, since the network device core already maintains a transmit queue. Drivers will typically call netdev_tx_complete_next() to complete a transmission without first obtaining the relevant I/O buffer pointer (and will rely on the network device core automatically cancelling any pending transmissions when the device is closed). To allow this driver design approach to be retained, update the netdev_tx_complete() family of functions to automatically perform the DMA unmapping operation if required. For symmetry, also update the netdev_rx() family of functions to behave the same way. As a further convenience for drivers, allow the network device core to automatically perform DMA mapping on the transmit datapath before calling the driver's transmit() method. This avoids the need to introduce a mapping error handling code path into the typically error-free transmit methods. With these changes, the modifications required to update a typical network device driver to use the new DMA API are fairly minimal: - Allocate and free descriptor rings and similar coherent structures using dma_alloc()/dma_free() rather than malloc_phys()/free_phys() - Allocate and free receive buffers using alloc_rx_iob()/free_rx_iob() rather than alloc_iob()/free_iob() - Calculate DMA addresses using dma() or iob_dma() rather than virt_to_bus() - Set a 64-bit DMA mask if needed using dma_set_mask_64bit() and thereafter eliminate checks on DMA address ranges - Either record the DMA device in netdev->dma, or call iob_map_tx() as part of the transmit() method - Ensure that debug messages use virt_to_phys() when displaying "hardware" addresses Signed-off-by: Michael Brown <mcb30@ipxe.org>
2020-11-26 12:25:02 +00:00
memset ( &iobuf->map, 0, sizeof ( iobuf->map ) );
iobuf->head = data;
iobuf->data = iobuf->tail = ( data + headroom );
iobuf->end = ( data + len );
return iobuf;
}
/**
* Allocate I/O buffer
*
* @v len Required length of buffer
* @ret iobuf I/O buffer, or NULL if none available
*
* The I/O buffer will be physically aligned on its own size (rounded
* up to the nearest power of two), up to a maximum of page-size
* alignment.
*/
struct io_buffer * alloc_iob ( size_t len ) {
size_t align;
/* Pad to minimum length */
if ( len < IOB_ZLEN )
len = IOB_ZLEN;
/* Align buffer on its own size (up to page size) to avoid
* potential problems with boundary-crossing DMA.
*/
align = len;
if ( align > PAGE_SIZE )
align = PAGE_SIZE;
return alloc_iob_raw ( len, align, 0 );
}
/**
* Free I/O buffer
*
* @v iobuf I/O buffer
*/
void free_iob ( struct io_buffer *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 );
[dma] Move I/O buffer DMA operations to iobuf.h Include a potential DMA mapping within the definition of an I/O buffer, and move all I/O buffer DMA mapping functions from dma.h to iobuf.h. This avoids the need for drivers to maintain a separate list of DMA mappings for each I/O buffer that they may handle. Network device drivers typically do not keep track of transmit I/O buffers, since the network device core already maintains a transmit queue. Drivers will typically call netdev_tx_complete_next() to complete a transmission without first obtaining the relevant I/O buffer pointer (and will rely on the network device core automatically cancelling any pending transmissions when the device is closed). To allow this driver design approach to be retained, update the netdev_tx_complete() family of functions to automatically perform the DMA unmapping operation if required. For symmetry, also update the netdev_rx() family of functions to behave the same way. As a further convenience for drivers, allow the network device core to automatically perform DMA mapping on the transmit datapath before calling the driver's transmit() method. This avoids the need to introduce a mapping error handling code path into the typically error-free transmit methods. With these changes, the modifications required to update a typical network device driver to use the new DMA API are fairly minimal: - Allocate and free descriptor rings and similar coherent structures using dma_alloc()/dma_free() rather than malloc_phys()/free_phys() - Allocate and free receive buffers using alloc_rx_iob()/free_rx_iob() rather than alloc_iob()/free_iob() - Calculate DMA addresses using dma() or iob_dma() rather than virt_to_bus() - Set a 64-bit DMA mask if needed using dma_set_mask_64bit() and thereafter eliminate checks on DMA address ranges - Either record the DMA device in netdev->dma, or call iob_map_tx() as part of the transmit() method - Ensure that debug messages use virt_to_phys() when displaying "hardware" addresses Signed-off-by: Michael Brown <mcb30@ipxe.org>
2020-11-26 12:25:02 +00:00
assert ( ! dma_mapped ( &iobuf->map ) );
/* Free buffer */
len = ( iobuf->end - iobuf->head );
if ( iobuf->end == iobuf ) {
/* Descriptor is inline */
free_phys ( iobuf->head, ( len + sizeof ( *iobuf ) ) );
} else {
/* Descriptor is detached */
free_phys ( iobuf->head, len );
free ( iobuf );
}
}
2007-07-24 17:11:31 +01:00
[dma] Move I/O buffer DMA operations to iobuf.h Include a potential DMA mapping within the definition of an I/O buffer, and move all I/O buffer DMA mapping functions from dma.h to iobuf.h. This avoids the need for drivers to maintain a separate list of DMA mappings for each I/O buffer that they may handle. Network device drivers typically do not keep track of transmit I/O buffers, since the network device core already maintains a transmit queue. Drivers will typically call netdev_tx_complete_next() to complete a transmission without first obtaining the relevant I/O buffer pointer (and will rely on the network device core automatically cancelling any pending transmissions when the device is closed). To allow this driver design approach to be retained, update the netdev_tx_complete() family of functions to automatically perform the DMA unmapping operation if required. For symmetry, also update the netdev_rx() family of functions to behave the same way. As a further convenience for drivers, allow the network device core to automatically perform DMA mapping on the transmit datapath before calling the driver's transmit() method. This avoids the need to introduce a mapping error handling code path into the typically error-free transmit methods. With these changes, the modifications required to update a typical network device driver to use the new DMA API are fairly minimal: - Allocate and free descriptor rings and similar coherent structures using dma_alloc()/dma_free() rather than malloc_phys()/free_phys() - Allocate and free receive buffers using alloc_rx_iob()/free_rx_iob() rather than alloc_iob()/free_iob() - Calculate DMA addresses using dma() or iob_dma() rather than virt_to_bus() - Set a 64-bit DMA mask if needed using dma_set_mask_64bit() and thereafter eliminate checks on DMA address ranges - Either record the DMA device in netdev->dma, or call iob_map_tx() as part of the transmit() method - Ensure that debug messages use virt_to_phys() when displaying "hardware" addresses Signed-off-by: Michael Brown <mcb30@ipxe.org>
2020-11-26 12:25:02 +00:00
/**
* Allocate and map I/O buffer for receive DMA
*
* @v len Length of I/O buffer
* @v dma DMA device
* @ret iobuf I/O buffer, or NULL on error
*/
struct io_buffer * alloc_rx_iob ( size_t len, struct dma_device *dma ) {
struct io_buffer *iobuf;
int rc;
/* Allocate I/O buffer */
iobuf = alloc_iob ( len );
if ( ! iobuf )
goto err_alloc;
/* Map I/O buffer */
if ( ( rc = iob_map_rx ( iobuf, dma ) ) != 0 )
goto err_map;
return iobuf;
iob_unmap ( iobuf );
err_map:
free_iob ( iobuf );
err_alloc:
return NULL;
}
/**
* Unmap and free I/O buffer for receive DMA
*
* @v iobuf I/O buffer
*/
void free_rx_iob ( struct io_buffer *iobuf ) {
/* Unmap I/O buffer */
iob_unmap ( iobuf );
/* Free I/O buffer */
free_iob ( iobuf );
}
2007-07-24 17:11:31 +01:00
/**
* Ensure I/O buffer has sufficient headroom
*
* @v iobuf I/O buffer
* @v len Required headroom
*
* This function currently only checks for the required headroom; it
* does not reallocate the I/O buffer if required. If we ever have a
* code path that requires this functionality, it's a fairly trivial
* change to make.
*/
int iob_ensure_headroom ( struct io_buffer *iobuf, size_t len ) {
if ( iob_headroom ( iobuf ) >= len )
return 0;
return -ENOBUFS;
}
/**
* Concatenate I/O buffers into a single buffer
*
* @v list List of I/O buffers
* @ret iobuf Concatenated I/O buffer, or NULL on allocation failure
*
* After a successful concatenation, the list will be empty.
*/
struct io_buffer * iob_concatenate ( struct list_head *list ) {
struct io_buffer *iobuf;
struct io_buffer *tmp;
struct io_buffer *concatenated;
size_t len = 0;
/* If the list contains only a single entry, avoid an
* unnecessary additional allocation.
*/
if ( list_is_singular ( list ) ) {
iobuf = list_first_entry ( list, struct io_buffer, list );
INIT_LIST_HEAD ( list );
return iobuf;
}
/* Calculate total length */
list_for_each_entry ( iobuf, list, list )
len += iob_len ( iobuf );
/* Allocate new I/O buffer */
concatenated = alloc_iob_raw ( len, IOB_ZLEN, 0 );
if ( ! concatenated )
return NULL;
/* Move data to new I/O buffer */
list_for_each_entry_safe ( iobuf, tmp, list, list ) {
list_del ( &iobuf->list );
memcpy ( iob_put ( concatenated, iob_len ( iobuf ) ),
iobuf->data, iob_len ( iobuf ) );
free_iob ( iobuf );
}
return concatenated;
}
/**
* Split I/O buffer
*
* @v iobuf I/O buffer
* @v len Length to split into a new I/O buffer
* @ret split New I/O buffer, or NULL on allocation failure
*
* Split the first @c len bytes of the existing I/O buffer into a
* separate I/O buffer. The resulting buffers are likely to have no
* headroom or tailroom.
*
* If this call fails, then the original buffer will be unmodified.
*/
struct io_buffer * iob_split ( struct io_buffer *iobuf, size_t len ) {
struct io_buffer *split;
/* Sanity checks */
assert ( len <= iob_len ( iobuf ) );
/* Allocate new I/O buffer */
split = alloc_iob ( len );
if ( ! split )
return NULL;
/* Copy in data */
memcpy ( iob_put ( split, len ), iobuf->data, len );
iob_pull ( iobuf, len );
return split;
}