[PATCH] b43-asm: Add 3 new virtual instructions.
francesco.gringoli at ing.unibs.it
francesco.gringoli at ing.unibs.it
Sat Nov 12 12:31:21 EST 2011
Hi Michael,
I added three new (virtual) instructions to make the code more readable.
These are "human-friendly" versions of orx, jzx and jnzx, e.g., one can
use "orxh" like
orxh (r1 << 8) & 0x0100, r2 & ~0x0100, r2
instead of
orx 0, 8, r1, r2, r2
or better write
#define NEED_ACK 0x10
#define NEED_BEACON 0x20
orxh NEED_ACK, r1 & ~(NEED_ACK|NEED_BEACON), r2
instead of
orx 1, 4, 0x1, r1, r2
Similarly:
jzxh (r12 << 16 | r13) & 0x1F000, label
instead of
jzx 4, 12, r13, r12, label
Examples added to test.asm and README.
Regards,
-Francesco
-------------=--------------
The following changes are made to b43-tools
1) b43-asm assembles new virtual instructions orxh, jzxh, jnzxh
Signed-off-by: Francesco Gringoli <francesco.gringoli at ing.unibs.it>
Index: b43-tools/assembler/main.c
===================================================================
--- b43-tools/assembler/main.c 2011-09-12 16:47:26.000000000 +0200
+++ b43-tools/assembler/main.c 2011-11-12 13:39:05.000000000 +0100
@@ -503,6 +503,106 @@
do_assemble_insn(ctx, insn, opcode);
}
+static unsigned int merge_ext_into_opcode_human(struct assembler_context *ctx,
+ unsigned int opbase,
+ struct instruction *insn)
+{
+ struct operlist *ol;
+ unsigned int opcode;
+
+ unsigned long masks[256];
+ unsigned long m, s, j = 0;
+ unsigned long imm, shift, mask, mask2;
+ for(m = 0; m < 16; m++) {
+ mask = (1 << (m+1)) - 1;
+ for(s = 0; s < 16; s++) {
+ mask2 = (mask << s) | (mask >> (16 - s));
+ mask2 = mask2 & 0xFFFF;
+ masks[j] = mask2;
+ j++;
+ }
+ }
+
+ opcode = opbase;
+ ol = insn->operands;
+
+ for(j = 0; j < 256; j++)
+ if(masks[j] == ((~ol->oper[5]->u.raw) & 0xFFFF))
+ break;
+ if(j == 256)
+ asm_error(ctx, "can't build valid mask");
+ m = j / 16; s = j % 16;
+ mask = (1 << (m + 1)) - 1;
+ mask = (mask << s) | (mask >> (16 - s));
+ if(mask != ((~ol->oper[5]->u.raw) & 0xFFFF))
+ asm_error(ctx, "can't match mask");
+
+ if(ol->oper[4]->u.raw != 0)
+ asm_error(ctx, "can't use shift here");
+
+ shift = ol->oper[1]->u.raw;
+ mask = ol->oper[2]->u.raw;
+
+ if(ol->oper[0]->type == OPER_IMM) {
+ imm = ((ol->oper[0]->u.imm->imm << shift) & mask) & 0xFFFF;
+ ol->oper[0]->u.imm->imm = imm;
+ imm = ((imm >> s) | (imm << (16-s))) & 0xFFFF;
+ if(imm & ~0x3FF ||
+ ol->oper[0]->u.imm->imm != (((imm << s) | (imm >> (16-s))) & 0xFFFF))
+ asm_error(ctx, "immediate value can't be encoded");
+ ol->oper[0]->u.imm->imm = imm;
+ } else {
+ if(shift > 15)
+ asm_error(ctx, "invalid shift");
+ if(mask != ((~ol->oper[5]->u.raw) & 0xFFFF))
+ asm_error(ctx, "unmatched masks");
+ }
+
+ opcode |= (m << 4);
+ opcode |= s;
+
+ ol->oper[1] = ol->oper[3];
+ ol->oper[2] = ol->oper[6];
+
+ return opcode;
+}
+
+static unsigned int merge_ext_into_opcode_human2(struct assembler_context *ctx,
+ unsigned int opbase,
+ struct instruction *insn)
+{
+ struct operlist *ol;
+ unsigned long masks[256];
+ unsigned long m, s, j = 0;
+ unsigned long mask;
+ unsigned int opcode = opbase;
+ for(m = 0; m < 16; m++) {
+ mask = (1 << (m+1)) - 1;
+ for(s = 0; s < 16; s++) {
+ masks[j] = (mask << s) & 0xFFFFFFFF;
+ j++;
+ }
+ }
+
+ ol = insn->operands;
+
+ for(j = 0; j < 256; j++)
+ if(ol->oper[2]->u.imm->imm == masks[j])
+ break;
+ if(j == 256)
+ asm_error(ctx, "can't build valid mask");
+ m = j / 16; s = j % 16;
+ mask = (1 << (m + 1)) - 1;
+ mask = (mask << s) & 0xFFFFFFFF;
+ if(mask != ol->oper[2]->u.imm->imm)
+ asm_error(ctx, "can't match mask");
+ opcode |= (m << 4);
+ opcode |= s;
+
+ ol->oper[2] = ol->oper[3];
+ return opcode;
+}
+
static unsigned int merge_ext_into_opcode(struct assembler_context *ctx,
unsigned int opbase,
struct instruction *insn)
@@ -799,6 +899,10 @@
opcode = merge_ext_into_opcode(ctx, 0x300, insn);
do_assemble_insn(ctx, insn, opcode);
break;
+ case OP_ORXH:
+ opcode = merge_ext_into_opcode_human(ctx, 0x300, insn);
+ do_assemble_insn(ctx, insn, opcode);
+ break;
case OP_MOV:
emulate_mov_insn(ctx, insn);
return;
@@ -879,11 +983,21 @@
out = do_assemble_insn(ctx, insn, opcode);
out->is_jump_insn = 1;
break;
+ case OP_JZXH:
+ opcode = merge_ext_into_opcode_human2(ctx, 0x400, insn);
+ out = do_assemble_insn(ctx, insn, opcode);
+ out->is_jump_insn = 1;
+ break;
case OP_JNZX:
opcode = merge_ext_into_opcode(ctx, 0x500, insn);
out = do_assemble_insn(ctx, insn, opcode);
out->is_jump_insn = 1;
break;
+ case OP_JNZXH:
+ opcode = merge_ext_into_opcode_human2(ctx, 0x500, insn);
+ out = do_assemble_insn(ctx, insn, opcode);
+ out->is_jump_insn = 1;
+ break;
case OP_JEXT:
opcode = merge_external_jmp_into_opcode(ctx, 0x700, insn);
out = do_assemble_insn(ctx, insn, opcode);
Index: b43-tools/assembler/main.h
===================================================================
--- b43-tools/assembler/main.h 2011-09-11 19:59:43.000000000 +0200
+++ b43-tools/assembler/main.h 2011-11-10 11:26:46.000000000 +0100
@@ -96,8 +96,14 @@
} u;
};
+struct operand_shift_mask {
+ struct operand *op;
+ unsigned int mask;
+ unsigned int shift;
+};
+
struct operlist {
- struct operand *oper[5];
+ struct operand *oper[7];
};
struct instruction {
Index: b43-tools/assembler/parser.y
===================================================================
--- b43-tools/assembler/parser.y 2011-09-12 16:57:33.000000000 +0200
+++ b43-tools/assembler/parser.y 2011-11-12 16:41:41.000000000 +0100
@@ -43,7 +43,7 @@
%token EQUAL NOT_EQUAL LOGICAL_OR LOGICAL_AND PLUS MINUS MULTIPLY DIVIDE BITW_OR BITW_AND BITW_XOR BITW_NOT LEFTSHIFT RIGHTSHIFT
-%token OP_MUL OP_ADD OP_ADDSC OP_ADDC OP_ADDSCC OP_SUB OP_SUBSC OP_SUBC OP_SUBSCC OP_SRA OP_OR OP_AND OP_XOR OP_SR OP_SRX OP_SL OP_RL OP_RR OP_NAND OP_ORX OP_MOV OP_JMP OP_JAND OP_JNAND OP_JS OP_JNS OP_JE OP_JNE OP_JLS OP_JGES OP_JGS OP_JLES OP_JL OP_JGE OP_JG OP_JLE OP_JZX OP_JNZX OP_JEXT OP_JNEXT OP_JDN OP_JDPZ OP_JDP OP_JDNZ OP_CALL OP_CALLS OP_RET OP_RETS OP_TKIPH OP_TKIPHS OP_TKIPL OP_TKIPLS OP_NAP RAW_CODE
+%token OP_MUL OP_ADD OP_ADDSC OP_ADDC OP_ADDSCC OP_SUB OP_SUBSC OP_SUBC OP_SUBSCC OP_SRA OP_OR OP_AND OP_XOR OP_SR OP_SRX OP_SL OP_RL OP_RR OP_NAND OP_ORX OP_ORXH OP_MOV OP_JMP OP_JAND OP_JNAND OP_JS OP_JNS OP_JE OP_JNE OP_JLS OP_JGES OP_JGS OP_JLES OP_JL OP_JGE OP_JG OP_JLE OP_JZX OP_JZXH OP_JNZX OP_JNZXH OP_JEXT OP_JNEXT OP_JDN OP_JDPZ OP_JDP OP_JDNZ OP_CALL OP_CALLS OP_RET OP_RETS OP_TKIPH OP_TKIPHS OP_TKIPL OP_TKIPLS OP_NAP RAW_CODE
%token IVAL_MMIO16 IVAL_MMIO32 IVAL_PHY IVAL_RADIO IVAL_SHM16 IVAL_SHM32 IVAL_TRAM
@@ -316,6 +316,13 @@
s->u.insn = $1;
$$ = s;
}
+ | insn_orxh {
+ struct statement *s = xmalloc(sizeof(struct statement));
+ INIT_LIST_HEAD(&s->list);
+ s->type = STMT_INSN;
+ s->u.insn = $1;
+ $$ = s;
+ }
| insn_mov {
struct statement *s = xmalloc(sizeof(struct statement));
INIT_LIST_HEAD(&s->list);
@@ -463,6 +470,13 @@
s->u.insn = $1;
$$ = s;
}
+ | insn_jzxh {
+ struct statement *s = xmalloc(sizeof(struct statement));
+ INIT_LIST_HEAD(&s->list);
+ s->type = STMT_INSN;
+ s->u.insn = $1;
+ $$ = s;
+ }
| insn_jnzx {
struct statement *s = xmalloc(sizeof(struct statement));
INIT_LIST_HEAD(&s->list);
@@ -470,6 +484,13 @@
s->u.insn = $1;
$$ = s;
}
+ | insn_jnzxh {
+ struct statement *s = xmalloc(sizeof(struct statement));
+ INIT_LIST_HEAD(&s->list);
+ s->type = STMT_INSN;
+ s->u.insn = $1;
+ $$ = s;
+ }
| insn_jext {
struct statement *s = xmalloc(sizeof(struct statement));
INIT_LIST_HEAD(&s->list);
@@ -795,6 +816,14 @@
}
;
+insn_orxh : OP_ORXH operlist_3_human {
+ struct instruction *insn = xmalloc(sizeof(struct instruction));
+ insn->op = OP_ORXH;
+ insn->operands = $2;
+ $$ = insn;
+ }
+ ;
+
insn_mov : OP_MOV operlist_2 {
struct instruction *insn = xmalloc(sizeof(struct instruction));
insn->op = OP_MOV;
@@ -933,6 +962,14 @@
}
;
+insn_jzxh : OP_JZXH operlist_2_jump_human {
+ struct instruction *insn = xmalloc(sizeof(struct instruction));
+ insn->op = OP_JZXH;
+ insn->operands = $2;
+ $$ = insn;
+ }
+ ;
+
insn_jnzx : OP_JNZX extended_operlist {
struct instruction *insn = xmalloc(sizeof(struct instruction));
insn->op = OP_JNZX;
@@ -941,6 +978,14 @@
}
;
+insn_jnzxh : OP_JNZXH operlist_2_jump_human {
+ struct instruction *insn = xmalloc(sizeof(struct instruction));
+ insn->op = OP_JNZXH;
+ insn->operands = $2;
+ $$ = insn;
+ }
+ ;
+
insn_jdn : OP_JDN operlist_3 {
struct instruction *insn = xmalloc(sizeof(struct instruction));
insn->op = OP_JDN;
@@ -1217,6 +1262,196 @@
}
;
+operlist_2_jump_human : operand_shift_operand_mask COMMA operand {
+ struct operlist *ol = $1;
+ ol->oper[3] = store_oper_sanity($3);
+ $$ = ol;
+ }
+ ;
+
+/* all the following could be implemented:
+ Y => implemented N => raise error
+ N 0xabcdefgh
+ N 0xabcdefgh & BITMASK
+ Y REG12 & BITMASK
+ Y (REG12 << 16) & BITMASK
+ Y (REG31 << 16 | REG12) & BITMASK
+ N (REG12 << 16| 0xabcd) & BITMASK
+ N (0xabcd0000 | REG12) & BITMASK
+*/
+operand_shift_operand_mask : imm {
+ yyerror("Special jump not yet implemented");
+ }
+ | imm BITW_AND imm {
+ yyerror("Special jump not yet implemented");
+ }
+ | operandh BITW_AND imm {
+ struct operlist *ol = xmalloc(sizeof(struct operlist));
+ ol->oper[0] = $1;
+ ol->oper[1] = xmalloc(sizeof(struct operand));
+ ol->oper[1]->type = OPER_IMM;
+ ol->oper[1]->u.imm = xmalloc(sizeof(struct immediate));
+ ol->oper[1]->u.imm->imm = 0;
+ ol->oper[2] = xmalloc(sizeof(struct operand));
+ ol->oper[2]->type = OPER_IMM;
+ ol->oper[2]->u.imm = $3;
+ $$ = ol;
+ }
+ | PAREN_OPEN operandh LEFTSHIFT imm PAREN_CLOSE BITW_AND imm {
+ struct operlist *ol = xmalloc(sizeof(struct operlist));
+ struct immediate *imm = $4;
+ if(imm->imm != 16)
+ yyerror("Only 16 bit shift allowed here");
+ ol->oper[0] = xmalloc(sizeof(struct operand));
+ ol->oper[0]->type = OPER_IMM;
+ ol->oper[0]->u.imm = xmalloc(sizeof(struct immediate));
+ ol->oper[0]->u.imm->imm = 0;
+ ol->oper[1] = $2;
+ ol->oper[2] = xmalloc(sizeof(struct operand));
+ ol->oper[2]->type = OPER_IMM;
+ ol->oper[2]->u.imm = $7;
+ $$ = ol;
+ }
+ | PAREN_OPEN operandh LEFTSHIFT imm BITW_OR operandh PAREN_CLOSE BITW_AND imm {
+ struct operlist *ol = xmalloc(sizeof(struct operlist));
+ struct immediate *imm = $4;
+ if(imm->imm != 16)
+ yyerror("Only 16 bit shift allowed here");
+ ol->oper[0] = $6;
+ ol->oper[1] = $2;
+ ol->oper[2] = xmalloc(sizeof(struct operand));
+ ol->oper[2]->type = OPER_IMM;
+ ol->oper[2]->u.imm = $9;
+ $$ = ol;
+ }
+ | PAREN_OPEN operandh LEFTSHIFT imm BITW_OR imm PAREN_CLOSE BITW_AND imm {
+ yyerror("Special jump not yet implemented");
+ }
+ | PAREN_OPEN operandh BITW_OR imm PAREN_CLOSE BITW_AND imm {
+ yyerror("Special jump not yet implemented");
+ }
+ ;
+
+operlist_3_human : operand_shift_mask COMMA operand_shift_mask COMMA operand {
+ struct operlist *ol = xmalloc(sizeof(struct operlist));
+ struct operand_shift_mask *xxx = $1;
+ struct operand *shift_xxx = xmalloc(sizeof(struct operand));
+ struct operand *mask_xxx = xmalloc(sizeof(struct operand));
+ struct operand_shift_mask *yyy = $3;
+ struct operand *shift_yyy = xmalloc(sizeof(struct operand));
+ struct operand *mask_yyy = xmalloc(sizeof(struct operand));
+ shift_xxx->type = OPER_RAW;
+ shift_xxx->u.raw = (unsigned long) xxx->shift;
+ mask_xxx->type = OPER_RAW;
+ mask_xxx->u.raw = (unsigned long) xxx->mask;
+ shift_yyy->type = OPER_RAW;
+ shift_yyy->u.raw = (unsigned long) yyy->shift;
+ mask_yyy->type = OPER_RAW;
+ mask_yyy->u.raw = (unsigned long) yyy->mask;
+ ol->oper[0] = xxx->op;
+ ol->oper[1] = shift_xxx;
+ ol->oper[2] = mask_xxx;
+ ol->oper[3] = yyy->op;
+ ol->oper[4] = shift_yyy;
+ ol->oper[5] = mask_yyy;
+ ol->oper[6] = store_oper_sanity($5);
+ free(xxx);
+ free(yyy);
+ $$ = ol;
+ }
+ ;
+
+/* duplicate some complex_imm rules to avoid parentheses
+ The following are implemented:
+ 0x143 || 0x143 & 0x12 || 0x143 & ~0x12
+ REG12 || REG12 & 0x12 || REG12 & ~0x12
+ (REG12 << 0x12) || (REG12 << 0x12) & 0x12 || (REG12 << 0x12) & ~0x12
+*/
+operand_shift_mask : imm {
+ struct operand_shift_mask *oper = xmalloc(sizeof(struct operand_shift_mask));
+ struct operand *oper_imm = xmalloc(sizeof(struct operand));
+ oper_imm->type = OPER_IMM;
+ oper_imm->u.imm = $1;
+ oper->op = oper_imm;
+ oper->mask = 0xFFFF;
+ oper->shift = 0;
+ $$ = oper;
+ }
+ | imm BITW_AND imm {
+ struct operand_shift_mask *oper = xmalloc(sizeof(struct operand_shift_mask));
+ struct operand *oper_imm = xmalloc(sizeof(struct operand));
+ struct immediate *mask_imm = $3;
+ oper_imm->type = OPER_IMM;
+ oper_imm->u.imm = $1;
+ oper->op = oper_imm;
+ oper->shift = 0;
+ oper->mask = mask_imm->imm & 0xFFFF;
+ free(mask_imm);
+ $$ = oper;
+ }
+ | imm BITW_AND BITW_NOT imm {
+ struct operand_shift_mask *oper = xmalloc(sizeof(struct operand_shift_mask));
+ struct operand *oper_imm = xmalloc(sizeof(struct operand));
+ struct immediate *mask_imm = $4;
+ oper_imm->type = OPER_IMM;
+ oper_imm->u.imm = $1;
+ oper->op = oper_imm;
+ oper->shift = 0;
+ oper->mask = (~mask_imm->imm) & 0xFFFF;
+ free(mask_imm);
+ $$ = oper;
+ }
+ | operand_wwo_shift {
+ struct operand_shift_mask *oper = $1;
+ oper->mask = 0xFFFF;
+ $$ = oper;
+ }
+ | operand_wwo_shift BITW_AND imm {
+ struct operand_shift_mask *oper = $1;
+ struct immediate *mask_imm = $3;
+ oper->mask = mask_imm->imm;
+ free(mask_imm);
+ $$ = oper;
+ }
+ | operand_wwo_shift BITW_AND BITW_NOT imm {
+ struct operand_shift_mask *oper = $1;
+ struct immediate *mask_imm = $4;
+ oper->mask = (~mask_imm->imm) & 0xFFFF;
+ free(mask_imm);
+ $$ = oper;
+ }
+ ;
+
+operand_wwo_shift : operandh {
+ struct operand_shift_mask *oper = xmalloc(sizeof(struct operand_shift_mask));
+ oper->op = $1;
+ oper->shift = 0;
+ $$ = oper;
+ }
+ | PAREN_OPEN operandh LEFTSHIFT imm PAREN_CLOSE {
+ struct operand_shift_mask *oper = xmalloc(sizeof(struct operand_shift_mask));
+ struct immediate *shift_imm = $4;
+ oper->op = $2;
+ oper->shift = shift_imm->imm;
+ free(shift_imm);
+ $$ = oper;
+ }
+ ;
+
+operandh : reg {
+ struct operand *oper = xmalloc(sizeof(struct operand));
+ oper->type = OPER_REG;
+ oper->u.reg = $1;
+ $$ = oper;
+ }
+ | mem {
+ struct operand *oper = xmalloc(sizeof(struct operand));
+ oper->type = OPER_MEM;
+ oper->u.mem = $1;
+ $$ = oper;
+ }
+ ;
+
operand : reg {
struct operand *oper = xmalloc(sizeof(struct operand));
oper->type = OPER_REG;
Index: b43-tools/assembler/scanner.l
===================================================================
--- b43-tools/assembler/scanner.l 2011-09-11 19:59:43.000000000 +0200
+++ b43-tools/assembler/scanner.l 2011-11-11 18:15:47.000000000 +0100
@@ -105,6 +105,7 @@
rr { update_lineinfo(); return OP_RR; }
nand { update_lineinfo(); return OP_NAND; }
orx { update_lineinfo(); return OP_ORX; }
+orxh { update_lineinfo(); return OP_ORXH; }
mov { update_lineinfo(); return OP_MOV; }
jmp { update_lineinfo(); return OP_JMP; }
@@ -127,7 +128,9 @@
jdp { update_lineinfo(); return OP_JDP; }
jdnz { update_lineinfo(); return OP_JDNZ; }
jzx { update_lineinfo(); return OP_JZX; }
+jzxh { update_lineinfo(); return OP_JZXH; }
jnzx { update_lineinfo(); return OP_JNZX; }
+jnzxh { update_lineinfo(); return OP_JNZXH; }
jext { update_lineinfo(); return OP_JEXT; }
jnext { update_lineinfo(); return OP_JNEXT; }
Index: b43-tools/assembler/test.asm
===================================================================
--- b43-tools/assembler/test.asm 2011-09-12 17:03:22.000000000 +0200
+++ b43-tools/assembler/test.asm 2011-11-12 17:09:04.000000000 +0100
@@ -72,6 +72,11 @@
orx 7,8,r0,r1,r2 /* eXtended OR */
+ orxh 0x10, r1 & ~0x30, r2 /* r2 = 0x10 | (r1 & ~0x30) */
+ orxh 0, r1 & ~0xFFF0, r2 /* r2 = r1 & ~0xFFF0 */
+ orxh r1 & 0x7FFF, 0 & ~0x7FFF, r2 /* r2 = r1 & 0x7FFF */
+ orxh (r1 << 8) & 0x0100, r2 & ~0x0100, r2 /* r2 = (r1 << 8) & 0x0100 | r2 & ~0x0100 */
+
/* Copy instruction. This is a virtual instruction
* translated to more lowlevel stuff like OR. */
mov r0,r2 /* copy data */
@@ -100,6 +105,13 @@
jzx 7,8,r0,r1,label /* Jump if zero after shift and mask */
jnzx 7,8,r0,r1,label /* Jump if nonzero after shift and mask */
+ jzxh r12 & 0x18, label /* jump if result is zero */
+ jzxh (r12 << 16) & 0x001FFFC0, label /* jump if result is zero */
+ jzxh (r12 << 16 | r13) & 0x1F000, label /* jump if result is zero */
+ jnzxh r12 & 0x18, label /* jump if result is non zero */
+ jnzxh (r12 << 16) & 0x001FFFC0, label /* jump if result is non zero */
+ jnzxh (r12 << 16 | r13) & 0x1F000, label /* jump if result is non zero */
+
/* jump on external conditions */
jext ECOND_MAC_ON,label /* jump if external condition is TRUE */
jnext ECOND_MAC_ON,label /* jump if external condition is FALSE */
--- b43-tools/assembler/README 2011-11-12 17:53:50.000000000 +0100
+++ b43-tools/assembler/README 2011-11-12 17:53:14.000000000 +0100
@@ -43,7 +43,9 @@
ret | lrX,lrY | Store PC, ret from func | lrX=PC; PC=lrY
rets | | ret from function | stack->PC
jzx | M,S,A,B,l | Jump if zero after shift + mask |
+jzxh | A,l | Human readable alias for jzx | See detailed docs
jnzx | M,S,A,B,l | Jump if nonzero after shift+msk |
+jnzxh | A,l | Human readable alias for jnzx | See detailed docs
jext | E,A,B,l | Jump if External Condition true | if(E) PC=l
jnext | E,A,B,l | Jump if External Condition false| if(!E) PC=l
@@ -66,6 +68,7 @@
rr | A,B,rD | Rotate right | rD=rrot(A, B bits)
nand | A,B,rD | Clear bits (notmask+and) | rD=A&(~B)
orx | M,S,A,B,rD | OR with shift and select | See detailed docs
+orxh | A,B,rD | Human readable alias for orx | See detailed docs
Other instructions:
nap | none | Sleep until event | See detailed docs
More information about the b43-dev
mailing list