root/usr/src/uts/common/io/cxgbe/t4nex/cudbg_flash_utils.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source. A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*-
 * Copyright (c) 2017 Chelsio Communications, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/param.h>

#include "common/common.h"
#include "common/t4_regs.h"
#include "cudbg.h"
#include "cudbg_lib_common.h"

enum {
        SF_ATTEMPTS = 10,               /* max retries for SF operations */

        /* flash command opcodes */
        SF_PROG_PAGE    = 2,    /* program page */
        SF_WR_DISABLE   = 4,    /* disable writes */
        SF_RD_STATUS    = 5,    /* read status register */
        SF_WR_ENABLE    = 6,    /* enable writes */
        SF_RD_DATA_FAST = 0xb,  /* read flash */
        SF_RD_ID        = 0x9f, /* read ID */
        SF_ERASE_SECTOR = 0xd8, /* erase sector */
};

int write_flash(struct adapter *adap, u32 start_sec, void *data, u32 size);
int read_flash(struct adapter *adap, u32 start_sec , void *data, u32 size,
                u32 start_address);

void
update_skip_size(struct cudbg_flash_sec_info *sec_info, u32 size)
{
        sec_info->skip_size += size;
}

static
void set_sector_availability(struct cudbg_flash_sec_info *sec_info,
    int sector_nu, int avail)
{
        sector_nu -= CUDBG_START_SEC;
        if (avail)
                set_dbg_bitmap(sec_info->sec_bitmap, sector_nu);
        else
                reset_dbg_bitmap(sec_info->sec_bitmap, sector_nu);
}

/* This function will return empty sector available for filling */
static int
find_empty_sec(struct cudbg_flash_sec_info *sec_info)
{
        int i, index, bit;

        for (i = CUDBG_START_SEC; i < CUDBG_SF_MAX_SECTOR; i++) {
                index = (i - CUDBG_START_SEC) / 8;
                bit = (i - CUDBG_START_SEC) % 8;
                if (!(sec_info->sec_bitmap[index] & (1 << bit)))
                        return i;
        }

        return CUDBG_STATUS_FLASH_FULL;
}

/* This function will get header initially. If header is already there
 * then it will update that header */
static void
update_headers(void *handle, struct cudbg_buffer *dbg_buff,
               u64 timestamp, u32 cur_entity_hdr_offset,
               u32 start_offset, u32 ext_size)
{
        struct cudbg_private *priv = handle;
        struct cudbg_flash_sec_info *sec_info = &priv->sec_info;
        void *sec_hdr;
        struct cudbg_hdr *cudbg_hdr;
        struct cudbg_flash_hdr *flash_hdr;
        struct cudbg_entity_hdr *entity_hdr;
        u32 hdr_offset;
        u32 data_hdr_size;
        u32 total_hdr_size;
        u32 sec_hdr_start_addr;

        data_hdr_size = CUDBG_MAX_ENTITY * sizeof(struct cudbg_entity_hdr) +
                                sizeof(struct cudbg_hdr);
        total_hdr_size = data_hdr_size + sizeof(struct cudbg_flash_hdr);
        sec_hdr_start_addr = CUDBG_SF_SECTOR_SIZE - total_hdr_size;
        sec_hdr  = sec_info->sec_data + sec_hdr_start_addr;

        flash_hdr = (struct cudbg_flash_hdr *)(sec_hdr);
        cudbg_hdr = (struct cudbg_hdr *)dbg_buff->data;

        /* initially initialize flash hdr and copy all data headers and
         * in next calling (else part) copy only current entity header
         */
        if ((start_offset - sec_info->skip_size) == data_hdr_size) {
                flash_hdr->signature = CUDBG_FL_SIGNATURE;
                flash_hdr->major_ver = CUDBG_FL_MAJOR_VERSION;
                flash_hdr->minor_ver = CUDBG_FL_MINOR_VERSION;
                flash_hdr->build_ver = CUDBG_FL_BUILD_VERSION;
                flash_hdr->hdr_len = sizeof(struct cudbg_flash_hdr);
                hdr_offset =  sizeof(struct cudbg_flash_hdr);

                memcpy((void *)((char *)sec_hdr + hdr_offset),
                       (void *)((char *)dbg_buff->data), data_hdr_size);
        } else
                memcpy((void *)((char *)sec_hdr +
                        sizeof(struct cudbg_flash_hdr) +
                        cur_entity_hdr_offset),
                        (void *)((char *)dbg_buff->data +
                        cur_entity_hdr_offset),
                        sizeof(struct cudbg_entity_hdr));

        hdr_offset = data_hdr_size + sizeof(struct cudbg_flash_hdr);
        flash_hdr->data_len = cudbg_hdr->data_len - sec_info->skip_size;
        flash_hdr->timestamp = timestamp;

        entity_hdr = (struct cudbg_entity_hdr *)((char *)sec_hdr +
                      sizeof(struct cudbg_flash_hdr) +
                      cur_entity_hdr_offset);
        /* big entity like mc need to be skipped */
        entity_hdr->start_offset -= sec_info->skip_size;

        cudbg_hdr = (struct cudbg_hdr *)((char *)sec_hdr +
                        sizeof(struct cudbg_flash_hdr));
        cudbg_hdr->data_len = flash_hdr->data_len;
        flash_hdr->data_len += ext_size;
}

/* Write CUDBG data into serial flash */
int
cudbg_write_flash(void *handle, u64 timestamp, void *data,
                  u32 start_offset, u32 cur_entity_hdr_offset,
                  u32 cur_entity_size,
                  u32 ext_size)
{
        struct cudbg_private *priv = handle;
        struct cudbg_init *cudbg_init = &priv->dbg_init;
        struct cudbg_flash_sec_info *sec_info = &priv->sec_info;
        struct adapter *adap = cudbg_init->adap;
        struct cudbg_flash_hdr *flash_hdr = NULL;
        struct cudbg_buffer *dbg_buff = (struct cudbg_buffer *)data;
        u32 data_hdr_size;
        u32 total_hdr_size;
        u32 tmp_size;
        u32 sec_data_offset;
        u32 sec_hdr_start_addr;
        u32 sec_data_size;
        u32 space_left;
        int rc = 0;
        int sec;

        data_hdr_size = CUDBG_MAX_ENTITY * sizeof(struct cudbg_entity_hdr) +
                        sizeof(struct cudbg_hdr);
        total_hdr_size = data_hdr_size + sizeof(struct cudbg_flash_hdr);
        sec_hdr_start_addr = CUDBG_SF_SECTOR_SIZE - total_hdr_size;
        sec_data_size = sec_hdr_start_addr;

        cudbg_init->print(adap->dip, CE_NOTE, "\tWriting %u bytes to flash\n", cur_entity_size);

        /* this function will get header if sec_info->sec_data does not
         * have any header and
         * will update the header if it has header
         */
        update_headers(handle, dbg_buff, timestamp,
                       cur_entity_hdr_offset,
                       start_offset, ext_size);

        if (ext_size) {
                cur_entity_size += sizeof(struct cudbg_entity_hdr);
                start_offset = dbg_buff->offset - cur_entity_size;
        }

        flash_hdr = (struct cudbg_flash_hdr *)(sec_info->sec_data +
                        sec_hdr_start_addr);

        if (flash_hdr->data_len > CUDBG_FLASH_SIZE) {
                rc = CUDBG_STATUS_FLASH_FULL;
                goto out;
        }

        space_left = CUDBG_FLASH_SIZE - flash_hdr->data_len;

        if (cur_entity_size > space_left) {
                rc = CUDBG_STATUS_FLASH_FULL;
                goto out;
        }

        while (cur_entity_size > 0) {
                sec = find_empty_sec(sec_info);
                if (sec_info->par_sec) {
                        sec_data_offset = sec_info->par_sec_offset;
                        set_sector_availability(sec_info, sec_info->par_sec, 0);
                        sec_info->par_sec = 0;
                        sec_info->par_sec_offset = 0;

                } else {
                        sec_info->cur_seq_no++;
                        flash_hdr->sec_seq_no = sec_info->cur_seq_no;
                        sec_data_offset = 0;
                }

                if (cur_entity_size + sec_data_offset > sec_data_size) {
                        tmp_size = sec_data_size - sec_data_offset;
                } else {
                        tmp_size = cur_entity_size;
                        sec_info->par_sec = sec;
                        sec_info->par_sec_offset = cur_entity_size +
                                                  sec_data_offset;
                }

                memcpy((void *)((char *)sec_info->sec_data + sec_data_offset),
                       (void *)((char *)dbg_buff->data + start_offset),
                       tmp_size);

                rc = write_flash(adap, sec, sec_info->sec_data,
                                CUDBG_SF_SECTOR_SIZE);
                if (rc)
                        goto out;

                cur_entity_size -= tmp_size;
                set_sector_availability(sec_info, sec, 1);
                start_offset += tmp_size;
        }
out:
        return rc;
}

int
write_flash(struct adapter *adap, u32 start_sec, void *data, u32 size)
{
        unsigned int addr;
        unsigned int i, n;
        unsigned int sf_sec_size;
        int rc = 0;

        u8 *ptr = (u8 *)data;

        sf_sec_size = adap->params.sf_size/adap->params.sf_nsec;

        addr =  start_sec * CUDBG_SF_SECTOR_SIZE;
        i = DIV_ROUND_UP(size,/* # of sectors spanned */
                        sf_sec_size);

        rc = t4_flash_erase_sectors(adap, start_sec,
                   start_sec + i - 1);
        /*
         * If size == 0 then we're simply erasing the FLASH sectors associated
         * with the on-adapter OptionROM Configuration File.
         */

        if (rc || size == 0)
                goto out;

        /* this will write to the flash up to SF_PAGE_SIZE at a time */
        for (i = 0; i < size; i += SF_PAGE_SIZE) {
                if ((size - i) <  SF_PAGE_SIZE)
                        n = size - i;
                else
                        n = SF_PAGE_SIZE;
                rc = t4_write_flash(adap, addr, n, ptr, 0);
                if (rc)
                        goto out;

                addr += n;
                ptr += n;
        }

        return 0;
out:
        return rc;
}

int
cudbg_read_flash_details(void *handle, struct cudbg_flash_hdr *data)
{
        int rc;
        rc = cudbg_read_flash(handle, (void *)data,
                              sizeof(struct cudbg_flash_hdr), 0);

        return rc;
}

int
cudbg_read_flash_data(void *handle, void *buf, u32 buf_size)
{
        int rc;
        u32 total_hdr_size, data_header_size;
        void *payload = NULL;
        u32 payload_size = 0;

        data_header_size = CUDBG_MAX_ENTITY * sizeof(struct cudbg_entity_hdr) +
                sizeof(struct cudbg_hdr);
        total_hdr_size = data_header_size + sizeof(struct cudbg_flash_hdr);

        /* Copy flash header to buffer */
        rc = cudbg_read_flash(handle, buf, total_hdr_size, 0);
        if (rc != 0)
                goto out;
        payload = (char *)buf + total_hdr_size;
        payload_size  = buf_size - total_hdr_size;

        /* Reading flash data to buf */
        rc = cudbg_read_flash(handle, payload, payload_size, 1);
        if (rc != 0)
                goto out;

out:
        return rc;
}

int
cudbg_read_flash(void *handle, void *data, u32 size, int data_flag)
{
        struct cudbg_private *priv = handle;
        struct cudbg_init *cudbg_init = &priv->dbg_init;
        struct cudbg_flash_sec_info *sec_info = &priv->sec_info;
        struct adapter *adap = cudbg_init->adap;
        struct cudbg_flash_hdr flash_hdr;
        u32 total_hdr_size;
        u32 data_hdr_size;
        u32 sec_hdr_start_addr;
        u32 tmp_size;
        u32 data_offset = 0;
        u32 i, j;
        int rc;

        rc = t4_get_flash_params(adap);
        if (rc) {
                cudbg_init->print(adap->dip, CE_NOTE,"\nGet flash params failed."
                        "Try Again...readflash\n\n");
                return rc;
        }

        data_hdr_size = CUDBG_MAX_ENTITY * sizeof(struct cudbg_entity_hdr) +
                        sizeof(struct cudbg_hdr);
        total_hdr_size = data_hdr_size + sizeof(struct cudbg_flash_hdr);
        sec_hdr_start_addr = CUDBG_SF_SECTOR_SIZE - total_hdr_size;

        if (!data_flag) {
                /* fill header */
                if (!sec_info->max_timestamp) {
                        /* finding max time stamp because it may
                         * have older filled sector also
                         */
                        memset(&flash_hdr, 0, sizeof(struct cudbg_flash_hdr));
                        rc = read_flash(adap, CUDBG_START_SEC, &flash_hdr,
                                sizeof(struct cudbg_flash_hdr),
                                sec_hdr_start_addr);

                        if (flash_hdr.signature == CUDBG_FL_SIGNATURE) {
                                sec_info->max_timestamp = flash_hdr.timestamp;
                        } else {
                                rc = read_flash(adap, CUDBG_START_SEC + 1,
                                        &flash_hdr,
                                        sizeof(struct cudbg_flash_hdr),
                                        sec_hdr_start_addr);

                                if (flash_hdr.signature == CUDBG_FL_SIGNATURE)
                                        sec_info->max_timestamp =
                                                        flash_hdr.timestamp;
                                else {
                                        cudbg_init->print(adap->dip, CE_NOTE,
                                                          "\n\tNo cudbg dump "\
                                                          "found in flash\n\n");
                                        return CUDBG_STATUS_NO_SIGNATURE;
                                }

                        }

                        /* finding max sequence number because max sequenced
                         * sector has updated header
                         */
                        for (i = CUDBG_START_SEC; i <
                                        CUDBG_SF_MAX_SECTOR; i++) {
                                memset(&flash_hdr, 0,
                                       sizeof(struct cudbg_flash_hdr));
                                rc = read_flash(adap, i, &flash_hdr,
                                                sizeof(struct cudbg_flash_hdr),
                                                sec_hdr_start_addr);

                                if (flash_hdr.signature == CUDBG_FL_SIGNATURE &&
                                    sec_info->max_timestamp ==
                                    flash_hdr.timestamp &&
                                    sec_info->max_seq_no <=
                                    flash_hdr.sec_seq_no) {
                                        if (sec_info->max_seq_no ==
                                            flash_hdr.sec_seq_no) {
                                                if (sec_info->hdr_data_len <
                                                    flash_hdr.data_len)
                                                        sec_info->max_seq_sec = i;
                                        } else {
                                                sec_info->max_seq_sec = i;
                                                sec_info->hdr_data_len =
                                                        flash_hdr.data_len;
                                        }
                                        sec_info->max_seq_no = flash_hdr.sec_seq_no;
                                }
                        }
                }
                rc = read_flash(adap, sec_info->max_seq_sec,
                                (struct cudbg_flash_hdr *)data,
                                size, sec_hdr_start_addr);

                if (rc)
                        cudbg_init->print(adap->dip, CE_NOTE,
                                          "Read flash header failed, rc %d\n",
                                          rc);

                return rc;
        }

        /* finding sector sequence sorted */
        for (i = 1; i <= sec_info->max_seq_no; i++) {
                for (j = CUDBG_START_SEC; j < CUDBG_SF_MAX_SECTOR; j++) {
                        memset(&flash_hdr, 0, sizeof(struct cudbg_flash_hdr));
                        rc = read_flash(adap, j, &flash_hdr,
                                sizeof(struct cudbg_flash_hdr),
                                sec_hdr_start_addr);

                        if (flash_hdr.signature ==
                                        CUDBG_FL_SIGNATURE &&
                                        sec_info->max_timestamp ==
                                        flash_hdr.timestamp &&
                                        flash_hdr.sec_seq_no == i) {
                                if (size + total_hdr_size >
                                                CUDBG_SF_SECTOR_SIZE)
                                        tmp_size = CUDBG_SF_SECTOR_SIZE -
                                                total_hdr_size;
                                else
                                        tmp_size =  size;

                                if ((i != sec_info->max_seq_no) ||
                                    (i == sec_info->max_seq_no &&
                                    j == sec_info->max_seq_sec)){
                                        /* filling data buffer with sector data
                                         * except sector header
                                         */
                                        rc = read_flash(adap, j,
                                                        (void *)((char *)data +
                                                        data_offset),
                                                        tmp_size, 0);
                                        data_offset += (tmp_size);
                                        size -= (tmp_size);
                                        break;
                                }
                        }
                }
        }

        return rc;
}

int
read_flash(struct adapter *adap, u32 start_sec , void *data, u32 size,
           u32 start_address)
{
        unsigned int addr, i, n;
        int rc;
        u32 *ptr = (u32 *)data;
        addr = start_sec * CUDBG_SF_SECTOR_SIZE + start_address;
        size = size / 4;
        for (i = 0; i < size; i += SF_PAGE_SIZE) {
                if ((size - i) <  SF_PAGE_SIZE)
                        n = size - i;
                else
                        n = SF_PAGE_SIZE;
                rc = t4_read_flash(adap, addr, n, ptr, 0);
                if (rc)
                        goto out;

                addr = addr + (n*4);
                ptr += n;
        }

        return 0;
out:
        return rc;
}