Hi Markus,

Thank you for the background information.

Since the OTP device is part of the Secure Boot Controller (SBC), I plan to 
register it in the global table. I believe this will simplify usage.

Meanwhile, based on Philippe's comment, I’m working on `aspeed_otp.c` to handle 
low-level OTP operations. This approach should help decouple SBC and OTP 
functionalities.

Once testing is complete, I will submit a separate patch for further review.

Best regards,
Kane
> -----Original Message-----
> From: Markus Armbruster <arm...@redhat.com>
> Sent: Tuesday, April 8, 2025 7:39 PM
> To: Cédric Le Goater <c...@kaod.org>
> Cc: Kane Chen <kane_c...@aspeedtech.com>; Philippe Mathieu-Daudé
> <phi...@linaro.org>; Peter Maydell <peter.mayd...@linaro.org>; Steven Lee
> <steven_...@aspeedtech.com>; Troy Lee <leet...@gmail.com>; Jamin Lin
> <jamin_...@aspeedtech.com>; Andrew Jeffery
> <and...@codeconstruct.com.au>; Joel Stanley <j...@jms.id.au>; open
> list:ASPEED BMCs <qemu-...@nongnu.org>; open list:All patches CC here
> <qemu-de...@nongnu.org>; qemu-block <qemu-block@nongnu.org>; Troy Lee
> <troy_...@aspeedtech.com>
> Subject: Configuring onboard devices, in particular memory contents (was:
> [PATCH v1 0/1] hw/misc/aspeed_sbc: Implement OTP memory and controller)
> 
> Cédric Le Goater <c...@kaod.org> writes:
> 
> > Hello Kane,
> >
> > + Markus (for ebc29e1beab0 implementation)
> >
> > On 4/7/25 09:33, Kane Chen wrote:
> >> Hi Cédric/Philippe,
> >> OTP (One-Time Programmable) memory is a type of non-volatile memory
> >> in which each bit can be programmed only once. It is typically used
> >> to store critical and permanent information, such as the chip ID and
> >> secure boot keys. The structure and behavior of OTP memory are
> >> consistent across both the AST1030 and AST2600 platforms.
> >> As Philippe pointed out, this proposal models the OTP memory as a
> >> flash device and utilizes a block backend for persistent storage. In
> >> contrast, existing implementations such as NPCM7xxOTPState,
> >> BCM2835OTPState, and SiFiveUOTPState expose OTP memory via MMIO
> and
> >> always initialize it in a blank state.
> >
> > AFAIU, Aspeed SBC is also MMIO based or is there another device, an
> > eeprom, accessible through an external bus ? How is it implemented in
> > HW ?
> >
> >> The goal of this design is to
> >> allow the guest system to boot with a pre-configured OTP memory
> >> state.
> >
> > Yes. This is a valid request. It's not the first time we've had this
> > kind of requests. The initial content of EEPROM devices are an example
> > and some machines, like the rainier, have a lot.
> >
> > If the device can be defined on the command line, like would be an
> > EEPROM device attached to an I2C bus or a flash device attached to a
> > SPI bus, we can use a 'drive' property. Something like :
> >
> >   qemu-system-arm -M ast2600-evb \
> >       -blockdev node-name=fmc0,driver=file,filename=/path/to/fmc0.img
> \
> >       -device mx66u51235f,bus=ssi.0,cs=0x0,drive=fmc0 \
> >       -blockdev node-name=fmc1,driver=file,filename=/path/to/fmc1.img
> \
> >       -device mx66u51235f,bus=ssi.0,cs=0x1,drive=fmc1 \
> >       -blockdev node-name=spi1,driver=file,filename=/path/to/spi1.img \
> >       -device mx66u51235f,cs=0x0,bus=ssi.1,drive=spi1 \
> >       ...
> >
> > However, the Aspeed SBC device is a platform device and it makes
> > things more complex : it can not be created on the command line, it is
> > directly created by the machine and the soc and passing device
> > properties to specify a blockdev it is not possible :
> >
> >   qemu-system-arm -M ast2600-evb \
> >       -blockdev
> node-name=otpmem,driver=file,filename=/path/to/otpmem.img \
> >       -device aspeed-sbc,drive=otpmem \
> >       ...
> 
> Configuring onboard devices is an old problem, and so far we have failed at
> solving it adequately.
> 
> -device / device_add let you configure the new device in a general way, but
> these work only for device the user creates, not for devices the board creates
> automatically.
> 
> We have a bunch of ad hoc and mostly ancient ways to configure them, but
> they're all limited.  For example:
> 
> * A number of old command line options, such as -drive, -serial, -net
>   nic, create device backends and additionally deposit configuration in
>   some global table the board may elect to use however it sees fit.  The
>   intended use is to create frontends connected to these backends.
> 
>   Some boards error out when they can't honor something in the table.
>   Others silently ignore parts of the table, or all of it.  Bad UI.
> 
>   Device configuration the table doesn't support is not accessible this
>   way.  If you extend the table (and the associated option) to provide
>   access to some device-specific configuration, all the other devices
>   will silently ignore the new configuration bits.  Again, bad UI.
> 
>   There's another serious issue with block devices: -drive is obsolete
>   for configurating complex block backends.  But its replacement
>   -blockdev is for backend configuration only.  If you use -blockdev,
>   you can't add to the table.
> 
> * Command line option -global lets you change property defaults.  This
>   can be used to configure an onboard device as long as it is the only
>   such device in the system.  Limited use, and also bad UI.
> 
> A modern attempt at a solution is to have machine properties alias properties
> of onboard devices, so you can specify them with -machine.
> For instance, a few machines expose the "drive" property of two onboard
> pflash devices as machine properties "pflash0" and "pflash1".
> 
> Commits
> 
>     e0561e60f170 (hw/arm/virt: Support firmware configuration with
> -blockdev)
>     ebc29e1beab0 (pc: Support firmware configuration with -blockdev)
> 
> explain this in a lot more detail in their commit messages.
> 
> Sadly, this solution does not scale.  Adding alias properties to the machine
> object is work, sometimes a lot of work (evidence: the two commits above).
> There are simply too many onboard devices with too many properties to all
> manually alias.
> 
> Of course, even an insufficiently general / scalable solution like this one 
> can
> work well enough for specific cases.
> 
> >> To support this, the OTP memory is backed by a file, simulating
> >> persistent flash behavior.
> >
> > The idea is good but the implementation is problematic.
> >
> >     +static BlockBackend *init_otpmem(int64_t size_bytes)
> >     +{
> >     +    Error *local_err = NULL;
> >     +    BlockDriverState *bs = NULL;
> >     +    BlockBackend *blk = NULL;
> >     +    bool image_created = false;
> >     +    QDict *options;
> >     +    uint32_t i, odd_def = 0xffffffff, even_def = 0, *def;
> >     +
> >     +    if (!g_file_test(OTP_FILE_PATH, G_FILE_TEST_EXISTS)) {
> >     +        bdrv_img_create(OTP_FILE_PATH, "raw", NULL, NULL,
> >     +                        NULL, size_bytes, 0, true, &local_err);
> >     +        if (local_err) {
> >     +            qemu_log_mask(LOG_GUEST_ERROR,
> >     +                          "%s: Failed to create image %s: %s\n",
> >     +                          __func__, OTP_FILE_PATH,
> >     +                          error_get_pretty(local_err));
> >     +            error_free(local_err);
> >     +            return NULL;
> >     +        }
> >     +        image_created = true;
> >     +    }
> >     +
> >     +    blk = blk_new(qemu_get_aio_context(),
> >     +                  BLK_PERM_CONSISTENT_READ |
> BLK_PERM_WRITE,
> >     +                  0);
> >     +    if (!blk) {
> >     +        qemu_log_mask(LOG_GUEST_ERROR,
> >     +                      "%s: Failed to create BlockBackend\n",
> >     +                      __func__);
> >     +        return NULL;
> >     +    }
> >     +
> >     +    options =  qdict_new();
> >     +    qdict_put_str(options, "driver", "raw");
> >     +    bs = bdrv_open(OTP_FILE_PATH, NULL, options, BDRV_O_RDWR,
> &local_err);
> >     +    if (local_err) {
> >     +        qemu_log_mask(LOG_GUEST_ERROR,
> >     +                      "%s: Failed to create OTP memory, err =
> %s\n",
> >     +                      __func__, error_get_pretty(local_err));
> >     +        blk_unref(blk);
> >     +        error_free(local_err);
> >     +        return NULL;
> >     +    }
> >     +
> >     +    blk_insert_bs(blk, bs, &local_err);
> >     +    if (local_err) {
> >     +        qemu_log_mask(LOG_GUEST_ERROR,
> >     +                      "%s: Failed to insert OTP memory to SBC,
> err = %s\n",
> >     +                      __func__, error_get_pretty(local_err));
> >     +        bdrv_unref(bs);
> >     +        blk_unref(blk);
> >     +        error_free(local_err);
> >     +        return NULL;
> >     +    }
> >     +    bdrv_unref(bs);
> >     ...
> >
> > IMO, this is low level block code that a device model shouldn't have
> > to deal with. A 'drive' should be used instead. Now, if the qemu-block
> > maintainers are OK with it, we need their approval.
> 
> Using block backends to specify the contents of a memory device is a bit of a
> hack.  However, it's the hacky solution we use, and until we have a better
> solution, new code is well advised to stick to the same hacky solution we use 
> in
> existing code.
> 
> Why is it a bit of a hack?  Well, memory isn't a block device.  For read-only
> memory, all we want from the block device is slurping in some image in its
> entirety.  We're not interesting in reading parts, or writing at all.  For
> writable memory, we are interested in writing, but there's often a awkward
> translation to block device blocks.
> 
> >  > The OTP memory access flow is as follows:
> >> 1. The guest issues a read or write OTP command to the Secure Boot
> >>     Controller (SBC)
> >> 2. The SBC triggers the corresponding operation in the OTP controller
> >> 3. The SBC returns the result to the guest Since the guest interacts
> >> with OTP memory exclusively through the SBC, the OTP logic is
> >> implemented within aspeed_sbc.c.
> >> If there are existing architectural guidelines or design patterns
> >> that should be followed for modeling OTP devices, I would greatly
> >> appreciate your feedback. I am happy to revise the implementation
> >> accordingly and submit updated patches for further review.
> >
> > Adding a 'drive' property to the aspeed-sbc model shouldn't change too
> > much the proposal and it will remove the init_otpmem() routine, which
> > is problematic.
> >
> > Then, we need to find a way to set the 'drive' property of the
> > aspeed-sbc model. I suggest using the same method of the edk2 flash
> > devices of the q35 machine. See ebc29e1beab0. Setting a machine option
> > would set the drive. Something like :
> >
> >   qemu-system-arm -M ast2600-evb,otpmem=otpmem-drive \
> >       -blockdev
> node-name=otpmem,driver=file,filename=/path/to/otpmem.img \
> >       ...
> >
> > This machine option would only be defined for machine types needing
> > it.
> 
> I don't love this solution, but it's what we use elsewhere.  I think Cédric is
> right.

Reply via email to