Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: at86rf2xx: Simultaneous use of multiple at86rf2xx device types #12914

Closed
wants to merge 27 commits into from

Conversation

fabian18
Copy link
Contributor

@fabian18 fabian18 commented Dec 10, 2019

Contribution description

This pull request updates the AT86RF2XX driver while it adds functionality to manage more than just
one type of AT86RF2XX device at the time. Currently RIOT supports external SPI based transceivers:

  • AT86RF212B
  • AT86RF231
  • AT86Rf232
  • AT86RF233

and MCU integrated trasceivers:

  • AT86RFA1
  • AT86RFR2

The implementation concept was to add a dev_type member to the at86rf2xx_t structure.
Then there exist the types at86rf212b_t, at86rf231_t, at86rf232_t, at86rf233_t, at86rfa1_t
and at86rfr2_t. Depending on the dev_type member an at86rf2xx_t* is assumed to be safely castable
to the distinguished device. Explicit device types may implement additional members and they
have their correlating _params_t type because the number of connected devices is derived from
ther params. As well as device structures, params structures could have additional members and so
may vary in size.
The size of the driver scales with the number of transceivers, it was compiled for,
following the case differentiation pattern:

#if IS_USED(MODULE_AT86RF212B)
    /* Code for type AT86RF212B */
#endif
#if IS_USED(MODULE_AT86RF231)
    /* Code for type AT86RF231 */
#endif
#if IS_USED(MODULE_AT86RF232)
    /* Code for type AT86RF232 */
#endif
#if IS_USED(MODULE_AT86RF233)
    /* Code for type AT86RF233 */
#endif
#if IS_USED(MODULE_AT86RFA1)
    /* Code for type AT86RFA1 */
#endif
#if IS_USED(MODULE_AT86RFR2)
    /* Code for type AT86RFR2 */
#endif

However this does not propagate to application code.

Testing procedure

For testing I used a nucleo-f767zi with an AT86RF233 on the one side and a microduino-corerf with
an AT86RF233 on the other side and tested the application in tests/driver_at86rf2xx with txtsnd.
Since I don´t have any other type by my hand, maybe others with more transceiver types could also
test.
Nucleo side:

2019-12-10 10:13:57,679 #  ifconfig
2019-12-10 10:13:57,686 # Iface   0  HWaddr: 14:01, Long HWaddr: 26:10:51:13:21:72:14:01, PAN: 0x0023
2019-12-10 10:13:57,692 #            Address length: 2, Source address length: 2, Max.Payload: 102
2019-12-10 10:13:57,698 #            Channel: 26, Ch.page: 0, TXPower: 0 dBm, wireless
2019-12-10 10:13:57,700 #            AUTOACK  CSMA
> 2019-12-10 10:14:15,874 #  
2019-12-10 10:14:15,875 # DATA
2019-12-10 10:14:15,880 # Dest. PAN: 0x0023, Dest. addr.: 26:10:51:13:21:72:14:01
2019-12-10 10:14:15,884 # Src. PAN: 0x0023, Src. addr.: 22:2e
2019-12-10 10:14:15,888 # Security: 0, Frame pend.: 0, ACK req.: 1, PAN comp.: 1
2019-12-10 10:14:15,890 # Version: 1, Seq.: 0
2019-12-10 10:14:15,892 # 00000000  48  69
2019-12-10 10:14:15,892 # txt: Hi
2019-12-10 10:14:15,894 # RSSI: -46, LQI: 255
2019-12-10 10:14:15,894 # 
2019-12-10 10:14:19,343 # 
2019-12-10 10:14:19,344 # DATA
2019-12-10 10:14:19,349 # Dest. PAN: 0x0023, Dest. addr.: 26:10:51:13:21:72:14:01
2019-12-10 10:14:19,352 # Src. PAN: 0x0023, Src. addr.: 22:2f
2019-12-10 10:14:19,357 # Security: 0, Frame pend.: 0, ACK req.: 1, PAN comp.: 1
2019-12-10 10:14:19,359 # Version: 1, Seq.: 0
2019-12-10 10:14:19,360 # 00000000  48  69
2019-12-10 10:14:19,361 # txt: Hi
2019-12-10 10:14:19,363 # RSSI: -40, LQI: 255
2019-12-10 10:14:19,363 # 
txtsnd 0 3e:84:22:96:3d:84:22:2e Ho
2019-12-10 10:14:34,175 # txtsnd 0 3e:84:22:96:3d:84:22:2e Ho
2019-12-10 10:14:34,181 # txtsnd: send 2 bytes to 3e:84:22:96:3d:84:22:2e (PAN: 23:00)
> txtsnd 0 3e:84:22:96:3d:84:22:2f Ho
2019-12-10 10:14:38,828 #  txtsnd 0 3e:84:22:96:3d:84:22:2f Ho
2019-12-10 10:14:38,833 # txtsnd: send 2 bytes to 3e:84:22:96:3d:84:22:2f (PAN: 23:00)

Microduino side:

> ifconfig
2019-12-10 10:13:52,709 #  ifconfig
2019-12-10 10:13:52,716 # Iface   0  HWaddr: 22:2e, Long HWaddr: 3e:84:22:96:3d:84:22:2e, PAN: 0x0023
2019-12-10 10:13:52,722 #            Address length: 2, Source address length: 2, Max.Payload: 102
2019-12-10 10:13:52,728 #            Channel: 26, Ch.page: 0, TXPower: 0 dBm, wireless
2019-12-10 10:13:52,730 #            AUTOACK  CSMA
2019-12-10 10:13:52,737 # Iface   1  HWaddr: 22:2f, Long HWaddr: 3e:84:22:96:3d:84:22:2f, PAN: 0x0023
2019-12-10 10:13:52,743 #            Address length: 2, Source address length: 2, Max.Payload: 102
2019-12-10 10:13:52,749 #            Channel: 26, Ch.page: 0, TXPower: 0 dBm, wireless
2019-12-10 10:13:52,751 #            AUTOACK  CSMA
> txtsnd 0 26:10:51:13:21:72:14:01 Hi
2019-12-10 10:14:15,871 #  txtsnd 0 26:10:51:13:21:72:14:01 Hi
2019-12-10 10:14:15,877 # txtsnd: send 2 bytes to 26:10:51:13:21:72:14:01 (PAN: 23:00)
> txtsnd 01 26:10:51:13:21:72:14:01 Hi
2019-12-10 10:14:19,340 #  txtsnd 01 26:10:51:13:21:72:14:01 Hi
2019-12-10 10:14:19,345 # txtsnd: send 2 bytes to 26:10:51:13:21:72:14:01 (PAN: 23:00)
> 2019-12-10 10:14:34,178 #  
2019-12-10 10:14:34,180 # DATA
2019-12-10 10:14:34,185 # Dest. PAN: 0x0023, Dest. addr.: 3e:84:22:96:3d:84:22:2e
2019-12-10 10:14:34,188 # Src. PAN: 0x0023, Src. addr.: 14:01
2019-12-10 10:14:34,193 # Security: 0, Frame pend.: 0, ACK req.: 1, PAN comp.: 1
2019-12-10 10:14:34,195 # Version: 1, Seq.: 0
2019-12-10 10:14:34,196 # 00000000  48  6F
2019-12-10 10:14:34,197 # txt: Ho
2019-12-10 10:14:34,199 # RSSI: -46, LQI: 255
2019-12-10 10:14:34,199 # 
2019-12-10 10:14:38,830 # 
2019-12-10 10:14:38,837 # gnrc_netdev: possibly lost interrupt.
2019-12-10 10:14:38,837 # DATA
2019-12-10 10:14:38,840 # Dest. PAN: 0x0023, Dest. addr.: 3e:84:22:96:3d:84:22:2f
2019-12-10 10:14:38,843 # Src. PAN: 0x0023, Src. addr.: 14:01
2019-12-10 10:14:38,848 # Security: 0, Frame pend.: 0, ACK req.: 1, PAN comp.: 1
2019-12-10 10:14:38,850 # Version: 1, Seq.: 1
2019-12-10 10:14:38,851 # 00000000  48  6F
2019-12-10 10:14:38,852 # txt: Ho
2019-12-10 10:14:38,854 # RSSI: -39, LQI: 97

Issues/PRs references

#4876

@fabian18
Copy link
Contributor Author

@benpicco and @maribu, would be great if you could take a look at this.

@benpicco benpicco added Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation labels Dec 10, 2019
@jia200x
Copy link
Member

jia200x commented Dec 10, 2019

very nice one!

Copy link
Member

@maribu maribu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First chunk of comments

Comment on lines 82 to 93
#if IS_USED(MODULE_AT86RF212B)
for (int i = 0; i < num; i++) {
devs->base.dev_type = AT86RF2XX_DEV_TYPE_AT86RF212B;
devs->params = *params;
at86rf2xx_setup((at86rf2xx_t *)devs);
devs++;
params++;
}
#else
/* initialize device descriptor */
dev->params = *params;
(void)devs;
(void)params;
(void)num;
Copy link
Member

@maribu maribu Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really needed to use the processor here? If no AT86RF212B is used, the function will never be called and garbage collected at link time. So it would not matter if this contains some actual code, when it is never called anyway.

Update: Fixed/clearified sentence above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I don´t wrap it with #if, "AT86RF2XX_DEV_TYPE_AT86RF212B" will not be defined.
The types are supposed to work as index for the arrays in at86rf2xx_properties.c.
It does not compile if I drop it.
E.g. someone has an AT86RF212B and an AT86RF233, AT86RF2XX_DEV_TYPE_AT86RF212B will be zero and AT86RF2XX_DEV_TYPE_AT86RF233 will be 1. If all types were defined, it would e.g. mess up something like at86rf2xx_rssi_base_values[dev->base.dev_type], if I don´t drop the #if´s in at86rf2xx_properties.c which would increase RAM.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have them always defined, but give them some bogus value if the device is not used.

Comment on lines 83 to 89
for (int i = 0; i < num; i++) {
devs->base.dev_type = AT86RF2XX_DEV_TYPE_AT86RF212B;
devs->params = *params;
at86rf2xx_setup((at86rf2xx_t *)devs);
devs++;
params++;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just using devs[i].base.dev_type = AT86RF2XX_DEV_TYPE_AT86RF212B is more readable and with that you can drop the lines devs++ and params++. (The compiler will generate the same machine code anyway.)

Also: The cast from the more specific "object" to its parent "object" is not needed. You could just use at86rf2xx_setup(&devs[i].base) and have the type safety still in place. (Only when converting from the parent at86rf2xx_t to the more specific at86rf212b_t requires some magic. You can use the container_of() macro provided by kernel_defines.h for some limited type safety there: container_of() will only work if the more specific struct is indeed a "child object" of the at86rf2xx_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok,
so I will use container_of() for any cast from the parent to the specific type.

Hmm, if I drop the caset (at86rf2xx_t*) in at86rf2xx_setup(&devs[i].base) my compile complains.
Because .base is of type at86rf2xx_base_t. The type at86rf2xx_t is at86rf2xx_base_t + at86rf2xx_params_t.

size_t at86rf2xx_get_size(const at86rf2xx_t *dev)
{
switch (dev->base.dev_type) {
default: return sizeof(at86rf2xx_t);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default: return sizeof(at86rf2xx_t);
default:

This would allow the compiler to optimize this function much better if only a single type of AT86RF2xx devices is used: The default would just fall through to the single remaining case if only one device is used, so that all checks can be dropped in that case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could actually drop this function if there are variant specific functions (see 98797e0#r356007674)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

static void at86rf2xx_disable_clock_output(at86rf2xx_t *dev)
{
switch (dev->base.dev_type) {
default:;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default:;
default:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

Comment on lines 212 to 216
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__TRX_CTRL_0);
tmp &= ~(AT86RF2XX_TRX_CTRL_0_MASK__CLKM_CTRL);
tmp &= ~(AT86RF2XX_TRX_CTRL_0_MASK__CLKM_SHA_SEL);
tmp |= (AT86RF2XX_TRX_CTRL_0_CLKM_CTRL__OFF);
at86rf2xx_reg_write(dev, AT86RF2XX_REG__TRX_CTRL_0, tmp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable uint8_t tmp needs a scope to live it. You could just enclose this all into a pair of curly braces, e.g.

            {
                uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__TRX_CTRL_0);
                tmp &= ~(AT86RF2XX_TRX_CTRL_0_MASK__CLKM_CTRL);
                tmp &= ~(AT86RF2XX_TRX_CTRL_0_MASK__CLKM_SHA_SEL);
                tmp |= (AT86RF2XX_TRX_CTRL_0_CLKM_CTRL__OFF);
                at86rf2xx_reg_write(dev, AT86RF2XX_REG__TRX_CTRL_0, tmp);
            }

or you can forward declare uint8_t tmp before the switch() statement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am gonna take the curly braces.

Comment on lines 227 to 246
static void at86rf2xx_enable_smart_idle(at86rf2xx_t *dev)
{
switch (dev->base.dev_type) {
default:
(void)dev;
break;
#if IS_USED(MODULE_AT86RF233)
case AT86RF2XX_DEV_TYPE_AT86RF233: {
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__TRX_RPC);
tmp |= (AT86RF2XX_TRX_RPC_MASK__RX_RPC_EN |
AT86RF2XX_TRX_RPC_MASK__PDT_RPC_EN |
AT86RF2XX_TRX_RPC_MASK__PLL_RPC_EN |
AT86RF2XX_TRX_RPC_MASK__XAH_TX_RPC_EN |
AT86RF2XX_TRX_RPC_MASK__IPAN_RPC_EN);
at86rf2xx_reg_write(dev, AT86RF2XX_REG__TRX_RPC, tmp);
at86rf2xx_set_rxsensitivity(dev, AT86RF233_RSSI_BASE_VAL);
break;
}
#endif
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the RFA1 and the RFR2 also support smart idle and this was recently merged upstream.

Copy link
Contributor

@benpicco benpicco Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only RFR2 does. #12704

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. With device specific functions (at86rf233_enable_smart_idle) we get rid of the switch-case.

Copy link
Contributor Author

@fabian18 fabian18 Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, a device specific function makes sens to me.
However, the calling function must check the device type

But the implementation of at86rf233_enable_smart_idle() and at86rfr2_enable_smart idle() would be the same.
So I think the best would be to take the core of at86rf2xx_enable_smart_idle(), keep the name, and make static inline at86rf233_enable_smart_idle() and at86rfr2_enable_smart idle()
static inline wrappers and the calling function (at86rf2xx_reset()) performs the switch case type checking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the calling function must check the device type

Why?
We could move that logic on top. I would assume the user of the raw driver knows which device is being used (AT86RF231, AT86RF233, etc). I would define the driver with variants specific prefixes (most of them can be just static inline wrappers to a common function) and let the layer on top (e.g netdev) device which function should be called.
Does this makes sense?

Comment on lines 291 to 298
switch (dev->base.dev_type) {
#if IS_USED(MODULE_AT86RF212B)
case AT86RF2XX_DEV_TYPE_AT86RF212B: {
at86rf2xx_set_page(dev, AT86RF212B_DEFAULT_PAGE);
break;
}
#endif
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a pseudomodule at86rf2xx_multidev that is automatically used in case more than a single type of the AT86RF2xx family is used. That would allow to optimize for the case only a single kind of device is used, e.g. like this:

    switch (dev->base.dev_type) {
        default:
#if IS_USED(MODULE_AT86RF2XX_MULTIDEV)
            break;
#endif
#if IS_USED(MODULE_AT86RF212B)
        case AT86RF2XX_DEV_TYPE_AT86RF212B: {
            at86rf2xx_set_page(dev, AT86RF212B_DEFAULT_PAGE);
            break;
        }
#endif
    }

With the above code when only at86rf212b is used and no other type, the compiler knows for sure that it will just always have to call at86rf2xx_set_page() and no checks are needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me the issue here is that only Sub GHz radio have different page settings. Do we need a generic at86rf2xx_set_page?
As proposed in some other comments, I would define the common code for setting channel page and then only declare variants that make sense (at86rf231_set_page should not exist).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would get the same compiler optimization if we only use 2.4 ghz radios.

tmp &= ~(AT86RF2XX_TRX_CTRL_1_MASK__IRQ_MASK_MODE);
at86rf2xx_reg_write(dev, AT86RF2XX_REG__TRX_CTRL_1, tmp);
switch (dev->base.dev_type) {
default:;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default:;
default:

default:;
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__TRX_CTRL_1);
tmp &= ~(AT86RF2XX_TRX_CTRL_1_MASK__IRQ_MASK_MODE);
at86rf2xx_reg_write(dev, AT86RF2XX_REG__TRX_CTRL_1, tmp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again uint8_t tmp needs a scope to live in. You could use a forward declaration before the switch() or add this to its own scope by wrapping it into a pair of curly braces.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, you code add pseudo modules at86rf2xx_spi and at86rf2xx_periph. The first would be always added if any of the SPI attached variants is used, and at86rf2xx_periph would be added if any of the peripheral variants (RFA1, RFR2) is used.

That would allow you to wrap the code above into:

#if IS_USED(MODULE_AT86RF2XX_SPI)

#endif

As a result, the ROM wouldn't increase if only the periph variants are used.

at86rf2xx_reg_write(dev, AT86RF2XX_REG__IRQ_MASK,
AT86RF2XX_IRQ_STATUS_MASK__TRX_END);
at86rf2xx_reg_write(dev, AT86RF2XX_REG__IRQ_MASK, en_irq_mask);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this instead:

    switch (dev->base.dev_type) {
        default:
#if IS_USED(MODULE_AT86RF2XX_SPI):
            at86rf2xx_reg_read(dev, AT86RF2XX_REG__IRQ_STATUS);
#endif
#if IS_USED(MODULE_AT86RF2XX_PERIPH):
        case AT86RF2XX_DEV_TYPE_AT86RFA1:
        case AT86RF2XX_DEV_TYPE_AT86RFR2:
            at86rf2xx_reg_write(dev, AT86RF2XX_REG__IRQ_MASK,
                                AT86RF2XX_IRQ_STATUS_MASK__TX_END | AT86RF2XX_IRQ_STATUS_MASK__RX_END;
#endif

#endif
}

void at86rfa1_setup(at86rfa1_t *dev)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this pattern of having variant specific functions. Does it make sense to do the same for most of the functions and implement at86rf2xx_ common only where it's needed?

Copy link
Member

@jia200x jia200x left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some (non blocking) comments on my side

@miri64
Copy link
Member

miri64 commented Dec 10, 2019

As I was pulled into this somehow: A more descriptive PR title would be nice ;-).

@maribu
Copy link
Member

maribu commented Dec 10, 2019

The file at86rf2xx_devs.h seems to be missing

@fabian18
Copy link
Contributor Author

The file at86rf2xx_devs.h seems to be missing

Ups...
I am working on the fixups.

Comment on lines 723 to 1333
#if IS_USED(MODULE_AT86RFA1)
case AT86RF2XX_DEV_TYPE_AT86RFA1: {
irq_mask = ((at86rfa1_t *)dev)->irq_status;
((at86rfa1_t *)dev)->irq_status = 0;
break;
}
#endif
#if IS_USED(MODULE_AT86RFR2)
case AT86RF2XX_DEV_TYPE_AT86RFR2: {
irq_mask = ((at86rfr2_t *)dev)->irq_status;
((at86rfr2_t *)dev)->irq_status = 0;
break;
}
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's useful to duplicate the code for those two.
You could also have a static inline _get_irq_status() function to get the #ifdef out of _isr()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can do a static inline static inline _get_irq_status().

Unfortunately, I cannot do:

#if IS_USED(MODULE_AT86RFA1) || IS_USED(MODULE_AT86RFR2)
case AT86RF2XX_DEV_TYPE_AT86RFA1:
case AT86RF2XX_DEV_TYPE_AT86RFR2: {
 /* code */
}
#endif

because the AT86RF2XX_DEV_TYPE_* is only known to the linker if the module is included.
But that´s part of the concept to reduce memory.

If I always have defined all device types, I always must have all values in at86rf2xx_properties.c defined to keep the index base property access working. And it would need more RAM. If the compiler sees that the program does the exact same thing for two cases, I thought it gets optimized to one case?

Do you prefer to have all types always available and for that increase RAM size?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I see. Of course I'd rather save on RAM ROM (those arrays are const so they'll reside in ROM).

If the compiler sees that the program does the exact same thing for two cases, I thought it gets optimized to one case?

I know the compiler does this for strings, but for code I wouldn't be so sure.
Anyway it doesn't matter in this case as you can't have both AT86RFA1 & AT86RFR2 enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On AVR they will end up consuming both RAM and ROM :-(

In this regard the von-Neumann architecture simply is better.

@fabian18 fabian18 changed the title Driver at86rf2xx Simultaneous use of multiple at86rf2xx device types Dec 10, 2019
Comment on lines 453 to 491
switch (dev->base.dev_type) {
#if IS_USED(MODULE_AT86RF212B)
/* AT86RF212B has CCA_CS_THRES as bits [7; 4] in reg CCA_THRES,
so take care that we don´t override them. */
case AT86RF2XX_DEV_TYPE_AT86RF212B: {
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__CCA_THRES);
tmp &= ~AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;
value = tmp |= value;
break;
}
#endif
#if IS_USED(MODULE_AT86RF231)
/* AT86RF231 also has CCA_CS_THRES */
case AT86RF2XX_DEV_TYPE_AT86RF231: {
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__CCA_THRES);
tmp &= ~AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;
value = tmp |= value;
break;
}
#endif
#if IS_USED(MODULE_AT86RFA1)
/* AT86RFA1 also has CCA_CS_THRES */
case AT86RF2XX_DEV_TYPE_AT86RFA1: {
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__CCA_THRES);
tmp &= ~AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;
value = tmp |= value;
break;
}
#endif
#if IS_USED(MODULE_AT86RFR2)
/* AT86RFR2 also has CCA_CS_THRES */
case AT86RF2XX_DEV_TYPE_AT86RFR2: {
uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__CCA_THRES);
tmp &= ~AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;
value = tmp |= value;
break;
}
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is the same in all cases, so do you even need the switch?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is also a good candidate of static inline functions (e.g at86rf2_xxx`) that call the same function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g

/* at86rf2xx.c */
void at86rf2xx_set_cca_threshold(const at86rf2xx_t *dev, int8_t value)
{
            uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__CCA_THRES);
            tmp &= ~AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;
            value = tmp |= value;
}

/* at86rf231.h */
void at86rf231_set_cca_threshold(const at86rf2xx_t *dev, int8_t value)
{
    at86rf2xx_set_cca_threshold(dev, value);
}

/* at86rf233.h */
void at86rf233_set_cca_threshold(const at86rf2xx_t *dev, int8_t value)
{
    at86rf2xx_set_cca_threshold(dev, value);
}

Etc

Copy link
Contributor Author

@fabian18 fabian18 Dec 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All types have CCA_ED_THRES in CCA_THRESH register.
Inside the switch were types that also have CCA_CS_THRES.
But what is inside the cases would work for any kind.

So if it works for all kinds, a type specific static inline wrapper would not be necessary, right?
This should work for any.

void at86rf2xx_set_cca_threshold(const at86rf2xx_t *dev, int8_t value)
{
   /* ensure the given value is negative, since a CCA threshold > 0 is
      just impossible: thus, any positive value given is considered
      to be the absolute value of the actually wanted threshold */
   if (value > 0) {
       value = -value;
   }
   /* transform the dBm value in the form
      that will fit in the AT86RF2XX_REG__CCA_THRES register */
   value -= at86rf2xx_rssi_base_values[dev->base.dev_type];
   value >>= 1;
   value &= AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;

   uint8_t tmp = at86rf2xx_reg_read(dev, AT86RF2XX_REG__CCA_THRES);
   tmp &= ~AT86RF2XX_CCA_THRES_MASK__CCA_ED_THRES;
   value = tmp | value;
   value |= AT86RF2XX_CCA_THRES_MASK__RSVD_HI_NIBBLE; /* What is this? */
   at86rf2xx_reg_write(dev, AT86RF2XX_REG__CCA_THRES, value);
}

I could not find in any datasheet where
AT86RF2XX_CCA_THRES_MASK__RSVD_HI_NIBBLE
could come from.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to have this function as a common one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this specific case, I think the conversion from dbm to internal values should be done outside of this function. Otherwise it removes all chances of caching the internal value and we would need to recalculate the internal value each time we call this function

@kaspar030 kaspar030 changed the title Simultaneous use of multiple at86rf2xx device types drivers: at86rf2xx: Simultaneous use of multiple at86rf2xx device types Dec 10, 2019
@maribu
Copy link
Member

maribu commented Dec 10, 2019

Providing separate netdev_driver_t for each of the variants would likely result in a lot less #ifdef ... #endif. They would have their own send()/recv()/... version. To avoid duplication of code, static functions could be provided to handle the common stuff. I think this is also what @jia200x proposed in the inline comments.

In case of only a single variant of the AT86RF2xx is used, I guess this will have no impact on the ROM size, as modern compilers are pretty smart regarding inlining. When multiple variants are used, this will likely result in more ROM being used that a central function with a switch(). But I guess the gained readability could be just worth this. (And of course having different netdev_driver_t structures to be stored somewhere will also increase ROM size on top.)

@jia200x
Copy link
Member

jia200x commented Dec 11, 2019

Providing separate netdev_driver_t for each of the variants would likely result in a lot less #ifdef ... #endif. They would have their own send()/recv()/... version. To avoid duplication of code, static functions could be provided to handle the common stuff. I think this is also what @jia200x proposed in the inline comments.

Same opinion here. I would have only one netdev_driver_t that calls the specific variant.

Basically what I'm proposing is to avoid variant specific functionalities as generic AT86RF2XX functions. Most functions make sense as at86rf2xx (register access, set channel, etc). Some others only make sense for a giving variant or subset of radios (set page, smart idle, etc).
If we are afraid of mixing variant specific APIs with a generic API(e.g at86rf2xx_xxx and at86rf233_xxx in the same file) we can always create static inline wrappers for common functions, but it would probably not hurt to have a mixture here.

So, who would call the specific variant function? The user of the driver (netdev, test, etc).

} at86rf2xx_t;

#if IS_USED(MODULE_AT86RF212B)
typedef struct at86rf212b_params {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think variant specific descriptors should be in their respective header file (e.g this one in the at86rf212b.h file)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schould e.g. at86212b_t and at86rf212b_params_t and at86rf212b_setup() be publicly available or just within the driver, e.g in at86rf2xx.c?
I decided to have them publicly available because if someone knows that he builds an application just for at86212b he could directly call at86rf212b_setup()

dev->base.state = AT86RF2XX_STATE_P_ON;
dev->base.pending_tx = 0;

switch (dev->base.dev_type) {
Copy link
Member

@jia200x jia200x Dec 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would move this "dev_type" logic to an upper layer (e.g netdev). The user of the driver will always know which the variant is (e.g "at86rfa1_setup` function could work here)

{
#if IS_USED(MODULE_AT86RFR2)
dev->base.dev_type = AT86RF2XX_DEV_TYPE_AT86RFR2;
at86rf2xx_setup((at86rf2xx_t *)dev);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is an example of what I described above. You could call this line here and avoid the switch-case


void at86rfr2_setup(at86rfr2_t *dev)
{
#if IS_USED(MODULE_AT86RFR2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as commented by @maribu somewhere, this IS_USED macro is probably not needed. If no one calls this function, it will get optimized out.

IS_USED(MODULE_AT86RF232) || \
IS_USED(MODULE_AT86RFA1) || \
IS_USED(MODULE_AT86RFR2)
const int16_t _231_232_a1_r2_tx_pow_to_dbm[16] = { 3, 3, 2, 2, 1, 1, 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since now all these variables can coexist, we can remove the IS_USED macros. They won't get to the binary file if they are not referenced by someone

Copy link
Member

@jia200x jia200x left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More comments

* @param[in] params parameters
* @param[in] num number of transceivers
*/
void at86rf212b_setup(at86rf212b_t *devs, const at86rf212b_params_t *params, uint8_t num);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and below, variant specific functions should be in their header file

@fabian18
Copy link
Contributor Author

@jia200x maybe I do not completely understand your concept, I am sorry.
Let´s say there is a feature "foo" only supported by at86rf23[1 | 2 | 3] then you would put

  • at86rf231_foo() in at86rf231.h and make it (publicly available).
  • at86rf232_foo() in at86rf232.h and make it (publicly available).
  • at86rf233_foo() in at86rf233.h and make it (publicly available).
    So a user could call at86rf233_foo(), if he knows he has a 233, or netdev calls it.
    That´s an advantage, and I had this in mind with *_setup().
    However there should just be one netdev driver, right? (I hope so.)
    So the netdev driver does the type dispatching in its functions _init, _send etc.
    Maybe the _foo()´s are all static inline wrappers because "what" foo does is always the same but only 231, 232 and 233 support it.
    So even if it is always the same "foo", netdev would dispatch:
switch (...)
#if IS_USED(MODULE_AT86RF231)
    case AT86RF231: at86rf231_foo(); break;
#endif
#if IS_USED(MODULE_AT86RF232)
    case AT86RF232: at86rf232_foo(); break;
#endif
#if IS_USED(MODULE_AT86RF233)
    case AT86RF233: at86rf233_foo(); break;
#endif

I feel like it would be better to keep type dispatching out of netdev because it is more like
"program flow" - what shall I do to _init(), or to _send() and it is already a lot of code and type dispatching would put more code there.
Instead the at86rf2xx_*() are more like "functionality" to me.
Take at86rf2xx_foo() which could be called from netdev if there was some kind of NETOPT_FOO.
In at86rf2xx_foo(), there would be the type dispatching with a default case that at best would do the job for almost every type, and breaks if the given type does not support it.
I propose: type dispatching shall only be allowed in at86rf2xx_*() functions because they are the most device specific entry point.

@jia200x
Copy link
Member

jia200x commented Dec 12, 2019

Let´s say there is a feature "foo" only supported by at86rf23[1 | 2 | 3] then you would put
at86rf231_foo() in at86rf231.h and make it (publicly available).
at86rf232_foo() in at86rf232.h and make it (publicly available).
at86rf233_foo() in at86rf233.h and make it (publicly available).
So a user could call at86rf233_foo(), if he knows he has a 233, or netdev calls it.
That´s an advantage, and I had this in mind with *_setup().

Yes, that's exactly the idea.

However there should just be one netdev driver, right? (I hope so.)

It's not written anywhere how netdev should be implemented, bit I also hope so :) One netdev should be enough.

I feel like it would be better to keep type dispatching out of netdev because it is more like
"program flow" - what shall I do to _init(), or to _send() and it is already a lot of code and type dispatching would put more code there.

This is a problem of the implementation of the _send and _init functions. In part because netdev is currently mixing transceiver and PHY layer stuff (see #12688 )
Also, it's not possible to use the atmel radio without the netdev layer because a lot of driver code is in the netdev layer. That should be changed in order to have an autonomous driver.

Instead the at86rf2xx_*() are more like "functionality" to me.
Take at86rf2xx_foo() which could be called from netdev if there was some kind of NETOPT_FOO.
In at86rf2xx_foo(), there would be the type dispatching with a default case that at best would do the job for almost every type, and breaks if the given type does not support it.

It's possible to have a mixture of at86rfxx and e.g at86rf231 to avoid having these kind of ugly ifdefs (I'm aware in most cases it's not needed to have duplicated static inline functions).
From the point of view of the user of the driver, it will always know which device it's being used (if you plug an at86rf231 you are sure you would need to call either a common function with at86rf2xx prefix or at86rf231.
IMO adding dispatch code in the driver is adding a hardware abstraction layer there (which shouldn't be the responsibility of the driver but of a layer on top).
Netdev is acting as part of the HAL, so that's why I think it should be better to have it there.

I propose: type dispatching shall only be allowed in at86rf2xx_*() functions because they are the most device specific entry point.

As described above, my concern is about adding pieces of a HAL here. I'm aware it's possible to optimize code with ifdefs but at the same time it makes it harder to maintain and test (and we try to compete with compiler optimizations). The driver is already quite hard to maintain and tests.

I would propose it this way:

  • Identify common functions (since anything is black or white,if a small ifdef makes it easier to read and adds less boilerplate code, then it wouldn't hurt)
  • Identify functions that only make sense for certain variants (the APIs of a 2.4 GHz and SubGHz radios are different, so probably there will be some variants specific functions here). We could also think of grouping functions by technology (e.g at86rf2xx_subghz_xxx).
  • In the mid term, make this driver independent of netdev. That also means smaller _send, _init, etc.

What do you think?

@fabian18
Copy link
Contributor Author

I guess I will have to start over then because this is different from what I intended.
Does this layout match your concept?

drivers
  |
  +- include
  |    |
  |    +- at86rf2xx.h
  |
  +- at86rf2xx
       |
       +- at86rf212b
       |    |
       |    +- include
       |    |    |
       |    |    |
       |    |    |
       |    |    +- at86rf212b.h (driver internal hedaer)
       |    |
       |    +- at86rf212b.c (implements 212b specific stuff)
       |     
       +- at86rf231
       |     |
       |     +- include
       |     |    |
       |     |    +- at86rf231.h (driver internal header)
       |     |
       |     +- at86rf231.c (implements 231 specific stuff)
       |
       +- at86rf232
       |     |
       |     +- include
       |     |    |
       |     |    +- at86rf232.h (driver internal header)
       |     |
       |     +- at86rf232.c (implements 232 specific stuff)
       |
       +- at86rf233
       |     |
       |     +- include
       |     |    |
       |     |    +- at86rf233.h (driver internal header)
       |     |
       |     +- at86rf233.c (implements 233 specific stuff)
       | 
       +- at86rfa1
       |    |
       |    +- include
       |    |    |
       |    |    +- at86rfa1.h (driver internal header)
       |    |
       |    +- at86rfa1.c (implements rfa1 specific stuff)
       | 
       +- at86rfr2
       |     |
       |     +- include
       |     |    |
       |     |    +- at86rfr2.h (driver internal header)
       |     |
       |     +- at86rfr2.c (implements rfr2 specific stuff)
       |     
       |
       |
       +- include
       |   |
       |   +- at86rf212b.h        (included in at86rf2xx.h if module is used)
       |   +- at86rf212b_params.h (included in at86rf2xx_params.h if module is used)
       |   +- at86rf231.h         (included in at86rf2xx.h if module is used)
       |   +- at86rf231_params.h  (included in at86rf2xx_params.h if module is used)
       |   +- at86rf232.h         (included in at86rf2xx.h if module is used)
       |   +- at86rf232_params.h  (included in at86rf2xx_params.h if module is used)
       |   +- at86rf233.h         (included in at86rf2xx.h if module is used)
       |   +- at86rf233_params.h  (included in at86rf2xx_params.h if module is used)
       |   +- at86rfa1.h          (included in at86rf2xx.h if module is used)
       |   +- at86rfa1_params.h   (included in at86rf2xx_params.h if module is used)
       |   +- at86rf2xx_params.h
       |   +- at86rf2xx_dev_types.h
       |   +- at86rf2xx_registers.h
       |   +- at86rf2xx_spi.h
       |   +- at86rf2xx_periph.h
       |   +- at86rf2xx_communication.h (interface to read/write a register, no matter if spi or periph)
       |   +- at86rf2xx_common.h (driver internal interface for common functionalities)
       |   +- at86rf2xx_netdev.h
       |
       |
       +- at86rf2xx_spi.c
       +- at86rf2xx_periph.c
       +- at86rf2xx_communication.c
       +- at86rf2xx_common.c (impl. of common functions)
       +- at86rf2xx_netdev.c (point of dispatching)

@fabian18
Copy link
Contributor Author

I rebased to master and adopted the fixes in: #12728 #13356 #13362 .
The reviews have been requested automatically.

@@ -1530,6 +1530,7 @@ ISR(TRX24_RX_END_vect, ISR_BLOCK)
*/
ISR(TRX24_XAH_AMI_vect, ISR_BLOCK)
{
atmega_enter_isr();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don´t know where this went lost

@jia200x
Copy link
Member

jia200x commented Feb 17, 2020

@fabian18 thanks for the rework!

I think the current concept matches what we discussed (individual devices, netdev as a dispatcher).
I have 2 main comments:

  1. I think the APIs now are quite clean and make sense. However, I think the implementation of the APIs can reuse a lot of code.
    E.g there are indeed differences between the SPI and Periph versions, but the only think that changes is the "Presentation Layer" (a.k.a how to access the registers). The logic on top is mostly the same (same reg addresses, etc).
    If there's an "at86rf2xx_reg_read" that maps to "at86rf2xx_spi_reg_read" and "at86rf2xx_periph_reg_read" depending on the device type, a lot of common code can be factored out (basically all xxx_set_state, hardware reset, etc).
    Same thing for accessing the framebuffer (in the periph version is different, but the logic on top is the same). I would suggest to do most of the dispatch stuff in the presentation layer rather than in the functions on top.

For the netdev component, we will still need to do some dispatching (as discussed above). But here the receive procedure can also be the same with the right Presentation Layer functions.

Let me know if this is not clear.

  1. Someone (board, user) already knows the variants of these AT86RF2xx radios. A user of a raw at86rf231 radio already knows it should create the at86rf231_t descriptor and call at86rf231_setup directly. This is also possible from the test applications and auto_init_at86rf2xx.
    Then I have the impression we could get rid of the at86rf2xx_devs.h logic (with at86rf2xx_get_dev and at86rf2xx_get_size). It would be also easier to maintain and review.

What do you think?

@fabian18
Copy link
Contributor Author

If there's an "at86rf2xx_reg_read" that maps to "at86rf2xx_spi_reg_read" and "at86rf2xx_periph_reg_read" depending on the device type

There is, in at86rf2xx_communication.c. It distinguishes between SPI device and type of MCU.

The logic on top is mostly the same (same reg addresses, etc).

The register addresses differ between SPI and MCU, but the MCU register address is the SPI register address plus some offset. But theoretically the offset could be different for any MCU. That´s why the functions in at86rf2xx_communication.c distinguish between at86rfa1 and at86rfr2, although they actually have the same offset. But since there can only be 1 MCU, the offset could actually be a compile time constant. - I will do that.

(basically all xxx_set_state, hardware reset, etc)

There are different delays for awakening from sleep state and reset.
For reset we are currently using a high value that fits all, but one could use the explicit delays as well.
I could have an array of these different delays and access it´s fields with the device type, as it was before. Then if at86rf2xx_reg_read/write() only distinguishes between SPI and MCU, I could probably do a single at86rf2xx_set_state() / at86rf2xx_hardware_reset().

Same thing for accessing the framebuffer

I think having e.g. at86rf233_fb_read() is reasonable because the frame buffer contains different information for some transceiver types. No matter where (presentation layer / netdev) and how (SPI / Periph), I must know what I can expect to read from the frame buffer, e.g. ED_LEVEL. - right?
I think a general at86rf2xx_fb_read() without comparing device types won´t work.
Once there is a common at86rf2xx_set_state(), recv() will get smaller significantly.

I would suggest to do most of the dispatch stuff in the presentation layer rather than in the functions on top

What / where is the presentation layer in RIOT?

A user of a raw at86rf231 radio already knows it should create the at86rf231_t descriptor and call at86rf231_setup directly.

If I wrote an application for one certain type I would exactly do that.

This is also possible from the test applications and auto_init_at86rf2xx.

I could not figure out how to do that because the test should work for all different kinds.
So there must be an array of devices for each kind. But I cannot have an array of size 0.
So would you rather like to have:

#if IS_USED(MODULE_AT86RF212B)
        /**
         * @brief   AT86RF212B device array
         */
        at86rf212b_t at86rf212b_devs[AT86RF212B_NUM_OF];
#endif
#if IS_USED(MODULE_AT86RF231)
        /**
         * @brief   AT86RF231 device array
         */
        at86rf231_t at86rf231_devs[AT86RF231_NUM_OF];
#endif
#if IS_USED(MODULE_AT86RF232)
        /**
         * @brief   AT86RF232 device array
         */
        at86rf232_t at86rf232_devs[AT86RF232_NUM_OF];
#endif
#if IS_USED(MODULE_AT86RF233)
        /**
         * @brief   AT86RF233 device array
         */
        at86rf233_t at86rf233_devs[AT86RF233_NUM_OF];
#endif
#if IS_USED(MODULE_AT86RFA1)
        /**
         * @brief   AT86RFA1 device array
         */
        at86rfa1_t at86rfa1_devs[AT86RFA1_NUM_OF];
#endif
#if IS_USED(MODULE_AT86RFR2)
        /**
         * @brief   AT86RFR2 device array
         */
        at86rfr2_t at86rfr2_devs[AT86RFR2_NUM_OF];
#endif

and

#if IS_USED(MODULE_AT86RF212B)
    at86rf212b_setup(at86rf2xx_devs->named_devs.at86rf212b_devs,
                     at86rf212b_params, AT86RF212B_NUM_OF);
#endif
#if IS_USED(MODULE_AT86RF231)
    at86rf231_setup(at86rf2xx_devs->named_devs.at86rf231_devs,
                    at86rf231_params, AT86RF231_NUM_OF);
#endif
#if IS_USED(MODULE_AT86RF232)
    at86rf232_setup(at86rf2xx_devs->named_devs.at86rf232_devs,
                    at86rf232_params, AT86RF232_NUM_OF);
#endif
#if IS_USED(MODULE_AT86RF233)
    at86rf233_setup(at86rf2xx_devs->named_devs.at86rf233_devs,
                    at86rf233_params, AT86RF233_NUM_OF);
#endif
#if IS_USED(MODULE_AT86RFA1)
    at86rfa1_setup(at86rf2xx_devs->named_devs.at86rfa1_devs);
#endif
#if IS_USED(MODULE_AT86RFR2)
    at86rfr2_setup(at86rf2xx_devs->named_devs.at86rfr2_devs);
#endif

in each application, instead of wrapping this inside a struct or a function respectively?
Or how would you write an application that should work for all transceiver types?
And I see a necessity to have all devices stored in continuous memory, because the send() function in tests/driver_at86rf2xx/cmd.c want´s to access any at86rf2xx type by an index.

Most importantly I will try to keep a single at86rf2xx_set_state() function and then I should get rid of some duplicate code.

@jia200x
Copy link
Member

jia200x commented Feb 18, 2020

There are different delays for awakening from sleep state and reset.
For reset we are currently using a high value that fits all, but one could use the explicit delays as well.
I could have an array of these different delays and access it´s fields with the device type, as it was before. Then if at86rf2xx_reg_read/write() only distinguishes between SPI and MCU, I could probably do a single at86rf2xx_set_state() / at86rf2xx_hardware_reset().

The array approach should work. And yes, having one reg_read and reg_write functions simplifies a lot all the layers on top.

think having e.g. at86rf233_fb_read() is reasonable because the frame buffer contains different information for some transceiver types. No matter where (presentation layer / netdev) and how (SPI / Periph), I must know what I can expect to read from the frame buffer, e.g. ED_LEVEL. - right?
I think a general at86rf2xx_fb_read() without comparing device types won´t work.

The FB information is indeed different, but accessing the framebuffer is an abstract operation. For getting ED level, IMO the netdev "read()" function should care about this. The "read framebuffer" function should be agnostic to the content.

What / where is the presentation layer in RIOT?

The Presentation Layer is basically that translates from deep operations (SPI, MCU reg access) into some logic that can be reused by the driver. What I meant with this, is the fact all these reg functions can be written to be device independent, as you described above.

Or how would you write an application that should work for all transceiver types?

A user of a single radio should directly use the device descriptor (as discussed above).
IMO there should be only 2 cases where one should use declare this list of device descriptors:

  1. The test application (all AT86rf2xx function should work for the test)
  2. The auto_init routine

An application that should work for all transceiver types should use an abstraction (e.g netdev). I know it's not direct now (since auto_init_at86rf2xx does more stuff than just calling _setup with the stuff defined in periph_conf.h, but that something we can fix :).

And I see a necessity to have all devices stored in continuous memory, because the send() function in tests/driver_at86rf2xx/cmd.c want´s to access any at86rf2xx type by an index.

Consider there are other patterns that have the same effect (array of pointers, linked list, etc). This moves the allocation logic to the device user and not to the device driver.

@@ -1,5 +1,5 @@
ifneq (,$(filter netdev_default gnrc_netdev_default,$(USEMODULE)))
# USEMODULE += stm32_eth
USEMODULE += stm32_eth
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented this out to test exampes/gnrc_networking, but with the ethernet driver it was hanging during SPI register access, although I set GNRC_NETIF_NUMOF := 2 (ethernet + at86rf233). But this is not the topic of this PR.

@fabian18
Copy link
Contributor Author

So far I am struggling to have a common at86rf2xx_set_state(), because it would require a common at86rf2xx_sleep(), but in *_sleep() at86rfa1 and at86rfr2 set dev->irq_status = 0. And accessing that member variable would need dispatch.

@jia200x
Copy link
Member

jia200x commented Feb 25, 2020

So far I am struggling to have a common at86rf2xx_set_state(), because it would require a common at86rf2xx_sleep(), but in *_sleep() at86rfa1 and at86rfr2 set dev->irq_status = 0. And accessing that member variable would need dispatch.

In that case I think it's ok to have an #ifdef guard for the "at86rf2xx_set_sleep function, considering:

  1. having one at86rf2xx_set_state function improves maintainability a lot
  2. The sleep state is a device state, not a transceiver state. The sleep state can be removed from the set_state function (as seen in at86rf2xx: remove SLEEP from phy states #12062).

@fabian18
Copy link
Contributor Author

2. The sleep state is a device state, not a transceiver state. The sleep state can be removed from the `set_state` function (as seen in #12062).

I am pretty much in favor of this!

@fjmolinas fjmolinas removed their request for review February 28, 2020 09:21
@fabian18 fabian18 closed this Apr 23, 2020
@jia200x
Copy link
Member

jia200x commented Apr 23, 2020

hi @fabian18

what was the reason to close this PR? This is a nice cleanup that would improve quite a lot the maintainability of these drivers!

@fabian18
Copy link
Contributor Author

Hi @jia200x,
I closed it because each time I try to improve this, the code structure seems to become more and more irrational to me. Although you could use multiple types with it, I do no longer believe that this is the most extendable and maintainable approach to solve the issue.

My new design choice would be to have a module drivers/at86rf2xx_common, which is not a stand alone driver (no gnrc_netif thread), but contains all the stuff that could be reused. Then you would have a netdev driver implementation for all the types drivers/at86rf233 and so on. For example at86rf233::netdev::get()/set() could mostly be handled by at86rf2xx_common::netdev::get()/set().
I know that this is more overhead but it would be more flexible and a simple linear dependency. Having drivers/at86rfa1 ... drivers/at86rf233 and drivers/at86rf215 also makes me feel better then having drivers/at86rf2xx and drivers/at86rf215.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants