1 /*
2  * Copyright (c) 2017-2021 Nordic Semiconductor ASA
3  * Copyright (c) 2015 Runtime Inc
4  * Copyright (c) 2017 Linaro Ltd
5  * Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
6  *
7  * SPDX-License-Identifier: Apache-2.0
8  */
9 
10 #include <errno.h>
11 
12 #include <zephyr/types.h>
13 #include <stddef.h>
14 #include <sys/types.h>
15 #include <zephyr/device.h>
16 #include <zephyr/storage/flash_map.h>
17 #include <zephyr/drivers/flash.h>
18 #include <zephyr/init.h>
19 
20 struct layout_data {
21 	uint32_t area_idx;
22 	uint32_t area_off;
23 	uint32_t area_len;
24 	void *ret;        /* struct flash_area* or struct flash_sector* */
25 	uint32_t ret_idx;
26 	uint32_t ret_len;
27 	int status;
28 };
29 
30 /*
31  * Check if a flash_page_foreach() callback should exit early, due to
32  * one of the following conditions:
33  *
34  * - The flash page described by "info" is before the area of interest
35  *   described in "data"
36  * - The flash page is after the end of the area
37  * - There are too many flash pages on the device to fit in the array
38  *   held in data->ret. In this case, data->status is set to -ENOMEM.
39  *
40  * The value to return to flash_page_foreach() is stored in
41  * "bail_value" if the callback should exit early.
42  */
should_bail(const struct flash_pages_info * info,struct layout_data * data,bool * bail_value)43 static bool should_bail(const struct flash_pages_info *info,
44 						struct layout_data *data,
45 						bool *bail_value)
46 {
47 	if (info->start_offset < data->area_off) {
48 		*bail_value = true;
49 		return true;
50 	} else if (info->start_offset >= data->area_off + data->area_len) {
51 		*bail_value = false;
52 		return true;
53 	} else if (data->ret_idx >= data->ret_len) {
54 		data->status = -ENOMEM;
55 		*bail_value = false;
56 		return true;
57 	}
58 
59 	return false;
60 }
61 
62 /*
63  * Generic page layout discovery routine. This is kept separate to
64  * support both the deprecated flash_area_to_sectors() and the current
65  * flash_area_get_sectors(). A lot of this can be inlined once
66  * flash_area_to_sectors() is removed.
67  */
flash_area_layout(int idx,uint32_t * cnt,void * ret,flash_page_cb cb,struct layout_data * cb_data)68 static int flash_area_layout(int idx, uint32_t *cnt, void *ret,
69 flash_page_cb cb, struct layout_data *cb_data)
70 {
71 	const struct device *flash_dev;
72 	const struct flash_area *fa;
73 	int rc = flash_area_open(idx, &fa);
74 
75 	if (rc < 0 || fa == NULL) {
76 		return -EINVAL;
77 	}
78 
79 	cb_data->area_idx = idx;
80 	cb_data->area_off = fa->fa_off;
81 	cb_data->area_len = fa->fa_size;
82 
83 	cb_data->ret = ret;
84 	cb_data->ret_idx = 0U;
85 	cb_data->ret_len = *cnt;
86 	cb_data->status = 0;
87 
88 	flash_dev = fa->fa_dev;
89 	flash_area_close(fa);
90 	if (flash_dev == NULL) {
91 		return -ENODEV;
92 	}
93 
94 	flash_page_foreach(flash_dev, cb, cb_data);
95 
96 	if (cb_data->status == 0) {
97 		*cnt = cb_data->ret_idx;
98 	}
99 
100 	return cb_data->status;
101 }
102 
get_sectors_cb(const struct flash_pages_info * info,void * datav)103 static bool get_sectors_cb(const struct flash_pages_info *info, void *datav)
104 {
105 	struct layout_data *data = datav;
106 	struct flash_sector *ret = data->ret;
107 	bool bail;
108 
109 	if (should_bail(info, data, &bail)) {
110 		return bail;
111 	}
112 
113 	ret[data->ret_idx].fs_off = info->start_offset - data->area_off;
114 	ret[data->ret_idx].fs_size = info->size;
115 	data->ret_idx++;
116 
117 	return true;
118 }
119 
flash_area_get_sectors(int idx,uint32_t * cnt,struct flash_sector * ret)120 int flash_area_get_sectors(int idx, uint32_t *cnt, struct flash_sector *ret)
121 {
122 	struct layout_data data;
123 
124 	return flash_area_layout(idx, cnt, ret, get_sectors_cb, &data);
125 }
126