summaryrefslogtreecommitdiffstats
path: root/meta-xilinx-core/classes-recipe
diff options
context:
space:
mode:
Diffstat (limited to 'meta-xilinx-core/classes-recipe')
-rw-r--r--meta-xilinx-core/classes-recipe/amd_spi_image.bbclass142
-rw-r--r--meta-xilinx-core/classes-recipe/dfx_user_dts.bbclass313
-rw-r--r--meta-xilinx-core/classes-recipe/fw-package.bbclass94
-rw-r--r--meta-xilinx-core/classes-recipe/image-wic-utils.bbclass54
-rw-r--r--meta-xilinx-core/classes-recipe/kernel-simpleimage.bbclass35
-rw-r--r--meta-xilinx-core/classes-recipe/qemuboot-xilinx.bbclass140
-rw-r--r--meta-xilinx-core/classes-recipe/xilinx-fetch-restricted.bbclass35
-rw-r--r--meta-xilinx-core/classes-recipe/xilinx-platform-init.bbclass14
8 files changed, 827 insertions, 0 deletions
diff --git a/meta-xilinx-core/classes-recipe/amd_spi_image.bbclass b/meta-xilinx-core/classes-recipe/amd_spi_image.bbclass
new file mode 100644
index 00000000..ed4c1f87
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/amd_spi_image.bbclass
@@ -0,0 +1,142 @@
1#
2# Copyright (C) 2023, Advanced Micro Devices, Inc. All rights reserved.
3#
4# SPDX-License-Identifier: MIT
5#
6
7QSPI_SIZE ?= "0x2280000"
8
9# Register values
10IDN_REG ?= "0x4D554241"
11VERSION_REG ?= "0x1"
12LENGTH_REG ?= "0x4"
13PERSISTENT_REG ?= "0x01010000"
14
15# QSPI Offsets
16IMAGE_SELECTOR_OFFSET ?= "0x0"
17IMAGE_SELECTOR_BACKUP_OFFSET ?= "0x80000"
18PERSISTENT_REG_OFFSET ?= "0x100000"
19PERSISTENT_REG_BACKUP_OFFSET ?= "0x120000"
20IMAGE_A_OFFSET ?= "0x200000"
21IMAGE_A_IMGSEL_OFFSET ?= "0xF00000"
22IMAGE_B_OFFSET ?= "0xF80000"
23IMAGE_B_IMGSEL_OFFSET ?= "0x1C80000"
24IMAGE_RCVRY_OFFSET ?= "0x1E00000"
25IMAGE_RCVRY_BACKUP_OFFSET ?= "0x2000000"
26VERSION_OFFSET ?= "0x2240000"
27CHECKSUM_OFFSET ?= "0x2250000"
28
29def generate_spi_image(d):
30
31 import io
32 import hashlib
33 import time
34
35 qspi_size = int(d.getVar("QSPI_SIZE") or '0', 0)
36 int(d.getVar("QSPI_SIZE") or '0', 0)
37
38 # Register values
39 idn_reg = int(d.getVar("IDN_REG") or '0', 0)
40 version_reg = int(d.getVar("VERSION_REG") or '0', 0)
41 length_reg = int(d.getVar("LENGTH_REG") or '0', 0)
42 persistent_reg = int(d.getVar("PERSISTENT_REG") or '0', 0)
43
44 # QSPI Offsets
45 image_selector_offset = int(d.getVar("IMAGE_SELECTOR_OFFSET") or '0', 0)
46 image_selector_backup_offset = int(d.getVar("IMAGE_SELECTOR_BACKUP_OFFSET") or '0', 0)
47 persistent_reg_offset = int(d.getVar("PERSISTENT_REG_OFFSET") or '0', 0)
48 persistent_reg_backup_offset = int(d.getVar("PERSISTENT_REG_BACKUP_OFFSET") or '0', 0)
49 image_a_offset = int(d.getVar("IMAGE_A_OFFSET") or '0', 0)
50 image_a_imgsel_offset = int(d.getVar("IMAGE_A_IMGSEL_OFFSET") or '0', 0)
51 image_b_offset = int(d.getVar("IMAGE_B_OFFSET") or '0', 0)
52 image_b_imgsel_offset = int(d.getVar("IMAGE_B_IMGSEL_OFFSET") or '0', 0)
53 image_rcvry_offset = int(d.getVar("IMAGE_RCVRY_OFFSET") or '0', 0)
54 image_rcvry_backup_offset = int(d.getVar("IMAGE_RCVRY_BACKUP_OFFSET") or '0', 0)
55 version_offset = int(d.getVar("VERSION_OFFSET") or '0', 0)
56 checksum_offset = int(d.getVar("CHECKSUM_OFFSET") or '0', 0)
57
58 # QSPI data
59 qspi_data = io.BytesIO()
60 qspi_data.write(b'\xFF' * qspi_size)
61
62 # Image Selector - Primary, Backup, Image A and Image B
63 imgsel_file = d.getVar("DEPLOY_DIR_IMAGE")+"/imgsel-"+d.getVar("MACHINE")+".bin"
64 try:
65 with open(imgsel_file, "rb") as il:
66 imgsel = il.read(-1)
67 except OSError as err:
68 bb.fatal("Unable to open imgsel file: " + str(err))
69
70 qspi_data.seek(image_selector_offset)
71 qspi_data.write(imgsel)
72 qspi_data.seek(image_selector_backup_offset)
73 qspi_data.write(imgsel)
74 qspi_data.seek(image_a_imgsel_offset)
75 qspi_data.write(imgsel)
76 qspi_data.seek(image_b_imgsel_offset)
77 qspi_data.write(imgsel)
78
79 # Persistent Registers - Primary and Backup
80 p_reg = [idn_reg, version_reg, length_reg, persistent_reg, \
81 image_a_offset, image_b_offset, image_rcvry_offset]
82 checksum = 0xffffffff - (0xffffffff & sum(p_reg))
83 p_reg.insert(3, checksum)
84
85 qspi_data.seek(persistent_reg_offset)
86 for value in p_reg:
87 qspi_data.write(value.to_bytes(4, byteorder="little"))
88
89 qspi_data.seek(persistent_reg_backup_offset)
90 for value in p_reg:
91 qspi_data.write(value.to_bytes(4, byteorder="little"))
92
93 # Image A and B - boot.bin
94 try:
95 with open(d.getVar("DEPLOY_DIR_IMAGE")+"/boot.bin", "rb") as bo:
96 bootbin = bo.read(-1)
97 except OSError as err:
98 bb.fatal("Unable to open boot.bin file: " + str(err))
99
100 qspi_data.seek(image_a_offset)
101 qspi_data.write(bootbin)
102 qspi_data.seek(image_b_offset)
103 qspi_data.write(bootbin)
104
105 # Recovery Image & Recovery Image Backup
106 imgrcry_file = d.getVar("DEPLOY_DIR_IMAGE")+"/imgrcry-"+d.getVar("MACHINE")+".bin"
107 try:
108 with open(imgrcry_file, "rb") as iy:
109 imgrcry = iy.read(-1)
110 except OSError as err:
111 bb.fatal("Unable to open imgrcry file: " + str(err))
112
113 qspi_data.seek(image_rcvry_offset)
114 qspi_data.write(imgrcry)
115 qspi_data.seek(image_rcvry_backup_offset)
116 qspi_data.write(imgrcry)
117
118 # Version string and checksum
119 version = d.getVar("QSPI_IMAGE_VERSION")
120 date = time.strftime("%m%d%H%M")
121 machine = d.getVar("MACHINE")[:3]
122 image_name = d.getVar("QSPI_IMAGE_NAME")
123
124 qspi_version = f"{image_name}-{machine}-v{version}-{date}\x00"
125 qspi_data.seek(version_offset)
126 qspi_data.write(qspi_version.encode())
127
128 qspi_sha = hashlib.sha256(qspi_data.getbuffer())
129 qspi_data.seek(checksum_offset)
130 qspi_data.write(qspi_sha.digest())
131
132 # Write the QSPI data to file
133 with open(d.getVar("B") + "/" + d.getVar("IMAGE_NAME") + ".bin", "wb") as sq:
134 sq.write(qspi_data.getbuffer())
135
136do_compile[depends] += "virtual/boot-bin:do_deploy virtual/imgsel:do_deploy virtual/imgrcry:do_deploy"
137
138python amd_spi_image_do_compile() {
139 generate_spi_image(d)
140}
141
142EXPORT_FUNCTIONS do_compile
diff --git a/meta-xilinx-core/classes-recipe/dfx_user_dts.bbclass b/meta-xilinx-core/classes-recipe/dfx_user_dts.bbclass
new file mode 100644
index 00000000..2b699d9d
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/dfx_user_dts.bbclass
@@ -0,0 +1,313 @@
1#
2# Copyright (C) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.
3#
4# SPDX-License-Identifier: MIT
5#
6# This bbclass is inherited by flat, DFx Static and DFx RP firmware recipes.
7# dfx_user_dts.bbclass expects user to generate pl dtsi for flat, DFx Static
8# and DFx RP xsa outside of yocto.
9
10inherit devicetree
11
12DEPENDS = "dtc-native bootgen-native"
13
14# recipes that inherit from this class need to use an appropriate machine
15# override for COMPATIBLE_MACHINE to build successfully; don't allow building
16# for microblaze MACHINE
17COMPATIBLE_MACHINE ?= "^$"
18COMPATIBLE_MACHINE:microblaze = "^$"
19
20PACKAGE_ARCH = "${MACHINE_ARCH}"
21
22PROVIDES = ""
23
24do_fetch[cleandirs] = "${B}"
25
26DT_PADDING_SIZE = "0x1000"
27BOOTGEN_FLAGS ?= " -arch ${SOC_FAMILY} -w ${@bb.utils.contains('SOC_FAMILY','zynqmp','','-process_bitstream bin',d)}"
28
29S ?= "${WORKDIR}"
30FW_DIR ?= ""
31DTSI_PATH ?= ""
32DTBO_PATH ?= ""
33BIT_PATH ?= ""
34BIN_PATH ?= ""
35PDI_PATH ?= ""
36JSON_PATH ?= ""
37XCl_PATH ?= ""
38DT_FILES_PATH = "${S}/${DTSI_PATH}"
39FIRMWARE_NAME_DT_FILE ?= ""
40USER_DTS_FILE ?= ""
41
42FIRMWARE_NAME_DT_FILE[doc] = "DT file which has firmware-name device-tree property"
43USER_DTS_FILE[doc] = "Final DTSI or DTS file which is used for packaging final DT overlay"
44DTSI_PATH[doc] = "Absolute '.dtsi' or ''.dts' file path as input to SRC_URI"
45DTBO_PATH[doc] = "Absolute '.dtbo' file path as input to SRC_URI"
46BIT_PATH[doc] = "Absolute '.bit' file path as input to SRC_URI"
47BIN_PATH[doc] = "Absolute '.bin' file path as input to SRC_URI"
48JSON_PATH[doc] = "Absolute '.json' file path as input to SRC_URI"
49XCL_PATH[doc] = "Absolute '.xclbin' file path as input to SRC_URI"
50
51python() {
52 import re
53 soc_family = d.getVar("SOC_FAMILY")
54 if "git://" in d.getVar("SRC_URI") or "https://" in d.getVar("SRC_URI"):
55 d.setVar("S",'${WORKDIR}/git/'+d.getVar("FW_DIR"))
56 else:
57 dtsi_found = False
58 dtbo_found = False
59 bit_found = False
60 bin_found = False
61 pdi_found = False
62
63 # Required Inputs
64 for s in d.getVar("SRC_URI").split():
65 if s.endswith(('.dtsi', '.dts')):
66 dtsi_found = True
67 d.setVar("DTSI_PATH",os.path.dirname(s.lstrip('file://')))
68 if s.endswith('.dtbo'):
69 if dtbo_found:
70 bb.warn("More then one '.dtbo' file specified in SRC_URI.")
71 dtbo_found = True
72 d.setVar("DTBO_PATH",os.path.dirname(s.lstrip('file://')))
73 if soc_family == "zynq" or soc_family == "zynqmp":
74 if s.endswith('.bit'):
75 if bit_found:
76 bb.warn("More then one '.bit' file specified in SRC_URI.")
77 bit_found = True
78 d.setVar("BIT_PATH",os.path.dirname(s.lstrip('file://')))
79 if s.endswith('.bin'):
80 if bin_found:
81 bb.warn("More then one '.bin' file specified in SRC_URI.")
82 bin_found = True
83 d.setVar("BIN_PATH",os.path.dirname(s.lstrip('file://')))
84 else:
85 if s.endswith('.pdi'):
86 if pdi_found:
87 bb.warn("More then one '.pdi' file specified in SRC_URI.")
88 pdi_found = True
89 d.setVar("PDI_PATH",os.path.dirname(s.lstrip('file://')))
90
91 # Optional input
92 if s.endswith('.json'):
93 d.setVar("JSON_PATH",os.path.dirname(s.lstrip('file://')))
94
95 if s.endswith('.xclbin'):
96 d.setVar("XCL_PATH",os.path.dirname(s.lstrip('file://')))
97
98 # Check for valid combination of input files in SRC_URI
99 # Skip recipe if any of the below conditions are not satisfied.
100 # 1. At least one bit or bin or pdi or dts or dtsi or dtbo should exists.
101 # 2. More than one dtbo.
102 # 3. More than one bit or bin or pdi.
103 # 4. More than one dts and zero dtsi.
104 # 5. More than one dtsi and zero dts.
105 if dtsi_found or dtbo_found or bit_found or bin_found or pdi_found:
106 bb.debug(2, "dtsi or dtbo or bitstream or pdi found in SRC_URI")
107 if bit_found and pdi_found :
108 raise bb.parse.SkipRecipe("Both '.bit' and '.pdi' file found in SRC_URI, this is invalid use case.")
109
110 if bin_found and pdi_found :
111 raise bb.parse.SkipRecipe("Both '.bin' and '.pdi' file found in SRC_URI, this is invalid use case.")
112
113 if bit_found and bin_found:
114 raise bb.parse.SkipRecipe("Both '.bit' and '.bin' file found in SRC_URI, either .bit or .bin file is supported but not both.")
115 else:
116 raise bb.parse.SkipRecipe("Need one '.dtsi' or '.dtbo' or '.bit' or '.bin' or '.pdi' file added to SRC_URI ")
117
118 # Check for valid combination of dtsi and dts files in SRC_URI
119 # Following file combinations are not supported use case.
120 # 1. More than one '.dtsi' and zero '.dts' file.
121 # 2. More than one '.dts' and zero or more than one '.dtsi'file .
122 pattern_dts = re.compile(r'[.]+dts\b')
123 pattern_dtsi = re.compile(r'[.]+dtsi\b')
124 dts_count = len([*re.finditer(pattern_dts, d.getVar('SRC_URI'))])
125 dtsi_count = len([*re.finditer(pattern_dtsi, d.getVar("SRC_URI"))])
126
127 if dtsi_count > 1 and dts_count == 0:
128 raise bb.parse.SkipRecipe("Recipe has more than one '.dtsi' and zero '.dts' found, this is an unsupported use case")
129 elif dts_count > 1:
130 raise bb.parse.SkipRecipe("Recipe has more than one '.dts' and zero or more than one '.dtsi' found, this is an unsupported use case")
131}
132
133# Function to search for dt firmware-name property in dts or dtsi file.
134python find_firmware_file() {
135 import glob
136 pattern_fw = 'firmware-name'
137 search_count = 0
138 for dt_files in glob.iglob((d.getVar('S') + '/' + (d.getVar('DTSI_PATH')) + '/*.dts*'),recursive=True):
139 with open(dt_files, "r") as f:
140 current_fd = f.read()
141 if pattern_fw in current_fd:
142 search_count += 1
143 if search_count > 1:
144 bb.error("firmware-name dt property found in more than one dt files! Please fix the dts or dtsi file.")
145 break
146 else:
147 d.setVar('FIRMWARE_NAME_DT_FILE', os.path.basename(dt_files))
148}
149
150do_configure[prefuncs] += "find_firmware_file"
151
152python do_configure() {
153 import glob, re, shutil
154 soc_family = d.getVar("SOC_FAMILY")
155
156 if bb.utils.contains('MACHINE_FEATURES', 'fpga-overlay', False, True, d):
157 bb.warn("Using dfx_user_dts.bbclass requires fpga-overlay MACHINE_FEATURE to be enabled")
158
159 # Renaming firmware-name using $PN as bitstream/PDI will be renamed using
160 # $PN when generating the bin/pdi file.
161 if os.path.isfile(d.getVar('S') + '/' + (d.getVar('DTSI_PATH') or '') + '/' + d.getVar('FIRMWARE_NAME_DT_FILE')):
162 orig_dtsi = glob.glob(d.getVar('S') + '/' + (d.getVar('DTSI_PATH') or '') + '/' + d.getVar('FIRMWARE_NAME_DT_FILE'))[0]
163 new_dtsi = d.getVar('S') + '/' + (d.getVar('DTSI_PATH') or '') + '/pl.dtsi_firmwarename'
164 with open(new_dtsi, 'w') as newdtsi:
165 with open(orig_dtsi) as olddtsi:
166 for line in olddtsi:
167 if soc_family == 'zynq' or soc_family == 'zynqmp':
168 newdtsi.write(re.sub('firmware-name.*\".*\"','firmware-name = \"'+d.getVar('PN')+'.bin\"',line))
169 else:
170 newdtsi.write(re.sub('firmware-name.*\".*\"','firmware-name = \"'+d.getVar('PN')+'.pdi\"',line))
171 shutil.move(new_dtsi,orig_dtsi)
172}
173
174do_compile[prefuncs] += "find_firmware_file"
175
176python devicetree_do_compile:append() {
177 import glob, subprocess, shutil
178 soc_family = d.getVar("SOC_FAMILY")
179
180 dtbo_count = sum(1 for f in glob.iglob((d.getVar('S') + '/*.dtbo'),recursive=True) if os.path.isfile(f))
181 bin_count = sum(1 for f in glob.iglob((d.getVar('S') + '/*.bin'),recursive=True) if os.path.isfile(f))
182 # Skip devicetree do_compile task if input file is dtbo or bin in SRC_URI
183 if not dtbo_count and not bin_count:
184 # Convert .bit to .bin format only if dtsi is input.
185 # In case of dtbo as input, bbclass doesn't know if firmware-name is .bit
186 # or .bin format and corresponding file name. Hence we are not doing .bin
187 # conversion.
188 if soc_family == 'zynq' or soc_family == 'zynqmp' and glob.glob(d.getVar('S') + '/' +(d.getVar('DTSI_PATH') or '') + '/' + d.getVar('FIRMWARE_NAME_DT_FILE')):
189 pn = d.getVar('PN')
190 biffile = pn + '.bif'
191 with open(biffile, 'w') as f:
192 f.write('all:\n{\n\t' + glob.glob(d.getVar('S') + '/' + (d.getVar('BIT_PATH') or '') + '/*.bit')[0] + '\n}')
193
194 bootgenargs = ["bootgen"] + (d.getVar("BOOTGEN_FLAGS") or "").split()
195 bootgenargs += ["-image", biffile, "-o", pn + ".bin"]
196 subprocess.run(bootgenargs, check = True)
197
198 # In Zynq7k using both "-process_bitstream bin" and "-o" in bootgen flag,
199 # to convert bit file to bin format, "-o" option will not be effective
200 # and generated output file name is ${S}+${BIT_PATH}/<bit_file_name>.bin
201 # file, Hence we need to rename this file from <bit_file_name>.bin to
202 # ${PN}.bin which matches the firmware name in dtbo and move
203 # ${PN}.bin to ${B} directory.
204 if soc_family == 'zynq':
205 src_bitbin_file = glob.glob(d.getVar('S') + '/' + (d.getVar('BIT_PATH') or '') + '/*.bin')[0]
206 dst_bitbin_file = d.getVar('B') + '/' + pn + '.bin'
207 shutil.move(src_bitbin_file, dst_bitbin_file)
208
209 if not os.path.isfile(pn + ".bin"):
210 bb.fatal("Couldn't find %s file, Enable '-log trace' in BOOTGEN_FLAGS" \
211 "and check bootgen_log.txt" % (d.getVar('B') + '/' + pn + '.bin'))
212}
213
214# If user inputs both dtsi and dts files then device-tree will generate dtbo
215# files for each dt file, Hence to package the firmware pick the right user dt
216# overlay file.
217python find_user_dts_overlay_file() {
218 import glob
219 dtbo_count = sum(1 for f in glob.iglob((d.getVar('S') + '/' + d.getVar('DTBO_PATH') + '/*.dtbo'),recursive=True) if os.path.isfile(f))
220 dts_count = sum(1 for f in glob.iglob((d.getVar('S') + '/' + d.getVar('DTSI_PATH') + '/*.dts'),recursive=True) if os.path.isfile(f))
221 dtsi_count = sum(1 for f in glob.iglob((d.getVar('S') + '/' + d.getVar('DTSI_PATH') + '/*.dtsi'),recursive=True) if os.path.isfile(f))
222 # Set USER_DTS_FILE if input file is dts/dtsi in SRC_URI else skip operation.
223 if not dtbo_count and dts_count or dtsi_count:
224 if dtsi_count == 1 and dts_count == 0:
225 dts_file = glob.glob(d.getVar('S') + '/' + (d.getVar('DTSI_PATH') or '') + '/*.dtsi')[0]
226 elif dtsi_count >=0 and dts_count == 1:
227 dts_file = glob.glob(d.getVar('S') + '/' + (d.getVar('DTSI_PATH') or '') + '/*.dts')[0]
228 else:
229 dts_file = ''
230
231 d.setVar('USER_DTS_FILE', os.path.splitext(os.path.basename(dts_file))[0])
232 elif dtbo_count:
233 bb.debug(2, "Firmware recipe input file is dtbo in SRC_URI")
234 else:
235 bb.debug(2, "Firmware recipe input file is bit/bin/pdi in SRC_URI")
236}
237
238do_install[prefuncs] += "find_user_dts_overlay_file"
239
240do_install() {
241 install -d ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/
242
243 # Install dtbo
244 # In case of dtbo as input, dtbo will be copied from directly from ${S}
245 # In case of dtsi as input, dtbo will be copied from directly from ${B}
246 # If more than one dtbo file is found then fatal the task.
247 # If no dtbo file is found add warning message as in some use case if IP
248 # doesn't have any driver then user can load pdi/bit/bin file.
249 if [ `ls ${S}/*.dtbo | wc -l` -eq 1 ]; then
250 install -Dm 0644 ${S}/*.dtbo ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/
251 elif [ `ls ${S}/*.dtbo | wc -l` -gt 1 ]; then
252 bbfatal "Multiple DTBO found, use the right DTBO in SRC_URI from the following:\n$(basename -a ${S}/*.dtbo)"
253 elif [ -f ${B}/${USER_DTS_FILE}.dtbo ]; then
254 install -Dm 0644 ${B}/${USER_DTS_FILE}.dtbo ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/${PN}.dtbo
255 else
256 bbnote "A dtbo ending '.dtbo' expected but not found in ${S} or ${B}, This means firmware can be loaded without dtbo dependency."
257 fi
258
259 # Install bit or bin if soc family is zynq-7000 or zynqmp.
260 # In case of dtbo as input or no dtbo exists in ${B}, then .bit or .bin will
261 # be copied from directly from ${S} without renaming the .bit/.bin name to
262 # ${PN}.bit/${PN}.bin.
263 # if more than one .bit/.bin file is found then fatal the task.
264 # if no .bit/.bin file is found then fatal the task.
265 if [ "${SOC_FAMILY}" = "zynq" ] || [ "${SOC_FAMILY}" = "zynqmp" ]; then
266 if [ `ls ${S}/*.bit | wc -l` -gt 1 ]; then
267 bbfatal "Multiple .bit found, use the right .bit in SRC_URI from the following:\n$(basename -a ${S}/*.bit)"
268 elif [ `ls ${S}/*.bin | wc -l` -gt 1 ]; then
269 bbfatal "Multiple .bin found, use the right .bin in SRC_URI from the following:\n$(basename -a ${S}/*.bin)"
270 elif [ `ls ${S}/*.bit | wc -l` -eq 1 ] && [ ! -f ${B}/${USER_DTS_FILE}.dtbo ]; then
271 install -Dm 0644 ${S}/*.bit ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/
272 elif [ `ls ${S}/*.bin | wc -l` -eq 1 ]; then
273 install -Dm 0644 ${S}/*.bin ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/
274 elif [ -f ${B}/${PN}.bin ] && [ -f ${B}/${USER_DTS_FILE}.dtbo ]; then
275 install -Dm 0644 ${B}/${PN}.bin ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/${PN}.bin
276 else
277 bbfatal "A bitstream file with '.bit' or '.bin' expected but not found"
278 fi
279 fi
280
281 # Install pdi if soc family is versal or new silicon.
282 # In case of dtbo as input or no dtbo exists in ${B}, then .pdi will be copied
283 # from directly from ${S} without renaming the pdi name to ${PN}.pdi
284 # if more than one .pdi file is found then fail the task.
285 # In case of Versal DFx Static, only static dtbo can be loaded as BOOT.bin
286 # already contains static pdi. bbclass is not smart enough to determine
287 # whether it is static pdi or not, hence change fatal to warn if no PDI is found.
288 if [ "${SOC_FAMILY}" != "zynq" ] && [ "${SOC_FAMILY}" != "zynqmp" ]; then
289 if [ `ls ${S}/*.pdi | wc -l` -eq 1 ] && [ ! -f ${B}/${USER_DTS_FILE}.dtbo ]; then
290 install -Dm 0644 ${S}/*.pdi ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/
291 elif [ `ls ${S}/*.pdi | wc -l` -gt 1 ]; then
292 bbfatal "Multiple PDI found, use the right PDI in SRC_URI from the following:\n$(basename -a ${S}/*.pdi)"
293 elif [ `ls ${S}/*.pdi | wc -l` -eq 1 ] && [ -f ${B}/${USER_DTS_FILE}.dtbo ]; then
294 install -Dm 0644 ${S}/*.pdi ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/${PN}.pdi
295 else
296 bbwarn "A PDI file with '.pdi' expected but not found"
297 fi
298 fi
299
300 # Install xclbin
301 if ls ${S}/${XCL_PATH}/*.xclbin >/dev/null 2>&1; then
302 install -Dm 0644 ${S}/${XCL_PATH}/*.xclbin ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/${PN}.xclbin
303 fi
304
305 # Install shell.json or accel.json
306 if [ -f ${S}/${JSON_PATH}/shell.json ] || [ -f ${S}/${JSON_PATH}/accel.json ]; then
307 install -Dm 0644 ${S}/${JSON_PATH}/*.json ${D}/${nonarch_base_libdir}/firmware/xilinx/${PN}/
308 fi
309}
310
311do_deploy[noexec] = "1"
312
313FILES:${PN} += "${nonarch_base_libdir}/firmware/xilinx/${PN}"
diff --git a/meta-xilinx-core/classes-recipe/fw-package.bbclass b/meta-xilinx-core/classes-recipe/fw-package.bbclass
new file mode 100644
index 00000000..e9847d33
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/fw-package.bbclass
@@ -0,0 +1,94 @@
1#
2# Copyright (C) 2024, Advanced Micro Devices, Inc. All rights reserved.
3#
4# SPDX-License-Identifier: MIT
5#
6# This bbclass provides infrastructure to package and deploy firmware baremetal
7# or freertos application elf or bin files to linux root filesystem under
8# /lib/firmware directory.
9
10inherit deploy
11
12INHERIT_DEFAULT_DEPENDS = "1"
13
14# Since we're just copying, we can run any config.
15COMPATIBLE_HOST = ".*"
16
17PACKAGE_ARCH = "${MACHINE_ARCH}"
18
19# Default expects the user to provide the fw app in the deploy directory.
20# A machine, multiconfig, or local.conf should override this.
21FW_NAME ??= ""
22TARGET_MC ??= ""
23FW_DEPENDS ??= ""
24FW_MCDEPENDS ??= ""
25FW_DEPLOY_DIR ??= "${DEPLOY_DIR_IMAGE}"
26FW_DEPLOY_DIR[vardepsexclude] += "TOPDIR"
27FW_IMAGE_NAME ??= "${FW_NAME}-${MACHINE}-${TARGET_MC}"
28
29# Default is for the multilib case (without the extension .elf/.bin)
30FW_FILE ??= "${FW_DEPLOY_DIR}/${FW_IMAGE_NAME}"
31FW_FILE[vardepsexclude] = "FW_DEPLOY_DIR"
32
33do_fetch[depends] += "${FW_DEPENDS}"
34do_fetch[mcdepends] += "${FW_MCDEPENDS}"
35
36# Set default destination directory is /lib/firmware, user can change this value
37# to /boot directory depending on requirement.
38DESTDIR ??= "${nonarch_base_libdir}/firmware/xilinx"
39SYSROOT_DIRS += "/boot"
40
41INSANE_SKIP:${PN} = "arch"
42INSANE_SKIP:${PN}-dbg = "arch"
43
44# Disable buildpaths QA check warnings.
45INSANE_SKIP:${PN} += "buildpaths"
46
47do_install() {
48 if [ ! -e ${FW_FILE}.elf ]; then
49 echo "Unable to find FW_FILE (${FW_FILE}.elf)"
50 exit 1
51 fi
52
53 install -Dm 0644 ${FW_FILE}.elf ${D}${DESTDIR}/${FW_IMAGE_NAME}.elf
54}
55
56# If the item is already in OUR deploy_image_dir, nothing to deploy!
57SHOULD_DEPLOY = "${@'false' if (d.getVar('FW_FILE')).startswith(d.getVar('DEPLOY_DIR_IMAGE')) else 'true'}"
58do_deploy() {
59 # If the item is already in OUR deploy_image_dir, nothing to deploy!
60 if ${SHOULD_DEPLOY}; then
61 install -Dm 0644 ${FW_FILE}.elf ${DEPLOYDIR}/${FW_IMAGE_NAME}.elf
62 install -Dm 0644 ${FW_FILE}.bin ${DEPLOYDIR}/${FW_IMAGE_NAME}.bin
63 fi
64}
65
66FILES:${PN} += "${DESTDIR}/${FW_IMAGE_NAME}*"
67
68def check_fw_vars(d):
69 # If both are blank, the user MUST pass in the path to the firmware!
70 if not d.getVar('FW_DEPENDS') and not d.getVar('FW_MCDEPENDS'):
71 # Don't cache this, as the items on disk can change!
72 d.setVar('BB_DONT_CACHE', '1')
73
74 msg = ""
75 fail = False
76 if not os.path.exists(d.getVar('FW_FILE') + ".elf"):
77 msg = msg + "The expected file %s.elf is not available. " % d.getVar('FW_FILE')
78 fail = True
79 if not os.path.exists(d.getVar('FW_FILE') + ".bin"):
80 msg = msg + "The expected file %s.bin is not available. " % d.getVar('FW_FILE')
81 fail = True
82 if fail:
83 if not d.getVar('WITHIN_EXT_SDK'):
84 raise bb.parse.SkipRecipe("%s\nSee the meta-xilinx-core README." % msg)
85 else:
86 # We found the file, so be sure to track it
87 d.setVar('SRC_URI', 'file://${FW_FILE}.elf file://${FW_FILE}.bin')
88 d.setVarFlag('do_install', 'file-checksums', '${FW_FILE}.elf:True ${FW_FILE}.bin:True')
89 d.setVarFlag('do_deploy', 'file-checksums', '${FW_FILE}.elf:True ${FW_FILE}.bin:True')
90
91python() {
92 # Need to allow bbappends to change the check
93 check_fw_vars(d)
94}
diff --git a/meta-xilinx-core/classes-recipe/image-wic-utils.bbclass b/meta-xilinx-core/classes-recipe/image-wic-utils.bbclass
new file mode 100644
index 00000000..41ad8148
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/image-wic-utils.bbclass
@@ -0,0 +1,54 @@
1# Helper/utility functions to work with the IMAGE_BOOT_FILES variable and its
2# expected behvaior with regards to the contents of the DEPLOY_DIR_IMAGE.
3#
4# The use of these functions assume that the deploy directory is populated with
5# any dependent files/etc. Such that the recipe using these functions depends
6# on the recipe that provides the files being used/queried.
7
8def boot_files_split_expand(d):
9 # IMAGE_BOOT_FILES has extra renaming info in the format '<source>;<target>'
10 for f in (d.getVar("IMAGE_BOOT_FILES") or "").split(" "):
11 parts = f.split(";", 1)
12 sources = [parts[0].strip()]
13 if "*" in parts[0]:
14 # has glob part
15 import glob
16 deployroot = d.getVar("DEPLOY_DIR_IMAGE")
17 sources = []
18 for i in glob.glob(os.path.join(deployroot, parts[0])):
19 sources.append(os.path.basename(i))
20
21 # for all sources, yield an entry
22 for s in sources:
23 if len(parts) == 2:
24 if parts[1].endswith('/'):
25 yield s, '%s%s' % (parts[1].strip(), s)
26 else:
27 yield s, parts[1].strip()
28 yield s, s
29
30def boot_files_bitstream(d):
31 expectedfiles = [("bitstream", True)]
32 expectedexts = [(".bit", True), (".bin", False)]
33 # search for bitstream paths, use the renamed file. First matching is used
34 for source, target in boot_files_split_expand(d):
35 # skip boot.bin and u-boot.bin, it is not a bitstream
36 skip = ["boot.bin", "u-boot.bin"]
37 if source in skip or target in skip:
38 continue
39
40 for e, t in expectedfiles:
41 if source == e or target == e:
42 return target, t
43 for e, t in expectedexts:
44 if source.endswith(e) or target.endswith(e):
45 return target, t
46 return "", False
47
48def boot_files_dtb_filepath(d):
49 dtbs = (d.getVar("IMAGE_BOOT_FILES") or "").split(" ")
50 for source, target in boot_files_split_expand(d):
51 if target.endswith(".dtb"):
52 return target
53 return ""
54
diff --git a/meta-xilinx-core/classes-recipe/kernel-simpleimage.bbclass b/meta-xilinx-core/classes-recipe/kernel-simpleimage.bbclass
new file mode 100644
index 00000000..110ee254
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/kernel-simpleimage.bbclass
@@ -0,0 +1,35 @@
1python __anonymous () {
2 kerneltypes = set((d.getVar("KERNEL_IMAGETYPE") or "").split())
3 kerneltypes |= set((d.getVar("KERNEL_IMAGETYPES") or "").split())
4 if any(t.startswith("simpleImage.") for t in kerneltypes):
5 # Enable building of simpleImage
6 bb.build.addtask('do_prep_simpleimage', 'do_compile', 'do_configure', d)
7 uarch = d.getVar("UBOOT_ARCH")
8 if uarch == "microblaze":
9 d.appendVarFlag('do_prep_simpleimage', 'depends', ' virtual/dtb:do_populate_sysroot')
10}
11
12do_prep_simpleimage[dirs] += "${B}"
13do_prep_simpleimage () {
14 install -d ${B}/arch/${ARCH}/boot/dts
15 for type in ${KERNEL_IMAGETYPES} ; do
16 if [ -z "${type##*simpleImage*}" ] && [ ${ARCH} = "microblaze" ]; then
17 ext="${type##*.}"
18 # Microblaze simpleImage only works with dts file
19 cp ${RECIPE_SYSROOT}/boot/devicetree/${ext}.dts ${B}/arch/${ARCH}/boot/dts/
20 fi
21 done
22}
23
24do_deploy:append () {
25 for type in ${KERNEL_IMAGETYPES} ; do
26 if [ -z "${type##*simpleImage*}" ] && [ ${ARCH} = "microblaze" ]; then
27 base_name=${type}-${KERNEL_IMAGE_NAME}
28 install -m 0644 ${KERNEL_OUTPUT_DIR}/${type}.strip $deployDir/${base_name}.strip
29 install -m 0644 ${KERNEL_OUTPUT_DIR}/${type}.unstrip $deployDir/${base_name}.unstrip
30 symlink_name=${type}-${KERNEL_IMAGE_LINK_NAME}
31 ln -sf ${base_name}.strip $deployDir/${symlink_name}.strip
32 ln -sf ${base_name}.unstrip $deployDir/${symlink_name}.unstrip
33 fi
34 done
35}
diff --git a/meta-xilinx-core/classes-recipe/qemuboot-xilinx.bbclass b/meta-xilinx-core/classes-recipe/qemuboot-xilinx.bbclass
new file mode 100644
index 00000000..7466ab5e
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/qemuboot-xilinx.bbclass
@@ -0,0 +1,140 @@
1
2# enable the overrides for the context of the conf only
3OVERRIDES .= ":qemuboot-xilinx"
4
5# Default machine targets for Xilinx QEMU (FDT Generic)
6# Allow QB_MACHINE to be overridden by a BSP config
7QB_MACHINE ?= "${QB_MACHINE_XILINX}"
8QB_RNG=""
9QB_MACHINE_XILINX:aarch64 = "-machine arm-generic-fdt"
10QB_MACHINE_XILINX:arm = "-M arm-generic-fdt-7series"
11QB_MACHINE_XILINX:microblaze = "-M microblaze-fdt-plnx"
12
13QB_SYSTEM_NAME ?= "${@qemu_target_binary(d)}"
14QB_DEFAULT_FSTYPE ?= "${@qemu_rootfs_params(d,'fstype')}"
15QB_ROOTFS ?= "${@qemu_rootfs_params(d,'rootfs')}"
16QB_ROOTFS_OPT ?= "${@qemu_rootfs_params(d,'rootfs-opt')}"
17QB_DTB ?= "${@qemu_default_dtb(d)}"
18
19# defaults
20QB_DEFAULT_KERNEL ?= "none"
21QB_DEFAULT_KERNEL:zynq ?= "${@'zImage' if \
22 d.getVar('INITRAMFS_IMAGE_BUNDLE') != '1' else 'zImage-initramfs-${MACHINE}.bin'}"
23QB_DEFAULT_KERNEL:microblaze ?= "${@'simpleImage.mb' if \
24 d.getVar('INITRAMFS_IMAGE_BUNDLE') != '1' else 'simpleImage.mb-initramfs-${MACHINE}.bin'}"
25
26inherit qemuboot
27
28def qemu_target_binary(data):
29 package_arch = data.getVar("PACKAGE_ARCH")
30 qemu_target_binary = (data.getVar("QEMU_TARGET_BINARY_%s" % package_arch) or "")
31 if qemu_target_binary:
32 return qemu_target_binary
33
34 target_arch = data.getVar("TARGET_ARCH")
35 if target_arch == "microblazeeb":
36 target_arch = "microblaze"
37 elif target_arch == "aarch64":
38 target_arch += "-multiarch"
39 elif target_arch == "arm":
40 target_arch = "aarch64"
41 return "qemu-system-%s" % target_arch
42
43def qemu_add_extra_args(data):
44 initramfs_image = data.getVar('INITRAMFS_IMAGE') or ""
45 bundle_image = data.getVar('INITRAMFS_IMAGE_BUNDLE') or ""
46 deploy_dir = data.getVar('DEPLOY_DIR_IMAGE') or ""
47 machine_name = data.getVar('MACHINE') or ""
48 soc_family = data.getVar('SOC_FAMILY') or ""
49 qb_extra_args = ''
50 # Add kernel image and boot.scr to qemu boot command when initramfs_image supplied
51 kernel_name = ''
52 bootscr_image = '%s/boot.scr' % deploy_dir
53 if soc_family in ('zynqmp', 'versal'):
54 kernel_name = 'Image'
55 bootscr_loadaddr = '0x20000000'
56 if initramfs_image:
57 kernel_image = '%s/%s' % (deploy_dir, kernel_name)
58 if bundle_image == "1":
59 kernel_image = '%s/%s-initramfs-%s.bin' % (deploy_dir, kernel_name, machine_name)
60 kernel_loadaddr = '0x200000'
61 if kernel_name:
62 qb_extra_args = ' -device loader,file=%s,addr=%s,force-raw=on' % (kernel_image, kernel_loadaddr)
63 qb_extra_args += ' -device loader,file=%s,addr=%s,force-raw=on' % (bootscr_image, bootscr_loadaddr)
64 if soc_family == 'versal':
65 qb_extra_args += ' -boot mode=5'
66 else:
67 if soc_family in ('zynqmp', 'versal'):
68 qb_extra_args = ' -boot mode=5'
69 return qb_extra_args
70
71def qemu_rootfs_params(data, param):
72 initramfs_image = data.getVar('INITRAMFS_IMAGE') or ""
73 bundle_image = data.getVar('INITRAMFS_IMAGE_BUNDLE') or ""
74 soc_family = data.getVar('SOC_FAMILY') or ""
75 tune_features = (data.getVar('TUNE_FEATURES') or []).split()
76 if 'microblaze' in tune_features:
77 soc_family = 'microblaze'
78 soc_variant = data.getVar('SOC_VARIANT') or ""
79
80 if param == 'rootfs':
81 return 'none' if bundle_image == "1" else ''
82
83 elif param == 'fstype':
84 fstype_dict = {
85 "microblaze": "cpio.gz",
86 "zynq": "cpio.gz",
87 "zynqmp": "cpio.gz.u-boot",
88 "versal": "cpio.gz.u-boot.qemu-sd-fatimg"
89 }
90 if not initramfs_image:
91 image_fs = data.getVar('IMAGE_FSTYPES')
92 if 'wic.qemu-sd' in image_fs:
93 return 'wic.qemu-sd'
94 if soc_family not in fstype_dict:
95 return ""
96 return fstype_dict[soc_family]
97
98 elif param == 'rootfs-opt':
99 sd_index = "1"
100 if soc_family == 'zynq':
101 sd_index = "0"
102 if soc_family == 'versal' and soc_variant == 'net':
103 sd_index = "0"
104
105 # Device is using a disk
106 if not initramfs_image:
107 return ' -drive if=sd,index=%s,file=@ROOTFS@,format=raw' % (sd_index)
108
109 # Device is using a ramdisk
110 if soc_family not in ('zynq', 'microblaze'):
111 return ' -device loader,file=@ROOTFS@,addr=0x04000000,force-raw=on'
112
113 # Ramdisk must be compiled into the kernel
114 return ''
115
116def qemu_default_dtb(data):
117 if data.getVar("IMAGE_BOOT_FILES", True):
118 dtbs = data.getVar("IMAGE_BOOT_FILES", True).split(" ")
119 # IMAGE_BOOT_FILES has extra renaming info in the format '<source>;<target>'
120 # Note: Wildcard sources work here only because runqemu expands them at run time
121 dtbs = [f.split(";")[0] for f in dtbs]
122 dtbs = [f for f in dtbs if f.endswith(".dtb")]
123 if len(dtbs) != 0:
124 return dtbs[0]
125 return ""
126
127def qemu_default_serial(data):
128 if data.getVar("SERIAL_CONSOLES", True):
129 first_console = data.getVar("SERIAL_CONSOLES", True).split(" ")[0]
130 speed, console = first_console.split(";", 1)
131 # zynqmp uses earlycon and stdout (in dtb)
132 if "zynqmp" in data.getVar("MACHINEOVERRIDES", True).split(":"):
133 return ""
134 return "console=%s,%s earlyprintk" % (console, speed)
135 return ""
136
137def qemu_zynqmp_unhalt(data, multiarch):
138 if multiarch:
139 return "-global xlnx,zynqmp-boot.cpu-num=0 -global xlnx,zynqmp-boot.use-pmufw=true"
140 return "-device loader,addr=0xfd1a0104,data=0x8000000e,data-len=4 -device loader,addr=0xfd1a0104,data=0x8000000e,data-len=4"
diff --git a/meta-xilinx-core/classes-recipe/xilinx-fetch-restricted.bbclass b/meta-xilinx-core/classes-recipe/xilinx-fetch-restricted.bbclass
new file mode 100644
index 00000000..a778ec7d
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/xilinx-fetch-restricted.bbclass
@@ -0,0 +1,35 @@
1# This class is setup to override the default fetching for the target recipe.
2# When fetching it forces PREMIRROR only fetching so that no attempts are made
3# to fetch the Xilinx downloads that are restricted to authenticated users only.
4#
5# The purpose of this class is to allow for automatation with pre-downloaded
6# content or content that is available with curated/user defined pre-mirrors
7# and or pre-populated downloads/ directories.
8
9python do_fetch() {
10 xilinx_restricted_url = "xilinx.com/member/forms/download"
11
12 src_uri = (d.getVar('SRC_URI') or "").split()
13 if len(src_uri) == 0:
14 return
15
16 for i in src_uri:
17 if xilinx_restricted_url in i:
18 # force the use of premirrors only, do not attempt download from xilinx.com
19 d.setVar("BB_FETCH_PREMIRRORONLY", "1")
20 break
21
22 try:
23 fetcher = bb.fetch2.Fetch(src_uri, d)
24 fetcher.download()
25 except bb.fetch2.NetworkAccess as e:
26 if xilinx_restricted_url in e.url:
27 # fatal on access to xilinx.com restricted downloads, print the url for manual download
28 bb.fatal("The following download cannot be fetched automatically. " \
29 "Please manually download the file and place it in the 'downloads' directory (or on an available PREMIRROR).\n" \
30 " %s" % (e.url.split(";")[0]))
31 else:
32 bb.fatal(str(e))
33 except bb.fetch2.BBFetchException as e:
34 bb.fatal(str(e))
35}
diff --git a/meta-xilinx-core/classes-recipe/xilinx-platform-init.bbclass b/meta-xilinx-core/classes-recipe/xilinx-platform-init.bbclass
new file mode 100644
index 00000000..99f7863a
--- /dev/null
+++ b/meta-xilinx-core/classes-recipe/xilinx-platform-init.bbclass
@@ -0,0 +1,14 @@
1# This class should be included by any recipe that wants to access or provide
2# the platform init source files which are used to initialize a Zynq or ZynqMP
3# SoC.
4
5# Define the path to the xilinx platform init code/headers
6PLATFORM_INIT_DIR ?= "/usr/src/xilinx-platform-init"
7
8PLATFORM_INIT_STAGE_DIR = "${STAGING_DIR_HOST}${PLATFORM_INIT_DIR}"
9
10# Target files use for platform init
11PLATFORM_INIT_FILES ?= ""
12PLATFORM_INIT_FILES:zynq = "ps7_init_gpl.c ps7_init_gpl.h"
13PLATFORM_INIT_FILES:zynqmp = "psu_init_gpl.c psu_init_gpl.h"
14