简体   繁体   English

将位字段读取强制为32位

[英]force a bit field read to 32 bits

I am trying to perform a less-than-32bit read over the PCI bus to a VME-bridge chip (Tundra Universe II), which will then go onto the VME bus and picked up by the target. 我试图通过PCI总线读取低于32位的读取到VME桥接芯片(Tundra Universe II),然后将其转移到VME总线并由目标接收。

The target VME application only accepts D32 (a data width read of 32bits) and will ignore anything else. 目标VME应用程序仅接受D32(数据宽度读取为32位)并将忽略其他任何内容。

If I use bit field structure mapped over a VME window (nmap'd into main memory) I CAN read bit fields >24 bits, but anything less fails. 如果我使用在VME窗口上映射的位字段结构(nmap'd到主存储器中),我可以读取> 24位的位字段,但任何不足都会失败。 ie :- 即: -

struct works {
    unsigned int a:24;
};

struct fails {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct main {
    works work;
    fails fail;
}
volatile *reg = function_that_creates_and_maps_the_vme_windows_returns_address()

This shows that the struct works is read as a 32bit, but a read via fails struct of a for eg reg->fail.a is getting factored down to a X bit read. 这表明, 结构工程 ,读为32位,但一读通过了如REG-> fail.a是越来越因素下读取X位失败一个结构。 (where X might be 16 or 8?) (其中X可能是16或8?)

So the questions are : 所以问题是:
a) Where is this scaled down? a)这缩小了哪里? Compiler? 编译器? OS? OS? or the Tundra chip? 还是Tundra芯片?
b) What is the actual size of the read operation performed? b)执行的读操作的实际大小是多少?

I basiclly want to rule out everything but the chip. 我基本上想排除芯片以外的一切。 Documentation on that is on the web, but if it can be proved that the data width requested over the PCI bus is 32bits then the problem can be blamed on the Tundra chip! 关于它的文档是在网上,但如果可以证明通过PCI总线请求的数据宽度是32位,那么问题可归咎于Tundra芯片!

edit:- 编辑:-
Concrete example, code was:- 具体的例子,代码是: -

struct SVersion
{
    unsigned title         : 8;
    unsigned pecversion    : 8;
    unsigned majorversion  : 8;
    unsigned minorversion  : 8;
} Version;

So now I have changed it to this :- 所以现在我把它改成了: -

union UPECVersion
{
    struct SVersion
    {
        unsigned title         : 8;
        unsigned pecversion    : 8;
        unsigned majorversion  : 8;
        unsigned minorversion  : 8;
    } Version;
    unsigned int dummy;
};

And the base main struct :- 和基本主要结构: -

typedef struct SEPUMap
{
    ...
    ...
    UPECVersion PECVersion;

};

So I still have to change all my baseline code 所以我仍然需要更改所有基线代码

// perform dummy 32bit read
pEpuMap->PECVersion.dummy;

// get the bits out
x = pEpuMap->PECVersion.Version.minorversion;

And how do I know if the second read wont actually do a real read again, as my original code did? 我怎么知道第二次读取是否真的再次真正读取,就像我的原始代码那样? (Instead of using the already read bits via the union!) (而不是通过联合使用已读取的位!)

Your compiler is adjusting the size of your struct to a multiple of its memory alignment setting. 您的编译器正在将结构的大小调整为其内存对齐设置的倍数。 Almost all modern compilers do this. 几乎所有现代编译器都这样做。 On some processors, variables and instructions have to begin on memory addresses that are multiples of some memory alignment value (often 32-bits or 64-bits, but the alignment depends on the processor architecture). 在某些处理器上,变量和指令必须从内存地址开始,这些内存地址是某些内存对齐值的倍数(通常为32位或64位,但对齐取决于处理器体系结构)。 Most modern processors don't require memory alignment anymore - but almost all of them see substantial performance benefit from it. 大多数现代处理器不再需要内存对齐 - 但几乎所有处理器都看到了大量的性能优势。 So the compilers align your data for you for the performance boost. 因此,编译器会为您的数据调整以提高性能。

However, in many cases (such as yours) this isn't the behavior you want. 但是,在许多情况下(例如你的),这不是你想要的行为。 The size of your structure, for various reasons, can turn out to be extremely important. 由于各种原因,您的结构大小可能变得非常重要。 In those cases, there are various ways around the problem. 在这些情况下,有各种方法解决问题。

One option is to force the compiler to use different alignment settings. 一种选择是强制编译器使用不同的对齐设置。 The options for doing this vary from compiler to compiler, so you'll have to check your documentation. 执行此操作的选项因编译器而异,因此您必须检查文档。 It's usually a #pragma of some sort. 它通常是某种#pragma。 On some compilers (the Microsoft compilers, for instance) it's possible to change the memory alignment for only a very small section of code. 在某些编译器(例如Microsoft编译器)上,可以仅为非常小的代码段更改内存对齐。 For example (in VC++): 例如(在VC ++中):

#pragma pack(push)      // save the current alignment
#pragma pack(1)         // set the alignment to one byte
// Define variables that are alignment sensitive
#pragma pack(pop)       // restore the alignment

Another option is to define your variables in other ways. 另一种选择是以其他方式定义变量。 Intrinsic types are not resized based on alignment, so instead of your 24-bit bitfield, another approach is to define your variable as an array of bytes. 内部类型不会根据对齐进行调整,因此,不是24位位域,而是另一种方法是将变量定义为字节数组。

Finally, you can just let the compilers make the structs whatever size they want and manually record the size that you need to read/write. 最后,您可以让编译器根据需要制作结构,并手动记录您需要读/写的大小。 As long as you're not concatenating structures together, this should work fine. 只要你没有将结构连接在一起,这应该可以正常工作。 Remember, however, that the compiler is giving you padded structs under the hood, so if you make a larger struct that includes, say, a works and a fails struct, there will be padded bits in between them that could cause you problems. 但是请记住,编译器会在底层给你填充结构,所以如果你创建一个更大的结构,包括一个作品和一个失败的结构,它们之间会有填充位,这可能会导致你的问题。

On most compilers, it's going to be darn near impossible to create a data type smaller than 8 bits. 在大多数编译器中,创建小于8位的数据类型几乎是不可能的。 Most architectures just don't think that way. 大多数架构都不这么认为。 This shouldn't be a huge problem because most hardware devices that use datatypes of smaller than 8-bits end up arranging their packets in such a way that they still come in 8-bit multiples, so you can do the bit manipulations to extract or encode the values on the data stream as it leaves or comes in. 这应该不是一个大问题,因为大多数使用小于8位数据类型的硬件设备最终会以这样的方式安排它们的数据包,它们仍然是8位多路复用,所以你可以进行位操作来提取或在数据流离开或进入时对其进行编码。

For all of the reasons listed above, a lot of code that works with hardware devices like this work with raw byte arrays and just encode the data within the arrays. 由于上面列出的所有原因,许多适用于此类硬件设备的代码都使用原始字节数组,只是对数组中的数据进行编码。 Despite losing a lot of the conveniences of modern language constructs, it ends up just being easier. 尽管失去了现代语言结构的许多便利,但它最终变得更容易。

I am wondering about the value of sizeof(struct fails) . 我想知道sizeof(struct fails)的值sizeof(struct fails) Is it 1? 是1吗? In this case, if you perform the read by dereferencing a pointer to a struct fails , it looks correct to issue a D8 read on the VME bus. 在这种情况下,如果通过取消引用指向struct fails的指针来执行读取struct fails ,则在VME总线上发出D8读取是正确的。

You can try to add a field unsigned int unused:29; 您可以尝试添加unsigned int unused:29;字段unsigned int unused:29; to your struct fails . 你的struct fails

The size of a struct is not equal to the sum of the size of its fields, including bit fields. struct的大小不等于其字段大小的总和,包括位字段。 Compilers are allowed, by the C and C++ language specifications, to insert padding between fields in a struct . 根据C和C ++语言规范,编译器允许在struct字段之间插入填充。 Padding is often inserted for alignment purposes. 通常插入填充以用于对齐目的。

The common method in embedded systems programming is to read the data as an unsigned integer then use bit masking to retrieve the interesting bits. 嵌入式系统编程中的常用方法是将数据读取为无符号整数,然后使用位屏蔽来检索有趣的位。 This is due to the above rule that I stated and the fact that there is no standard compiler parameter for "packing" fields in a structure. 这是由于我所述的上述规则以及结构中“打包”字段没有标准编译器参数的事实。

I suggest creating an object ( class or struct ) for interfacing with the hardware. 我建议创建一个对象( classstruct )来与硬件连接。 Let the object read the data, then extract the bits as bool members. 让对象读取数据,然后将这些位提取为bool成员。 This puts the implementation as close to the hardware. 这使得实现尽可能靠近硬件。 The remaining software should not care how the bits are implemented. 剩下的软件不应该关心这些位是如何实现的。

When defining bit field positions / named constants, I suggest this format: 在定义位字段位置/命名常量时,我​​建议使用以下格式:

#define VALUE (1 << BIT POSITION )
// OR
const unsigned int VALUE = 1 << BIT POSITION ;

This format is more readable and has the compiler perform the arithmetic. 此格式更具可读性,并且编译器执行算法。 The calculation takes place during compilation and has no impact during run-time. 计算在编译期间进行,并且在运行期间没有影响。

Ian - 如果你想确定你正在读/写的东西的大小,我建议你不要使用这样的结构来实现它 - 失败结构的sizeof可能只有1个字节 - 编译器是免费的根据优化等来决定它应该是什么 - 我建议使用int来显式读/写,或者通常你需要确保大小的东西,然后做其他事情,比如转换到你没有的union / struct那些限制。

As an example, the Linux kernel has inline functions that explicitly handle memory-mapped IO reads and writes. 例如,Linux内核具有内联函数,可以显式处理内存映射的IO读写。 In newer kernels it's a big macro wrapper that boils down to an inline assembly movl instruction, but it older kernels it was defined like this: 在较新的内核中,它是一个大的宏包装器,归结为内联汇编的movl指令,但它的旧内核定义如下:

#define readl(addr) (*(volatile unsigned int *) (addr))
#define writel(b,addr) ((*(volatile unsigned int *) (addr)) = (b))

It is the compiler that decides what size read to issue. 编译器决定要发出的读取大小。 To force a 32 bit read, you could use a union : 要强制执行32位读取,可以使用union

union dev_word {
    struct dev_reg {
        unsigned int a:1;
        unsigned int b:1;
        unsigned int c:1;
    } fail;
    uint32_t dummy;
};

volatile union dev_word *vme_map_window();

If reading the union through a volatile-qualified pointer isn't enough to force a read of the whole union (I would think it would be - but that could be compiler-dependent), then you could use a function to provide the required indirection: 如果通过volatile限定指针读取联合并不足以强制读取整个联合(我认为它会是 - 但这可能是编译器相关的),那么你可以使用一个函数来提供所需的间接:

volatile union dev_word *real_reg; /* Initialised with vme_map_window() */

union dev_word * const *reg_func(void)
{
    static union dev_word local_copy;
    static union dev_word * const static_ptr = &local_copy;

    local_copy = *real_reg;
    return &static_ptr;
}

#define reg (*reg_func())

...then (for compatibility with the existing code) your accesses are done as: ...然后(为了与现有代码兼容)您的访问完成如下:

reg->fail.a

The method described earlier of using the gcc flag -fstrict-volatile-bitfields and defining bitfield variables as volatile u32 works, but the total number of bits defined must be greater than 16. 之前描述的使用gcc标志-fstrict-volatile-bitfields并将位域变量定义为易失性u32的方法有效,但定义的总位数必须大于16。

For example: 例如:

typedef     union{
    vu32    Word;
    struct{
        vu32    LATENCY     :3;
        vu32    HLFCYA      :1;
        vu32    PRFTBE      :1;
        vu32    PRFTBS      :1;  
    };
}tFlashACR;
.
tFLASH* const pFLASH    =   (tFLASH*)FLASH_BASE;
#define FLASH_LATENCY       pFLASH->ACR.LATENCY
.
FLASH_LATENCY = Latency;

causes gcc to generate code 导致gcc生成代码

.
ldrb r1, [r3, #0]
.

which is a byte read. 这是一个字节读取。 However, changing the typedef to 但是,将typedef更改为

typedef     union{
    vu32    Word;
    struct{
        vu32    LATENCY     :3;
        vu32    HLFCYA      :1;
        vu32    PRFTBE      :1;
        vu32    PRFTBS      :1;
        vu32                :2;

        vu32    DUMMY1      :8;

        vu32    DUMMY2      :8;
    };
}tFlashACR;

changes the resultant code to 将结果代码更改为

.
ldr r3, [r2, #0]
.

I believe the only solution is to 我相信唯一的解决方案是
1) edit/create my main struct as all 32bit ints (unsigned longs) 1)编辑/创建我的主结构作为所有32位整数(无符号长整数)
2) keep my original bit-field structs 2)保留我原来的位域结构
3) each access I require, 3)我需要的每次访问,
3.1) I have to read the struct member as a 32bit word, and cast it into the bit-field struct, 3.1)我必须将结构成员读取为32位字,并将其转换为位字段结构,
3.2) read the bit-field element I require. 3.2)读取我需要的位域元素。 (and for writes, set this bit-field, and write the word back!) (对于写入,设置此位字段,然后将字写回!)

(1) Which is a same, because then I lose the intrinsic types that each member of the "main/SEPUMap" struct are. (1)哪一个是相同的,因为那时我失去了“main / SEPUMap”结构的每个成员所固有的类型。

End solution :- 最终解决方案: -
Instead of :- 代替 :-

printf("FirmwareVersionMinor: 0x%x\n", pEpuMap->PECVersion);

This :- 这个 :-

SPECVersion ver = *(SPECVersion*)&pEpuMap->PECVersion;

printf("FirmwareVersionMinor: 0x%x\n", ver.minorversion);

Only problem I have is writting! 我唯一的问题就是写作! (Writes are now Read/Modify/Writes!) (写入现在是读/修改/写入!)

// Read - Get current
_HVPSUControl temp = *(_HVPSUControl*)&pEpuMap->HVPSUControl;

// Modify - set to new value
temp.OperationalRequestPort = true;

// Write
volatile unsigned int *addr = reinterpret_cast<volatile unsigned int*>(&pEpuMap->HVPSUControl);

*addr = *reinterpret_cast<volatile unsigned int*>(&temp);

Just have to tidy that code up into a method! 只需将代码整理成一个方法!

#define writel(addr, data) ( *(volatile unsigned long*)(&addr) = (*(volatile unsigned long*)(&data)) )

I had same problem on ARM using GCC compiler, where write into memory is only through bytes rather than 32bit word. 我在使用GCC编译器的ARM上遇到了同样的问题,其中写入内存只是通过字节而不是32位字。

The solution is to define bit-fields using volatile uint32_t (or required size to write): 解决方案是使用volatile uint32_t (或写入所需的大小)来定义位域:

union {
    volatile uint32_t XY;
    struct {
        volatile uint32_t XY_A : 4;
        volatile uint32_t XY_B : 12;
    };
};

but while compiling you need add to gcc or g++ this parameter: 但是在编译时你需要添加到gcc或g ++这个参数:

-fstrict-volatile-bitfields

more in gcc documentation. 更多在gcc文档中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM