Question

Je suis en train d'effectuer une moins-que-32bit lire sur le bus PCI à une puce VME-pont (Tundra Universe II), qui sera ensuite aller sur le bus VME et repris par la cible.

L'application cible accepte uniquement D32 VME (une largeur de lecture de données de 32 bits) et ignore tout le reste.

Si j'utilise la structure de champ de bits mappée sur une fenêtre VME (nmap'd dans la mémoire principale) Je peux lire des champs de bits> 24 bits, mais rien de moins échoue. à savoir: -

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()

Cela montre que le struct fonctionne est lu comme 32 bits, mais lu par échoue struct de a pour exemple reg -> fail.a se factorisation vers le bas à un bit X lu. (Où X pourrait être 16 ou 8?)

Ainsi, les questions sont les suivantes:
a) Où est cette mise à l'échelle vers le bas? Compilateur? OS? ou la puce Tundra?
b) Quelle est la taille réelle de l'opération de lecture effectuée?

Je veux basiclly à exclure tout sauf la puce. Documentation sur qui est sur le web, mais si l'on peut prouver que la largeur des données demandées sur le bus PCI est 32bits alors le problème peut être attribué à la puce Tundra!

modifier: -
Exemple concret, le code est: -

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

Alors maintenant, je l'ai changé à ceci: -

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

Et la struct principale de base: -

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

};

Je dois encore changer tout mon code de référence

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

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

Et comment puis-je savoir si la deuxième lecture wont réellement faire une vraie lecture à nouveau, comme mon code d'origine a fait? (Au lieu d'utiliser les lire déjà des bits par l'union!)

Était-ce utile?

La solution

À titre d'exemple, le noyau Linux a des fonctions inline qui gèrent explicitement lit et écrit IO cartographié mémoire. Dans les noyaux plus récents, il est un grand emballage macro qui se résume à un ensemble de ligne instruction movl, mais les noyaux plus anciens, il a été défini comme ceci:

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

Autres conseils

Votre compilateur ajuste la taille de votre struct à un multiple de son réglage d'alignement de la mémoire. Presque tous les compilateurs modernes font. Sur certains processeurs, des variables et des instructions Vous pour commencer sur les adresses de mémoire qui sont des multiples d'une certaine valeur d'alignement de mémoire (souvent 32 bits ou 64 bits, mais l'alignement dépend de l'architecture du processeur). La plupart des processeurs modernes ne le font pas besoin alignement de mémoire plus - mais presque tous les voir substantielle bénéfice de la performance de celui-ci. Ainsi, les compilateurs alignent vos données pour vous pour l'amélioration des performances.

Cependant, dans de nombreux cas (comme le vôtre) ce n'est pas le comportement que vous voulez. La taille de votre structure, pour diverses raisons, peut se révéler extrêmement important. Dans ces cas, il existe plusieurs façons de contourner le problème.

L'une des options est de forcer le compilateur à utiliser différents paramètres d'alignement. Les options pour ce faire varient d'un compilateur de compilateur, de sorte que vous devrez vérifier votre documentation. Il est généralement un #pragma de quelque sorte. Sur certains compilateurs (les compilateurs Microsoft, par exemple), il est possible de modifier l'alignement de la mémoire pour seulement une très petite section de code. Par exemple (en 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

Une autre option consiste à définir vos variables d'autres façons. types Intrinsic ne sont pas redimensionnées en fonction de l'alignement, donc au lieu de votre bitfield 24 bits, une autre approche est de définir votre variable comme un tableau d'octets.

Enfin, vous pouvez simplement laisser les compilateurs faire les struct quelle que soit la taille qu'ils veulent et enregistrer manuellement la taille que vous devez lecture / écriture. Tant que vous n'êtes pas concaténer structures ensemble, cela fonctionne bien. Rappelez-vous, cependant, que le compilateur vous donne struct rembourrées sous le capot, donc si vous faites une struct plus large qui comprend, par exemple, fonctionne et échoue struct, il seront morceaux rembourrés entre eux qui pourraient vous causer des problèmes.

Sur la plupart des compilateurs, il va être sacrément près impossible de créer un type de données plus petit que 8 bits. La plupart des architectures ne pensent pas de cette façon. Cela ne devrait pas être un énorme problème parce que la plupart des périphériques qui utilisent des types de données de plus petit que 8 bits finissent par arranger leurs paquets de telle sorte qu'ils viennent toujours en multiples de 8 bits, de sorte que vous pouvez faire les manipulations de bits pour extraire ou coder les valeurs sur le flux de données qu'il quitte ou entre en jeu.

Pour toutes les raisons énumérées ci-dessus, beaucoup de code qui fonctionne avec des périphériques matériels comme ce travail avec des tableaux d'octets brutes et juste coder les données contenues dans les tableaux. Malgré la perte d'un grand nombre de commodités de constructions linguistiques modernes, il finit par être juste plus facile.

Je me demande au sujet de la valeur de sizeof(struct fails). Est-ce 1? Dans ce cas, si vous effectuez la lecture par le déréférencement d'un pointeur à un struct fails, il semble correct d'émettre un J8 lu sur le bus VME.

Vous pouvez essayer d'ajouter un unsigned int unused:29; champ à votre struct fails.

La taille d'un struct est pas égal à la somme de la taille de ses domaines, y compris les champs de bits. compilateurs sont autorisées par le C et C ++ caractéristiques linguistiques, à insérer un rembourrage entre les champs dans un struct. Padding est souvent insérée à des fins d'alignement.

La méthode courante dans la programmation des systèmes embarqués est de lire les données en tant que entier non signé utiliser ensuite bit de masquage pour extraire les bits intéressants. Cela est dû à la règle ci-dessus que je l'ai dit et le fait qu'il n'y a pas de paramètre compilateur standard pour les champs « d'emballage » dans une structure.

Je suggère la création d'un objet (ou class struct) pour l'interfaçage avec le matériel. Laissez l'objet lire les données, puis extraire les bits en tant que membres de bool. Cela met la mise en œuvre au plus près du matériel. Le reste du logiciel ne doit pas prendre soin comment les bits sont mis en œuvre.

Lors de la définition des positions de champ de bits / constantes nommées, je suggère ce format:

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

Ce format est plus lisible et a le compilateur effectuer l'arithmétique. Le calcul a lieu lors de la compilation et n'a pas d'impact au cours de l'exécution.

Ian - si vous voulez être sûr que la taille des choses que vous êtes lecture / écriture, je vous suggère de ne pas utiliser struct comme ça pour le faire - il est possible que le sizeof du échoue struct est à seulement 1 octet - la compilateur est libre de décider ce qu'il devrait être fondé sur des optimisations etc- je vous suggère la lecture / écriture en utilisant explicitement ou INT généralement les choses dont vous avez besoin pour assurer les tailles et ensuite faire quelque chose d'autre que la conversion à une union / struct où vous don « t ont ces limites.

Il est le compilateur qui décide quelle taille lire à émettre. Pour forcer une lecture 32 bits, vous pouvez utiliser un 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();

Si la lecture de l'union par un pointeur volatile qualifié ne suffit pas de forcer une lecture de l'ensemble de l'Union (je pense que ce serait - mais qui pourrait être compilateur dépendant), vous pouvez alors utiliser une fonction pour fournir le indirection nécessaire:

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())

... puis (pour la compatibilité avec le code existant) vos accès sont effectués comme:

reg->fail.a

Le procédé décrit précédemment de l'utilisation de l'indicateur gcc -fstrict-volatile bitfields et définissant des variables de BITFIELD comme u32 fonctionne volatile, mais le nombre total de bits définies doit être supérieur à 16.

Par exemple:

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;

provoque gcc pour générer le code

.
ldrb r1, [r3, #0]
.

qui est un octet lu. Cependant, la modification du 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;

modifie le code résultant à

.
ldr r3, [r2, #0]
.

Je crois que la seule solution est de
1) modifier / créer mon struct principale que tous ints 32bit (non signé) de désire ardemment
2) garder mes struct originaux champ de bits
3) chaque accès, je requiers,
3.1) Je dois lire le membre struct comme un mot de 32 bits, et de le jeter dans le struct-champ de bits,
3.2) lire l'élément champ de bits que je demande. (Et pour les écritures, ce bit-champ, et écrire le mot de retour!)

(1) Ce qui est même, parce que je perds les types intrinsèques que chaque membre de la struct sont. "principale / SEPUMap"

solution finale: -
Au lieu de: -

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

: -

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

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

Le seul problème que j'ai est ecrivais! (Rédige sont maintenant Lire / Modifier / écriture!)

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

Suffit de ranger ce code en une méthode!

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

J'ai eu même problème sur ARM en utilisant le compilateur GCC, où écrire en mémoire est que par octets plutôt que mot de 32 bits.

La solution consiste à définir des champs binaires à l'aide de uint32_t volatile (ou de la taille requise pour l'écriture):

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

mais alors que vous devez ajouter à la compilation gcc ou g ++ ce paramètre:

-fstrict-volatile-bitfields

plus dans la documentation gcc.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top