2013-06-04 09:20:21 +04:00
/*
* Copyright ( C ) 2009 , 2010 Red Hat Inc , Steven Rostedt < srostedt @ redhat . com >
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ;
* version 2.1 of the License ( not later ! )
*
* 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 Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser 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 .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include "kbuffer.h"
# define MISSING_EVENTS (1 << 31)
# define MISSING_STORED (1 << 30)
# define COMMIT_MASK ((1 << 27) - 1)
enum {
KBUFFER_FL_HOST_BIG_ENDIAN = ( 1 < < 0 ) ,
KBUFFER_FL_BIG_ENDIAN = ( 1 < < 1 ) ,
KBUFFER_FL_LONG_8 = ( 1 < < 2 ) ,
KBUFFER_FL_OLD_FORMAT = ( 1 < < 3 ) ,
} ;
# define ENDIAN_MASK (KBUFFER_FL_HOST_BIG_ENDIAN | KBUFFER_FL_BIG_ENDIAN)
/** kbuffer
* @ timestamp - timestamp of current event
* @ lost_events - # of lost events between this subbuffer and previous
* @ flags - special flags of the kbuffer
* @ subbuffer - pointer to the sub - buffer page
* @ data - pointer to the start of data on the sub - buffer page
* @ index - index from @ data to the @ curr event data
* @ curr - offset from @ data to the start of current event
* ( includes metadata )
* @ next - offset from @ data to the start of next event
* @ size - The size of data on @ data
* @ start - The offset from @ subbuffer where @ data lives
*
* @ read_4 - Function to read 4 raw bytes ( may swap )
* @ read_8 - Function to read 8 raw bytes ( may swap )
* @ read_long - Function to read a long word ( 4 or 8 bytes with needed swap )
*/
struct kbuffer {
unsigned long long timestamp ;
long long lost_events ;
unsigned long flags ;
void * subbuffer ;
void * data ;
unsigned int index ;
unsigned int curr ;
unsigned int next ;
unsigned int size ;
unsigned int start ;
unsigned int ( * read_4 ) ( void * ptr ) ;
unsigned long long ( * read_8 ) ( void * ptr ) ;
unsigned long long ( * read_long ) ( struct kbuffer * kbuf , void * ptr ) ;
int ( * next_event ) ( struct kbuffer * kbuf ) ;
} ;
static void * zmalloc ( size_t size )
{
return calloc ( 1 , size ) ;
}
static int host_is_bigendian ( void )
{
unsigned char str [ ] = { 0x1 , 0x2 , 0x3 , 0x4 } ;
unsigned int * ptr ;
ptr = ( unsigned int * ) str ;
return * ptr = = 0x01020304 ;
}
static int do_swap ( struct kbuffer * kbuf )
{
return ( ( kbuf - > flags & KBUFFER_FL_HOST_BIG_ENDIAN ) + kbuf - > flags ) &
ENDIAN_MASK ;
}
static unsigned long long __read_8 ( void * ptr )
{
unsigned long long data = * ( unsigned long long * ) ptr ;
return data ;
}
static unsigned long long __read_8_sw ( void * ptr )
{
unsigned long long data = * ( unsigned long long * ) ptr ;
unsigned long long swap ;
swap = ( ( data & 0xffULL ) < < 56 ) |
( ( data & ( 0xffULL < < 8 ) ) < < 40 ) |
( ( data & ( 0xffULL < < 16 ) ) < < 24 ) |
( ( data & ( 0xffULL < < 24 ) ) < < 8 ) |
( ( data & ( 0xffULL < < 32 ) ) > > 8 ) |
( ( data & ( 0xffULL < < 40 ) ) > > 24 ) |
( ( data & ( 0xffULL < < 48 ) ) > > 40 ) |
( ( data & ( 0xffULL < < 56 ) ) > > 56 ) ;
return swap ;
}
static unsigned int __read_4 ( void * ptr )
{
unsigned int data = * ( unsigned int * ) ptr ;
return data ;
}
static unsigned int __read_4_sw ( void * ptr )
{
unsigned int data = * ( unsigned int * ) ptr ;
unsigned int swap ;
swap = ( ( data & 0xffULL ) < < 24 ) |
( ( data & ( 0xffULL < < 8 ) ) < < 8 ) |
( ( data & ( 0xffULL < < 16 ) ) > > 8 ) |
( ( data & ( 0xffULL < < 24 ) ) > > 24 ) ;
return swap ;
}
static unsigned long long read_8 ( struct kbuffer * kbuf , void * ptr )
{
return kbuf - > read_8 ( ptr ) ;
}
static unsigned int read_4 ( struct kbuffer * kbuf , void * ptr )
{
return kbuf - > read_4 ( ptr ) ;
}
static unsigned long long __read_long_8 ( struct kbuffer * kbuf , void * ptr )
{
return kbuf - > read_8 ( ptr ) ;
}
static unsigned long long __read_long_4 ( struct kbuffer * kbuf , void * ptr )
{
return kbuf - > read_4 ( ptr ) ;
}
static unsigned long long read_long ( struct kbuffer * kbuf , void * ptr )
{
return kbuf - > read_long ( kbuf , ptr ) ;
}
static int calc_index ( struct kbuffer * kbuf , void * ptr )
{
return ( unsigned long ) ptr - ( unsigned long ) kbuf - > data ;
}
static int __next_event ( struct kbuffer * kbuf ) ;
/**
* kbuffer_alloc - allocat a new kbuffer
* @ size ; enum to denote size of word
* @ endian : enum to denote endianness
*
* Allocates and returns a new kbuffer .
*/
struct kbuffer *
kbuffer_alloc ( enum kbuffer_long_size size , enum kbuffer_endian endian )
{
struct kbuffer * kbuf ;
int flags = 0 ;
switch ( size ) {
case KBUFFER_LSIZE_4 :
break ;
case KBUFFER_LSIZE_8 :
flags | = KBUFFER_FL_LONG_8 ;
break ;
default :
return NULL ;
}
switch ( endian ) {
case KBUFFER_ENDIAN_LITTLE :
break ;
case KBUFFER_ENDIAN_BIG :
flags | = KBUFFER_FL_BIG_ENDIAN ;
break ;
default :
return NULL ;
}
kbuf = zmalloc ( sizeof ( * kbuf ) ) ;
if ( ! kbuf )
return NULL ;
kbuf - > flags = flags ;
if ( host_is_bigendian ( ) )
kbuf - > flags | = KBUFFER_FL_HOST_BIG_ENDIAN ;
if ( do_swap ( kbuf ) ) {
kbuf - > read_8 = __read_8_sw ;
kbuf - > read_4 = __read_4_sw ;
} else {
kbuf - > read_8 = __read_8 ;
kbuf - > read_4 = __read_4 ;
}
if ( kbuf - > flags & KBUFFER_FL_LONG_8 )
kbuf - > read_long = __read_long_8 ;
else
kbuf - > read_long = __read_long_4 ;
/* May be changed by kbuffer_set_old_format() */
kbuf - > next_event = __next_event ;
return kbuf ;
}
/** kbuffer_free - free an allocated kbuffer
* @ kbuf : The kbuffer to free
*
* Can take NULL as a parameter .
*/
void kbuffer_free ( struct kbuffer * kbuf )
{
free ( kbuf ) ;
}
static unsigned int type4host ( struct kbuffer * kbuf ,
unsigned int type_len_ts )
{
if ( kbuf - > flags & KBUFFER_FL_BIG_ENDIAN )
return ( type_len_ts > > 29 ) & 3 ;
else
return type_len_ts & 3 ;
}
static unsigned int len4host ( struct kbuffer * kbuf ,
unsigned int type_len_ts )
{
if ( kbuf - > flags & KBUFFER_FL_BIG_ENDIAN )
return ( type_len_ts > > 27 ) & 7 ;
else
return ( type_len_ts > > 2 ) & 7 ;
}
static unsigned int type_len4host ( struct kbuffer * kbuf ,
unsigned int type_len_ts )
{
if ( kbuf - > flags & KBUFFER_FL_BIG_ENDIAN )
return ( type_len_ts > > 27 ) & ( ( 1 < < 5 ) - 1 ) ;
else
return type_len_ts & ( ( 1 < < 5 ) - 1 ) ;
}
static unsigned int ts4host ( struct kbuffer * kbuf ,
unsigned int type_len_ts )
{
if ( kbuf - > flags & KBUFFER_FL_BIG_ENDIAN )
return type_len_ts & ( ( 1 < < 27 ) - 1 ) ;
else
return type_len_ts > > 5 ;
}
/*
* Linux 2.6 .30 and earlier ( not much ealier ) had a different
* ring buffer format . It should be obsolete , but we handle it anyway .
*/
enum old_ring_buffer_type {
OLD_RINGBUF_TYPE_PADDING ,
OLD_RINGBUF_TYPE_TIME_EXTEND ,
OLD_RINGBUF_TYPE_TIME_STAMP ,
OLD_RINGBUF_TYPE_DATA ,
} ;
static unsigned int old_update_pointers ( struct kbuffer * kbuf )
{
unsigned long long extend ;
unsigned int type_len_ts ;
unsigned int type ;
unsigned int len ;
unsigned int delta ;
unsigned int length ;
void * ptr = kbuf - > data + kbuf - > curr ;
type_len_ts = read_4 ( kbuf , ptr ) ;
ptr + = 4 ;
type = type4host ( kbuf , type_len_ts ) ;
len = len4host ( kbuf , type_len_ts ) ;
delta = ts4host ( kbuf , type_len_ts ) ;
switch ( type ) {
case OLD_RINGBUF_TYPE_PADDING :
kbuf - > next = kbuf - > size ;
return 0 ;
case OLD_RINGBUF_TYPE_TIME_EXTEND :
extend = read_4 ( kbuf , ptr ) ;
extend < < = TS_SHIFT ;
extend + = delta ;
delta = extend ;
ptr + = 4 ;
break ;
case OLD_RINGBUF_TYPE_TIME_STAMP :
/* should never happen! */
kbuf - > curr = kbuf - > size ;
kbuf - > next = kbuf - > size ;
kbuf - > index = kbuf - > size ;
return - 1 ;
default :
if ( len )
length = len * 4 ;
else {
length = read_4 ( kbuf , ptr ) ;
length - = 4 ;
ptr + = 4 ;
}
break ;
}
kbuf - > timestamp + = delta ;
kbuf - > index = calc_index ( kbuf , ptr ) ;
kbuf - > next = kbuf - > index + length ;
return type ;
}
static int __old_next_event ( struct kbuffer * kbuf )
{
int type ;
do {
kbuf - > curr = kbuf - > next ;
if ( kbuf - > next > = kbuf - > size )
return - 1 ;
type = old_update_pointers ( kbuf ) ;
} while ( type = = OLD_RINGBUF_TYPE_TIME_EXTEND | | type = = OLD_RINGBUF_TYPE_PADDING ) ;
return 0 ;
}
static unsigned int
translate_data ( struct kbuffer * kbuf , void * data , void * * rptr ,
unsigned long long * delta , int * length )
{
unsigned long long extend ;
unsigned int type_len_ts ;
unsigned int type_len ;
type_len_ts = read_4 ( kbuf , data ) ;
data + = 4 ;
type_len = type_len4host ( kbuf , type_len_ts ) ;
* delta = ts4host ( kbuf , type_len_ts ) ;
switch ( type_len ) {
case KBUFFER_TYPE_PADDING :
* length = read_4 ( kbuf , data ) ;
break ;
case KBUFFER_TYPE_TIME_EXTEND :
extend = read_4 ( kbuf , data ) ;
data + = 4 ;
extend < < = TS_SHIFT ;
extend + = * delta ;
* delta = extend ;
* length = 0 ;
break ;
case KBUFFER_TYPE_TIME_STAMP :
data + = 12 ;
* length = 0 ;
break ;
case 0 :
* length = read_4 ( kbuf , data ) - 4 ;
* length = ( * length + 3 ) & ~ 3 ;
data + = 4 ;
break ;
default :
* length = type_len * 4 ;
break ;
}
* rptr = data ;
return type_len ;
}
static unsigned int update_pointers ( struct kbuffer * kbuf )
{
unsigned long long delta ;
unsigned int type_len ;
int length ;
void * ptr = kbuf - > data + kbuf - > curr ;
type_len = translate_data ( kbuf , ptr , & ptr , & delta , & length ) ;
kbuf - > timestamp + = delta ;
kbuf - > index = calc_index ( kbuf , ptr ) ;
kbuf - > next = kbuf - > index + length ;
return type_len ;
}
/**
* kbuffer_translate_data - read raw data to get a record
* @ swap : Set to 1 if bytes in words need to be swapped when read
* @ data : The raw data to read
* @ size : Address to store the size of the event data .
*
* Returns a pointer to the event data . To determine the entire
* record size ( record metadata + data ) just add the difference between
* @ data and the returned value to @ size .
*/
void * kbuffer_translate_data ( int swap , void * data , unsigned int * size )
{
unsigned long long delta ;
struct kbuffer kbuf ;
int type_len ;
int length ;
void * ptr ;
if ( swap ) {
kbuf . read_8 = __read_8_sw ;
kbuf . read_4 = __read_4_sw ;
kbuf . flags = host_is_bigendian ( ) ? 0 : KBUFFER_FL_BIG_ENDIAN ;
} else {
kbuf . read_8 = __read_8 ;
kbuf . read_4 = __read_4 ;
kbuf . flags = host_is_bigendian ( ) ? KBUFFER_FL_BIG_ENDIAN : 0 ;
}
type_len = translate_data ( & kbuf , data , & ptr , & delta , & length ) ;
switch ( type_len ) {
case KBUFFER_TYPE_PADDING :
case KBUFFER_TYPE_TIME_EXTEND :
case KBUFFER_TYPE_TIME_STAMP :
return NULL ;
} ;
* size = length ;
return ptr ;
}
static int __next_event ( struct kbuffer * kbuf )
{
int type ;
do {
kbuf - > curr = kbuf - > next ;
if ( kbuf - > next > = kbuf - > size )
return - 1 ;
type = update_pointers ( kbuf ) ;
} while ( type = = KBUFFER_TYPE_TIME_EXTEND | | type = = KBUFFER_TYPE_PADDING ) ;
return 0 ;
}
static int next_event ( struct kbuffer * kbuf )
{
return kbuf - > next_event ( kbuf ) ;
}
/**
* kbuffer_next_event - increment the current pointer
* @ kbuf : The kbuffer to read
* @ ts : Address to store the next record ' s timestamp ( may be NULL to ignore )
*
* Increments the pointers into the subbuffer of the kbuffer to point to the
* next event so that the next kbuffer_read_event ( ) will return a
* new event .
*
* Returns the data of the next event if a new event exists on the subbuffer ,
* NULL otherwise .
*/
void * kbuffer_next_event ( struct kbuffer * kbuf , unsigned long long * ts )
{
int ret ;
if ( ! kbuf | | ! kbuf - > subbuffer )
return NULL ;
ret = next_event ( kbuf ) ;
if ( ret < 0 )
return NULL ;
if ( ts )
* ts = kbuf - > timestamp ;
return kbuf - > data + kbuf - > index ;
}
/**
* kbuffer_load_subbuffer - load a new subbuffer into the kbuffer
* @ kbuf : The kbuffer to load
* @ subbuffer : The subbuffer to load into @ kbuf .
*
* Load a new subbuffer ( page ) into @ kbuf . This will reset all
* the pointers and update the @ kbuf timestamp . The next read will
* return the first event on @ subbuffer .
*
* Returns 0 on succes , - 1 otherwise .
*/
int kbuffer_load_subbuffer ( struct kbuffer * kbuf , void * subbuffer )
{
unsigned long long flags ;
void * ptr = subbuffer ;
if ( ! kbuf | | ! subbuffer )
return - 1 ;
kbuf - > subbuffer = subbuffer ;
kbuf - > timestamp = read_8 ( kbuf , ptr ) ;
ptr + = 8 ;
kbuf - > curr = 0 ;
if ( kbuf - > flags & KBUFFER_FL_LONG_8 )
kbuf - > start = 16 ;
else
kbuf - > start = 12 ;
kbuf - > data = subbuffer + kbuf - > start ;
flags = read_long ( kbuf , ptr ) ;
kbuf - > size = ( unsigned int ) flags & COMMIT_MASK ;
if ( flags & MISSING_EVENTS ) {
if ( flags & MISSING_STORED ) {
ptr = kbuf - > data + kbuf - > size ;
kbuf - > lost_events = read_long ( kbuf , ptr ) ;
} else
kbuf - > lost_events = - 1 ;
} else
kbuf - > lost_events = 0 ;
kbuf - > index = 0 ;
kbuf - > next = 0 ;
next_event ( kbuf ) ;
return 0 ;
}
/**
* kbuffer_read_event - read the next event in the kbuffer subbuffer
* @ kbuf : The kbuffer to read from
* @ ts : The address to store the timestamp of the event ( may be NULL to ignore )
*
* Returns a pointer to the data part of the current event .
* NULL if no event is left on the subbuffer .
*/
void * kbuffer_read_event ( struct kbuffer * kbuf , unsigned long long * ts )
{
if ( ! kbuf | | ! kbuf - > subbuffer )
return NULL ;
if ( kbuf - > curr > = kbuf - > size )
return NULL ;
if ( ts )
* ts = kbuf - > timestamp ;
return kbuf - > data + kbuf - > index ;
}
/**
* kbuffer_timestamp - Return the timestamp of the current event
* @ kbuf : The kbuffer to read from
*
* Returns the timestamp of the current ( next ) event .
*/
unsigned long long kbuffer_timestamp ( struct kbuffer * kbuf )
{
return kbuf - > timestamp ;
}
/**
* kbuffer_read_at_offset - read the event that is at offset
* @ kbuf : The kbuffer to read from
* @ offset : The offset into the subbuffer
* @ ts : The address to store the timestamp of the event ( may be NULL to ignore )
*
* The @ offset must be an index from the @ kbuf subbuffer beginning .
* If @ offset is bigger than the stored subbuffer , NULL will be returned .
*
* Returns the data of the record that is at @ offset . Note , @ offset does
* not need to be the start of the record , the offset just needs to be
* in the record ( or beginning of it ) .
*
* Note , the kbuf timestamp and pointers are updated to the
* returned record . That is , kbuffer_read_event ( ) will return the same
* data and timestamp , and kbuffer_next_event ( ) will increment from
* this record .
*/
void * kbuffer_read_at_offset ( struct kbuffer * kbuf , int offset ,
unsigned long long * ts )
{
void * data ;
if ( offset < kbuf - > start )
offset = 0 ;
else
offset - = kbuf - > start ;
/* Reset the buffer */
kbuffer_load_subbuffer ( kbuf , kbuf - > subbuffer ) ;
while ( kbuf - > curr < offset ) {
data = kbuffer_next_event ( kbuf , ts ) ;
if ( ! data )
break ;
}
return data ;
}
/**
* kbuffer_subbuffer_size - the size of the loaded subbuffer
* @ kbuf : The kbuffer to read from
*
* Returns the size of the subbuffer . Note , this size is
* where the last event resides . The stored subbuffer may actually be
* bigger due to padding and such .
*/
int kbuffer_subbuffer_size ( struct kbuffer * kbuf )
{
return kbuf - > size ;
}
/**
* kbuffer_curr_index - Return the index of the record
* @ kbuf : The kbuffer to read from
*
* Returns the index from the start of the data part of
* the subbuffer to the current location . Note this is not
* from the start of the subbuffer . An index of zero will
* point to the first record . Use kbuffer_curr_offset ( ) for
* the actually offset ( that can be used by kbuffer_read_at_offset ( ) )
*/
int kbuffer_curr_index ( struct kbuffer * kbuf )
{
return kbuf - > curr ;
}
/**
* kbuffer_curr_offset - Return the offset of the record
* @ kbuf : The kbuffer to read from
*
* Returns the offset from the start of the subbuffer to the
* current location .
*/
int kbuffer_curr_offset ( struct kbuffer * kbuf )
{
return kbuf - > curr + kbuf - > start ;
}
/**
* kbuffer_event_size - return the size of the event data
* @ kbuf : The kbuffer to read
*
* Returns the size of the event data ( the payload not counting
* the meta data of the record ) of the current event .
*/
int kbuffer_event_size ( struct kbuffer * kbuf )
{
return kbuf - > next - kbuf - > index ;
}
/**
* kbuffer_curr_size - return the size of the entire record
* @ kbuf : The kbuffer to read
*
* Returns the size of the entire record ( meta data and payload )
* of the current event .
*/
int kbuffer_curr_size ( struct kbuffer * kbuf )
{
return kbuf - > next - kbuf - > curr ;
}
/**
* kbuffer_missed_events - return the # of missed events from last event .
* @ kbuf : The kbuffer to read from
*
* Returns the # of missed events ( if recorded ) before the current
* event . Note , only events on the beginning of a subbuffer can
* have missed events , all other events within the buffer will be
* zero .
*/
int kbuffer_missed_events ( struct kbuffer * kbuf )
{
/* Only the first event can have missed events */
if ( kbuf - > curr )
return 0 ;
return kbuf - > lost_events ;
}
/**
* kbuffer_set_old_forma - set the kbuffer to use the old format parsing
* @ kbuf : The kbuffer to set
*
* This is obsolete ( or should be ) . The first kernels to use the
* new ring buffer had a slightly different ring buffer format
* ( 2.6 .30 and earlier ) . It is still somewhat supported by kbuffer ,
* but should not be counted on in the future .
*/
void kbuffer_set_old_format ( struct kbuffer * kbuf )
{
kbuf - > flags | = KBUFFER_FL_OLD_FORMAT ;
kbuf - > next_event = __old_next_event ;
}
2015-03-24 16:57:56 +03:00
/**
* kbuffer_start_of_data - return offset of where data starts on subbuffer
* @ kbuf : The kbuffer
*
* Returns the location on the subbuffer where the data starts .
*/
int kbuffer_start_of_data ( struct kbuffer * kbuf )
{
return kbuf - > start ;
}