| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "os/mynewt.h" |
| |
| #if MYNEWT_VAL(LOG_FCB) |
| |
| #include <string.h> |
| |
| #include "flash_map/flash_map.h" |
| #include "fcb/fcb.h" |
| #include "log/log.h" |
| |
| /* Assume the flash alignment requirement is no stricter than 8. */ |
| #define LOG_FCB_MAX_ALIGN 8 |
| |
| static struct flash_area sector; |
| |
| static int log_fcb_rtr_erase(struct log *log, void *arg); |
| |
| static int |
| log_fcb_start_append(struct log *log, int len, struct fcb_entry *loc) |
| { |
| struct fcb *fcb; |
| struct fcb_log *fcb_log; |
| struct flash_area *old_fa; |
| int rc = 0; |
| #if MYNEWT_VAL(LOG_STATS) |
| int cnt; |
| #endif |
| |
| fcb_log = (struct fcb_log *)log->l_arg; |
| fcb = &fcb_log->fl_fcb; |
| |
| while (1) { |
| rc = fcb_append(fcb, len, loc); |
| if (rc == 0) { |
| break; |
| } |
| |
| if (rc != FCB_ERR_NOSPACE) { |
| goto err; |
| } |
| |
| if (fcb_log->fl_entries) { |
| rc = log_fcb_rtr_erase(log, fcb_log); |
| if (rc) { |
| goto err; |
| } |
| continue; |
| } |
| |
| old_fa = fcb->f_oldest; |
| (void)old_fa; /* to avoid #ifdefs everywhere... */ |
| |
| #if MYNEWT_VAL(LOG_STATS) |
| rc = fcb_area_info(fcb, NULL, &cnt, NULL); |
| if (rc == 0) { |
| LOG_STATS_INCN(log, lost, cnt); |
| } |
| #endif |
| rc = fcb_rotate(fcb); |
| if (rc) { |
| goto err; |
| } |
| |
| #if MYNEWT_VAL(LOG_STORAGE_WATERMARK) |
| /* |
| * FCB was rotated successfully so let's check if watermark was within |
| * oldest flash area which was erased. If yes, then move watermark to |
| * beginning of current oldest area. |
| */ |
| if ((fcb_log->fl_watermark_off >= old_fa->fa_off) && |
| (fcb_log->fl_watermark_off < old_fa->fa_off + old_fa->fa_size)) { |
| fcb_log->fl_watermark_off = fcb->f_oldest->fa_off; |
| } |
| #endif |
| |
| |
| } |
| |
| err: |
| return (rc); |
| } |
| |
| static int |
| log_fcb_append(struct log *log, void *buf, int len) |
| { |
| struct fcb *fcb; |
| struct fcb_entry loc; |
| struct fcb_log *fcb_log; |
| int rc; |
| |
| fcb_log = (struct fcb_log *)log->l_arg; |
| fcb = &fcb_log->fl_fcb; |
| |
| rc = log_fcb_start_append(log, len, &loc); |
| if (rc) { |
| goto err; |
| } |
| |
| rc = flash_area_write(loc.fe_area, loc.fe_data_off, buf, len); |
| if (rc) { |
| goto err; |
| } |
| |
| rc = fcb_append_finish(fcb, &loc); |
| |
| err: |
| return (rc); |
| } |
| |
| /** |
| * Calculates the number of message body bytes that should be included after |
| * the entry header in the first write. Inclusion of body bytes is necessary |
| * to satisfy the flash hardware's write alignment restrictions. |
| */ |
| static int |
| log_fcb_hdr_body_bytes(uint8_t align) |
| { |
| uint8_t mod; |
| |
| /* Assume power-of-two alignment for faster modulo calculation. */ |
| assert((align & (align - 1)) == 0); |
| |
| mod = sizeof (struct log_entry_hdr) & (align - 1); |
| if (mod == 0) { |
| return 0; |
| } |
| |
| return align - mod; |
| } |
| |
| static int |
| log_fcb_append_body(struct log *log, const struct log_entry_hdr *hdr, |
| const void *body, int body_len) |
| { |
| uint8_t buf[sizeof (struct log_entry_hdr) + LOG_FCB_MAX_ALIGN - 1]; |
| struct fcb *fcb; |
| struct fcb_entry loc; |
| struct fcb_log *fcb_log; |
| const uint8_t *u8p; |
| int hdr_alignment; |
| int chunk_sz; |
| int rc; |
| |
| fcb_log = (struct fcb_log *)log->l_arg; |
| fcb = &fcb_log->fl_fcb; |
| |
| if (fcb->f_align > LOG_FCB_MAX_ALIGN) { |
| return SYS_ENOTSUP; |
| } |
| |
| rc = log_fcb_start_append(log, sizeof *hdr + body_len, &loc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| /* Append the first chunk (header + x-bytes of body, where x is however |
| * many bytes are required to increase the chunk size up to a multiple of |
| * the flash alignment). |
| */ |
| hdr_alignment = log_fcb_hdr_body_bytes(fcb->f_align); |
| if (hdr_alignment > body_len) { |
| chunk_sz = sizeof *hdr + body_len; |
| } else { |
| chunk_sz = sizeof *hdr + hdr_alignment; |
| } |
| |
| u8p = body; |
| |
| memcpy(buf, hdr, sizeof *hdr); |
| memcpy(buf + sizeof *hdr, u8p, hdr_alignment); |
| |
| rc = flash_area_write(loc.fe_area, loc.fe_data_off, buf, chunk_sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| /* Append the remainder of the message body. */ |
| |
| u8p += hdr_alignment; |
| body_len -= hdr_alignment; |
| |
| if (body_len > 0) { |
| rc = flash_area_write(loc.fe_area, loc.fe_data_off + chunk_sz, u8p, |
| body_len); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| rc = fcb_append_finish(fcb, &loc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| log_fcb_write_mbuf(struct fcb_entry *loc, const struct os_mbuf *om) |
| { |
| int rc; |
| |
| while (om) { |
| rc = flash_area_write(loc->fe_area, loc->fe_data_off, |
| om->om_data, om->om_len); |
| if (rc != 0) { |
| return SYS_EIO; |
| } |
| |
| loc->fe_data_off += om->om_len; |
| om = SLIST_NEXT(om, om_next); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| log_fcb_append_mbuf(struct log *log, const struct os_mbuf *om) |
| { |
| struct fcb *fcb; |
| struct fcb_entry loc; |
| struct fcb_log *fcb_log; |
| int len; |
| int rc; |
| |
| fcb_log = (struct fcb_log *)log->l_arg; |
| fcb = &fcb_log->fl_fcb; |
| |
| /* This function expects to be able to write each mbuf without any |
| * buffering. |
| */ |
| /* XXX: Fix this; buffer mbuf writes to satisfy flash alignment. */ |
| if (fcb->f_align != 1) { |
| return SYS_ENOTSUP; |
| } |
| |
| len = os_mbuf_len(om); |
| rc = log_fcb_start_append(log, len, &loc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = log_fcb_write_mbuf(&loc, om); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = fcb_append_finish(fcb, &loc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| log_fcb_append_mbuf_body(struct log *log, const struct log_entry_hdr *hdr, |
| const struct os_mbuf *om) |
| { |
| struct fcb *fcb; |
| struct fcb_entry loc; |
| struct fcb_log *fcb_log; |
| int len; |
| int rc; |
| |
| fcb_log = (struct fcb_log *)log->l_arg; |
| fcb = &fcb_log->fl_fcb; |
| |
| /* This function expects to be able to write each mbuf without any |
| * buffering. |
| */ |
| if (fcb->f_align != 1) { |
| return SYS_ENOTSUP; |
| } |
| |
| len = sizeof *hdr + os_mbuf_len(om); |
| rc = log_fcb_start_append(log, len, &loc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = flash_area_write(loc.fe_area, loc.fe_data_off, hdr, sizeof *hdr); |
| if (rc != 0) { |
| return rc; |
| } |
| loc.fe_data_off += sizeof *hdr; |
| |
| rc = log_fcb_write_mbuf(&loc, om); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = fcb_append_finish(fcb, &loc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| log_fcb_read(struct log *log, void *dptr, void *buf, uint16_t offset, |
| uint16_t len) |
| { |
| struct fcb_entry *loc; |
| int rc; |
| |
| loc = (struct fcb_entry *)dptr; |
| |
| if (offset + len > loc->fe_data_len) { |
| len = loc->fe_data_len - offset; |
| } |
| rc = flash_area_read(loc->fe_area, loc->fe_data_off + offset, buf, len); |
| if (rc == 0) { |
| return len; |
| } else { |
| return 0; |
| } |
| } |
| |
| static int |
| log_fcb_read_mbuf(struct log *log, void *dptr, struct os_mbuf *om, |
| uint16_t offset, uint16_t len) |
| { |
| struct fcb_entry *loc; |
| uint8_t data[128]; |
| uint16_t read_len; |
| uint16_t rem_len; |
| int rc; |
| |
| loc = (struct fcb_entry *)dptr; |
| |
| if (offset + len > loc->fe_data_len) { |
| len = loc->fe_data_len - offset; |
| } |
| |
| rem_len = len; |
| |
| while (rem_len > 0) { |
| read_len = min(rem_len, sizeof(data)); |
| rc = flash_area_read(loc->fe_area, loc->fe_data_off + offset, data, |
| read_len); |
| if (rc) { |
| goto done; |
| } |
| |
| rc = os_mbuf_append(om, data, read_len); |
| if (rc) { |
| goto done; |
| } |
| |
| rem_len -= read_len; |
| offset += read_len; |
| } |
| |
| done: |
| return len - rem_len; |
| } |
| |
| static int |
| log_fcb_walk(struct log *log, log_walk_func_t walk_func, |
| struct log_offset *log_offset) |
| { |
| struct fcb *fcb; |
| struct fcb_entry loc; |
| struct fcb_entry *locp; |
| int rc; |
| |
| rc = 0; |
| fcb = &((struct fcb_log *)log->l_arg)->fl_fcb; |
| |
| memset(&loc, 0, sizeof(loc)); |
| |
| /* |
| * if timestamp for request is < 0, return last log entry |
| */ |
| if (log_offset->lo_ts < 0) { |
| locp = &fcb->f_active; |
| rc = walk_func(log, log_offset, (void *)locp, locp->fe_data_len); |
| } else { |
| while (fcb_getnext(fcb, &loc) == 0) { |
| rc = walk_func(log, log_offset, (void *) &loc, loc.fe_data_len); |
| if (rc) { |
| break; |
| } |
| } |
| } |
| return (rc); |
| } |
| |
| static int |
| log_fcb_flush(struct log *log) |
| { |
| return fcb_clear(&((struct fcb_log *)log->l_arg)->fl_fcb); |
| } |
| |
| static int |
| log_fcb_registered(struct log *log) |
| { |
| #if MYNEWT_VAL(LOG_STORAGE_WATERMARK) |
| struct fcb_log *fl; |
| struct fcb *fcb; |
| struct fcb_entry loc; |
| |
| fl = (struct fcb_log *)log->l_arg; |
| fcb = &fl->fl_fcb; |
| |
| /* Set watermark to first element */ |
| memset(&loc, 0, sizeof(loc)); |
| if (fcb_getnext(fcb, &loc)) { |
| fl->fl_watermark_off = loc.fe_area->fa_off + loc.fe_elem_off; |
| } else { |
| fl->fl_watermark_off = fcb->f_oldest->fa_off; |
| } |
| #endif |
| return 0; |
| } |
| |
| #if MYNEWT_VAL(LOG_STORAGE_INFO) |
| static int |
| log_fcb_storage_info(struct log *log, struct log_storage_info *info) |
| { |
| struct fcb_log *fl; |
| struct fcb *fcb; |
| struct flash_area *fa; |
| uint32_t el_min; |
| uint32_t el_max; |
| uint32_t fa_min; |
| uint32_t fa_max; |
| uint32_t fa_size; |
| uint32_t fa_used; |
| int rc; |
| |
| fl = (struct fcb_log *)log->l_arg; |
| fcb = &fl->fl_fcb; |
| |
| rc = os_mutex_pend(&fcb->f_mtx, OS_WAIT_FOREVER); |
| if (rc && rc != OS_NOT_STARTED) { |
| return FCB_ERR_ARGS; |
| } |
| |
| /* |
| * Calculate location of 1st entry. |
| * We assume 1st log entry starts at beginning of oldest sector in FCB. |
| * This is because even if 1st entry is in the middle of sector (is this |
| * even possible?) we will never use free space before it thus that space |
| * can be also considered used. |
| */ |
| el_min = fcb->f_oldest->fa_off; |
| |
| /* Calculate end location of last entry */ |
| el_max = fcb->f_active.fe_area->fa_off + fcb->f_active.fe_elem_off; |
| |
| /* Sectors assigned to FCB are guaranteed to be contiguous */ |
| fa = &fcb->f_sectors[0]; |
| fa_min = fa->fa_off; |
| fa = &fcb->f_sectors[fcb->f_sector_cnt - 1]; |
| fa_max = fa->fa_off + fa->fa_size; |
| fa_size = fa_max - fa_min; |
| |
| /* Calculate used size */ |
| fa_used = el_max - el_min; |
| if ((int32_t)fa_used < 0) { |
| fa_used += fa_size; |
| } |
| |
| info->size = fa_size; |
| info->used = fa_used; |
| |
| #if MYNEWT_VAL(LOG_STORAGE_WATERMARK) |
| /* Calculate used size */ |
| fa_used = el_max - fl->fl_watermark_off; |
| if ((int32_t)fa_used < 0) { |
| fa_used += fa_size; |
| } |
| info->used_unread = fa_used; |
| #endif |
| |
| os_mutex_release(&fcb->f_mtx); |
| |
| return 0; |
| } |
| #endif |
| |
| #if MYNEWT_VAL(LOG_STORAGE_WATERMARK) |
| static int |
| log_fcb_set_watermark(struct log *log, uint32_t index) |
| { |
| struct fcb_log *fl; |
| struct fcb *fcb; |
| struct log_entry_hdr ueh; |
| struct fcb_entry loc; |
| uint32_t end_off; |
| int rc; |
| |
| fl = (struct fcb_log *)log->l_arg; |
| fcb = &fl->fl_fcb; |
| |
| memset(&loc, 0, sizeof(loc)); |
| end_off = fcb->f_oldest->fa_off; |
| rc = 0; |
| |
| while (fcb_getnext(fcb, &loc) == 0) { |
| rc = log_fcb_read(log, &loc, &ueh, 0, sizeof(ueh)); |
| |
| if (rc != sizeof(ueh)) { |
| break; |
| } |
| |
| if (ueh.ue_index > index) { |
| break; |
| } |
| |
| /* Move end offset max pointer to end of this element */ |
| end_off = loc.fe_area->fa_off + loc.fe_data_off + loc.fe_data_len; |
| } |
| |
| /* End of last element found is now our watermark */ |
| fl->fl_watermark_off = end_off; |
| |
| return rc; |
| } |
| #endif |
| |
| /** |
| * Copies one log entry from source fcb to destination fcb |
| * @param src_fcb, dst_fcb |
| * @return 0 on success; non-zero on error |
| */ |
| static int |
| log_fcb_copy_entry(struct log *log, struct fcb_entry *entry, |
| struct fcb *dst_fcb) |
| { |
| struct log_entry_hdr ueh; |
| char data[LOG_PRINTF_MAX_ENTRY_LEN + sizeof(ueh)]; |
| int dlen; |
| int rc; |
| struct fcb *fcb_tmp; |
| |
| rc = log_fcb_read(log, entry, &ueh, 0, sizeof(ueh)); |
| if (rc != sizeof(ueh)) { |
| goto err; |
| } |
| |
| dlen = min(entry->fe_data_len, LOG_PRINTF_MAX_ENTRY_LEN + sizeof(ueh)); |
| |
| rc = log_fcb_read(log, entry, data, 0, dlen); |
| if (rc < 0) { |
| goto err; |
| } |
| data[rc] = '\0'; |
| |
| /* Changing the fcb to be logged to be dst fcb */ |
| fcb_tmp = &((struct fcb_log *)log->l_arg)->fl_fcb; |
| |
| log->l_arg = dst_fcb; |
| rc = log_fcb_append(log, data, dlen); |
| log->l_arg = fcb_tmp; |
| if (rc) { |
| goto err; |
| } |
| |
| err: |
| return (rc); |
| } |
| |
| /** |
| * Copies log entries from source fcb to destination fcb |
| * @param src_fcb, dst_fcb, element offset to start copying |
| * @return 0 on success; non-zero on error |
| */ |
| static int |
| log_fcb_copy(struct log *log, struct fcb *src_fcb, struct fcb *dst_fcb, |
| uint32_t offset) |
| { |
| struct fcb_entry entry; |
| int rc; |
| |
| rc = 0; |
| |
| memset(&entry, 0, sizeof(entry)); |
| while (!fcb_getnext(src_fcb, &entry)) { |
| if (entry.fe_elem_off < offset) { |
| continue; |
| } |
| rc = log_fcb_copy_entry(log, &entry, dst_fcb); |
| if (rc) { |
| break; |
| } |
| } |
| |
| return (rc); |
| } |
| |
| /** |
| * Flushes the log while restoring specified number of entries |
| * using image scratch |
| * @param src_fcb, dst_fcb |
| * @return 0 on success; non-zero on error |
| */ |
| static int |
| log_fcb_rtr_erase(struct log *log, void *arg) |
| { |
| struct fcb_log *fcb_log; |
| struct fcb fcb_scratch; |
| struct fcb *fcb; |
| const struct flash_area *ptr; |
| struct fcb_entry entry; |
| int rc; |
| |
| rc = 0; |
| if (!log) { |
| rc = -1; |
| goto err; |
| } |
| |
| fcb_log = (struct fcb_log *)arg; |
| fcb = (struct fcb *)fcb_log; |
| |
| memset(&fcb_scratch, 0, sizeof(fcb_scratch)); |
| |
| if (flash_area_open(FLASH_AREA_IMAGE_SCRATCH, &ptr)) { |
| goto err; |
| } |
| sector = *ptr; |
| fcb_scratch.f_sectors = §or; |
| fcb_scratch.f_sector_cnt = 1; |
| fcb_scratch.f_magic = 0x7EADBADF; |
| fcb_scratch.f_version = g_log_info.li_version; |
| |
| flash_area_erase(§or, 0, sector.fa_size); |
| rc = fcb_init(&fcb_scratch); |
| if (rc) { |
| goto err; |
| } |
| |
| /* Calculate offset of n-th last entry */ |
| rc = fcb_offset_last_n(fcb, fcb_log->fl_entries, &entry); |
| if (rc) { |
| goto err; |
| } |
| |
| /* Copy to scratch */ |
| rc = log_fcb_copy(log, fcb, &fcb_scratch, entry.fe_elem_off); |
| if (rc) { |
| goto err; |
| } |
| |
| /* Flush log */ |
| rc = log_fcb_flush(log); |
| if (rc) { |
| goto err; |
| } |
| |
| /* Copy back from scratch */ |
| rc = log_fcb_copy(log, &fcb_scratch, fcb, 0); |
| |
| err: |
| return (rc); |
| } |
| |
| const struct log_handler log_fcb_handler = { |
| .log_type = LOG_TYPE_STORAGE, |
| .log_read = log_fcb_read, |
| .log_read_mbuf = log_fcb_read_mbuf, |
| .log_append = log_fcb_append, |
| .log_append_body = log_fcb_append_body, |
| .log_append_mbuf = log_fcb_append_mbuf, |
| .log_append_mbuf_body = log_fcb_append_mbuf_body, |
| .log_walk = log_fcb_walk, |
| .log_flush = log_fcb_flush, |
| #if MYNEWT_VAL(LOG_STORAGE_INFO) |
| .log_storage_info = log_fcb_storage_info, |
| #endif |
| #if MYNEWT_VAL(LOG_STORAGE_WATERMARK) |
| .log_set_watermark = log_fcb_set_watermark, |
| #endif |
| .log_registered = log_fcb_registered, |
| }; |
| |
| #endif |