2

我正在尝试通过 PCI 总线对 VME 桥芯片(Tundra Universe II)执行小于 32 位的读取,然后该芯片将进入 VME 总线并被目标拾取。

目标 VME 应用程序仅接受 D32(32 位的数据宽度读取)并且将忽略其他任何内容。

如果我使用映射到 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()

这表明结构工作被读取为 32 位,但是通过失败结构的读取例如reg->fail.a被分解为 X 位读取。(X 可能是 16 还是 8?)

所以问题是:
a)这在哪里缩小?编译器?操作系统?还是 Tundra 芯片?
b) 执行的读取操作的实际大小是多少?

我基本上想排除除芯片之外的所有内容。这方面的文档在网上,但如果可以证明通过 PCI 总线请求的数据宽度是 32 位,那么问题可以归咎于 Tundra 芯片!

编辑:-
具体示例,代码为:-

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

所以现在我把它改成了这样:-

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

和基本的主要结构:-

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

};

所以我仍然需要更改我所有的基线代码

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

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

我怎么知道第二次阅读是否真的不会像我的原始代码那样再次进行真正的阅读?(而不是通过联合使用已经读取的位!)

4

9 回答 9

6

您的编译器正在将结构的大小调整为其内存对齐设置的倍数。几乎所有现代编译器都这样做。在某些处理器上,变量和指令必须从某个内存对齐值(通常是 32 位或 64 位,但对齐取决于处理器架构)的倍数的内存地址开始。大多数现代处理器不再需要内存对齐 - 但几乎所有处理器都看到了显着的性能优势。因此,编译器会为您调整数据以提高性能。

但是,在许多情况下(例如您的情况),这不是您想要的行为。由于各种原因,您的结构的大小可能变得非常重要。在这些情况下,有多种方法可以解决问题。

一种选择是强制编译器使用不同的对齐设置。执行此操作的选项因编译器而异,因此您必须检查文档。它通常是某种#pragma。在某些编译器(例如 Microsoft 编译器)上,可以仅更改一小部分代码的内存对齐方式。例如(在 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

另一种选择是以其他方式定义变量。内在类型不会根据对齐方式调整大小,因此另一种方法是将变量定义为字节数组,而不是 24 位位域。

最后,您可以让编译器将结构设置为他们想要的任何大小,并手动记录您需要读/写的大小。只要您不将结构连接在一起,这应该可以正常工作。但是请记住,编译器在后台为您提供了填充结构,因此如果您创建一个更大的结构,其中包括一个工作结构和一个失败结构,它们之间将会有填充位,这可能会导致您出现问题。

在大多数编译器上,几乎不可能创建小于 8 位的数据类型。大多数架构不这么认为。这应该不是一个大问题,因为大多数使用小于 8 位数据类型的硬件设备最终会以这样一种方式排列它们的数据包,即它们仍然以 8 位倍数出现,因此您可以进行位操作以提取或在数据流离开或进入时对数据流上的值进行编码。

由于上面列出的所有原因,许多适用于此类硬件设备的代码都适用于原始字节数组,并且只是对数组中的数据进行编码。尽管失去了现代语言结构的许多便利,但它最终变得更容易了。

于 2009-12-04T15:10:28.447 回答
2

我想知道sizeof(struct fails). 是1吗?在这种情况下,如果您通过取消引用指向 a 的指针来执行读取struct fails,则在 VME 总线上发出 D8 读取看起来是正确的。

您可以尝试将字段添加unsigned int unused:29;到您的struct fails.

于 2009-12-04T13:42:08.360 回答
2

astruct的大小不等于其字段大小的总和,包括位字段。 C 和 C++ 语言规范允许编译器在struct. 填充通常用于对齐目的。

嵌入式系统编程中的常用方法是将数据读取为无符号整数,然后使用位掩码来检索感兴趣的位。这是由于我所说的上述规则以及结构中“打包”字段没有标准编译器参数的事实。

我建议创建一个对象( classstruct)来与硬件交互。让对象读取数据,然后提取位作为bool成员。这使得实现尽可能接近硬件。其余软件不应该关心这些位是如何实现的

在定义位域位置/命名常量时,我​​建议采用这种格式:

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

这种格式更具可读性,并且让编译器执行算术运算。计算在编译期间进行,在运行时没有影响。

于 2009-12-04T22:19:42.537 回答
1

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

于 2009-12-04T13:42:50.883 回答
1

例如,Linux 内核具有显式处理内存映射 IO 读取和写入的内联函数。在较新的内核中,它是一个大的宏包装器,归结为内联汇编movl指令,但在较旧的内核中,它的定义如下:

#define readl(addr) (*(volatile unsigned int *) (addr))
#define writel(b,addr) ((*(volatile unsigned int *) (addr)) = (b))
于 2009-12-04T17:02:05.083 回答
1

编译器决定了要发出的读取大小。要强制进行 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();

如果通过 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())

...然后(为了与现有代码兼容)您的访问完成如下:

reg->fail.a
于 2009-12-05T02:10:15.223 回答
1

前面描述的使用 gcc 标志 -fstrict-volatile-bitfields 并将位域变量定义为 volatile u32 的方法有效,但定义的总位数必须大于 16。

例如:

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;

导致 gcc 生成代码

.
ldrb r1, [r3, #0]
.

这是一个字节读取。但是,将 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;

将结果代码更改为

.
ldr r3, [r2, #0]
.
于 2015-03-11T09:52:16.420 回答
0

我相信唯一的解决方案是
1)编辑/创建我的主要结构作为所有 32 位整数(无符号长整数)
2)保留我的原始位字段结构
3)我需要的每次访问,
3.1)我必须将结构成员作为32位字,并将其转换为位域结构,
3.2)读取我需要的位域元素。(对于写操作,设置这个位域,然后写回这个字!)

(1) 这是相同的,因为这样我就失去了“main/SEPUMap”结构的每个成员的内在类型。

最终解决方案:-
而不是:-

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

这 :-

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

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

我唯一的问题就是写作!(写入现在是读取/修改/写入!)

// 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);

只需将该代码整理成一个方法!

#define writel(addr, data) ( *(volatile unsigned long*)(&addr) = (*(volatile unsigned long*)(&data)) )
于 2009-12-07T13:21:36.670 回答
0

我在使用 GCC 编译器的 ARM 上遇到了同样的问题,其中写入内存仅通过字节而不是 32 位字。

解决方案是使用volatile uint32_t(或写入所需的大小)定义位域:

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

但是在编译时你需要添加到 gcc 或 g++ 这个参数:

-fstrict-volatile-bitfields

更多在 gcc 文档中。

于 2014-09-01T20:51:47.080 回答