summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Scheible <johnscheible@google.com>2021-10-26 13:45:19 -0700
committerJohn Scheible <johnscheible@google.com>2021-10-28 16:14:12 -0700
commitde16475969ca7dfaecf7144ece32b929cf193861 (patch)
tree962335a74133193541bf8c398a7cdf445f79a610
parent497cbdab5ea4f505beac7bb6d686bb4c3c504e8c (diff)
downloadgs201-de16475969ca7dfaecf7144ece32b929cf193861.tar.gz
gxp: First import from development branch
Squash at commit 48fe5786f1779890538d14f709b26063698c3711 Signed-off-by: John Scheible <johnscheible@google.com> Change-Id: I208434dddde40c08b9fc4d5da072ba10541992a2
-rw-r--r--.clang-format548
-rw-r--r--.gitignore9
-rw-r--r--Kconfig38
-rw-r--r--Makefile59
-rw-r--r--gxp-bpm.c53
-rw-r--r--gxp-bpm.h37
-rw-r--r--gxp-config.h31
-rw-r--r--gxp-csrs.h115
-rw-r--r--gxp-debug-dump.c551
-rw-r--r--gxp-debug-dump.h164
-rw-r--r--gxp-debugfs.c220
-rw-r--r--gxp-debugfs.h15
-rw-r--r--gxp-dma-iommu.c826
-rw-r--r--gxp-dma-iommu.h20
-rw-r--r--gxp-dma-rmem.c576
-rw-r--r--gxp-dma.h264
-rw-r--r--gxp-doorbell.c57
-rw-r--r--gxp-doorbell.h18
-rw-r--r--gxp-firmware-data.c710
-rw-r--r--gxp-firmware-data.h56
-rw-r--r--gxp-firmware.c454
-rw-r--r--gxp-firmware.h40
-rw-r--r--gxp-host-device-structs.h262
-rw-r--r--gxp-hw-mailbox-driver.c247
-rw-r--r--gxp-internal.h186
-rw-r--r--gxp-iova.h19
-rw-r--r--gxp-lpm.c267
-rw-r--r--gxp-lpm.h106
-rw-r--r--gxp-mailbox-driver.h48
-rw-r--r--gxp-mailbox-regs.h53
-rw-r--r--gxp-mailbox.c811
-rw-r--r--gxp-mailbox.h181
-rw-r--r--gxp-mapping.c344
-rw-r--r--gxp-mapping.h55
-rw-r--r--gxp-platform.c700
-rw-r--r--gxp-range-alloc.c118
-rw-r--r--gxp-range-alloc.h94
-rw-r--r--gxp-sw-mailbox-driver.c484
-rw-r--r--gxp-tmp.h60
-rw-r--r--gxp-vd.c212
-rw-r--r--gxp-vd.h57
-rw-r--r--gxp.h220
-rw-r--r--mm-backport.h33
43 files changed, 9418 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..10dc5a9
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,548 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# clang-format configuration file. Intended for clang-format >= 4.
+#
+# For more information, see:
+#
+# Documentation/process/clang-format.rst
+# https://clang.llvm.org/docs/ClangFormat.html
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ #AfterExternBlock: false # Unknown to clang-format-5.0
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ #SplitEmptyFunction: true # Unknown to clang-format-4.0
+ #SplitEmptyRecord: true # Unknown to clang-format-4.0
+ #SplitEmptyNamespace: true # Unknown to clang-format-4.0
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+#CompactNamespaces: false # Unknown to clang-format-4.0
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+#FixNamespaceComments: false # Unknown to clang-format-4.0
+
+# Taken from:
+# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \
+# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
+# | sort | uniq
+ForEachMacros:
+ - 'apei_estatus_for_each_section'
+ - 'ata_for_each_dev'
+ - 'ata_for_each_link'
+ - '__ata_qc_for_each'
+ - 'ata_qc_for_each'
+ - 'ata_qc_for_each_raw'
+ - 'ata_qc_for_each_with_internal'
+ - 'ax25_for_each'
+ - 'ax25_uid_for_each'
+ - '__bio_for_each_bvec'
+ - 'bio_for_each_bvec'
+ - 'bio_for_each_bvec_all'
+ - 'bio_for_each_integrity_vec'
+ - '__bio_for_each_segment'
+ - 'bio_for_each_segment'
+ - 'bio_for_each_segment_all'
+ - 'bio_list_for_each'
+ - 'bip_for_each_vec'
+ - 'bitmap_for_each_clear_region'
+ - 'bitmap_for_each_set_region'
+ - 'blkg_for_each_descendant_post'
+ - 'blkg_for_each_descendant_pre'
+ - 'blk_queue_for_each_rl'
+ - 'bond_for_each_slave'
+ - 'bond_for_each_slave_rcu'
+ - 'bpf_for_each_spilled_reg'
+ - 'btree_for_each_safe128'
+ - 'btree_for_each_safe32'
+ - 'btree_for_each_safe64'
+ - 'btree_for_each_safel'
+ - 'card_for_each_dev'
+ - 'cgroup_taskset_for_each'
+ - 'cgroup_taskset_for_each_leader'
+ - 'cpufreq_for_each_entry'
+ - 'cpufreq_for_each_entry_idx'
+ - 'cpufreq_for_each_valid_entry'
+ - 'cpufreq_for_each_valid_entry_idx'
+ - 'css_for_each_child'
+ - 'css_for_each_descendant_post'
+ - 'css_for_each_descendant_pre'
+ - 'device_for_each_child_node'
+ - 'dma_fence_chain_for_each'
+ - 'do_for_each_ftrace_op'
+ - 'drm_atomic_crtc_for_each_plane'
+ - 'drm_atomic_crtc_state_for_each_plane'
+ - 'drm_atomic_crtc_state_for_each_plane_state'
+ - 'drm_atomic_for_each_plane_damage'
+ - 'drm_client_for_each_connector_iter'
+ - 'drm_client_for_each_modeset'
+ - 'drm_connector_for_each_possible_encoder'
+ - 'drm_for_each_bridge_in_chain'
+ - 'drm_for_each_connector_iter'
+ - 'drm_for_each_crtc'
+ - 'drm_for_each_encoder'
+ - 'drm_for_each_encoder_mask'
+ - 'drm_for_each_fb'
+ - 'drm_for_each_legacy_plane'
+ - 'drm_for_each_plane'
+ - 'drm_for_each_plane_mask'
+ - 'drm_for_each_privobj'
+ - 'drm_mm_for_each_hole'
+ - 'drm_mm_for_each_node'
+ - 'drm_mm_for_each_node_in_range'
+ - 'drm_mm_for_each_node_safe'
+ - 'flow_action_for_each'
+ - 'for_each_active_dev_scope'
+ - 'for_each_active_drhd_unit'
+ - 'for_each_active_iommu'
+ - 'for_each_aggr_pgid'
+ - 'for_each_available_child_of_node'
+ - 'for_each_bio'
+ - 'for_each_board_func_rsrc'
+ - 'for_each_bvec'
+ - 'for_each_card_auxs'
+ - 'for_each_card_auxs_safe'
+ - 'for_each_card_components'
+ - 'for_each_card_dapms'
+ - 'for_each_card_pre_auxs'
+ - 'for_each_card_prelinks'
+ - 'for_each_card_rtds'
+ - 'for_each_card_rtds_safe'
+ - 'for_each_card_widgets'
+ - 'for_each_card_widgets_safe'
+ - 'for_each_cgroup_storage_type'
+ - 'for_each_child_of_node'
+ - 'for_each_clear_bit'
+ - 'for_each_clear_bit_from'
+ - 'for_each_cmsghdr'
+ - 'for_each_compatible_node'
+ - 'for_each_component_dais'
+ - 'for_each_component_dais_safe'
+ - 'for_each_comp_order'
+ - 'for_each_console'
+ - 'for_each_cpu'
+ - 'for_each_cpu_and'
+ - 'for_each_cpu_not'
+ - 'for_each_cpu_wrap'
+ - 'for_each_dapm_widgets'
+ - 'for_each_dev_addr'
+ - 'for_each_dev_scope'
+ - 'for_each_displayid_db'
+ - 'for_each_dma_cap_mask'
+ - 'for_each_dpcm_be'
+ - 'for_each_dpcm_be_rollback'
+ - 'for_each_dpcm_be_safe'
+ - 'for_each_dpcm_fe'
+ - 'for_each_drhd_unit'
+ - 'for_each_dss_dev'
+ - 'for_each_efi_memory_desc'
+ - 'for_each_efi_memory_desc_in_map'
+ - 'for_each_element'
+ - 'for_each_element_extid'
+ - 'for_each_element_id'
+ - 'for_each_endpoint_of_node'
+ - 'for_each_evictable_lru'
+ - 'for_each_fib6_node_rt_rcu'
+ - 'for_each_fib6_walker_rt'
+ - 'for_each_free_mem_pfn_range_in_zone'
+ - 'for_each_free_mem_pfn_range_in_zone_from'
+ - 'for_each_free_mem_range'
+ - 'for_each_free_mem_range_reverse'
+ - 'for_each_func_rsrc'
+ - 'for_each_hstate'
+ - 'for_each_if'
+ - 'for_each_iommu'
+ - 'for_each_ip_tunnel_rcu'
+ - 'for_each_irq_nr'
+ - 'for_each_link_codecs'
+ - 'for_each_link_cpus'
+ - 'for_each_link_platforms'
+ - 'for_each_lru'
+ - 'for_each_matching_node'
+ - 'for_each_matching_node_and_match'
+ - 'for_each_member'
+ - 'for_each_mem_region'
+ - 'for_each_memblock_type'
+ - 'for_each_memcg_cache_index'
+ - 'for_each_mem_pfn_range'
+ - '__for_each_mem_range'
+ - 'for_each_mem_range'
+ - '__for_each_mem_range_rev'
+ - 'for_each_mem_range_rev'
+ - 'for_each_migratetype_order'
+ - 'for_each_msi_entry'
+ - 'for_each_msi_entry_safe'
+ - 'for_each_net'
+ - 'for_each_net_continue_reverse'
+ - 'for_each_netdev'
+ - 'for_each_netdev_continue'
+ - 'for_each_netdev_continue_rcu'
+ - 'for_each_netdev_continue_reverse'
+ - 'for_each_netdev_feature'
+ - 'for_each_netdev_in_bond_rcu'
+ - 'for_each_netdev_rcu'
+ - 'for_each_netdev_reverse'
+ - 'for_each_netdev_safe'
+ - 'for_each_net_rcu'
+ - 'for_each_new_connector_in_state'
+ - 'for_each_new_crtc_in_state'
+ - 'for_each_new_mst_mgr_in_state'
+ - 'for_each_new_plane_in_state'
+ - 'for_each_new_private_obj_in_state'
+ - 'for_each_node'
+ - 'for_each_node_by_name'
+ - 'for_each_node_by_type'
+ - 'for_each_node_mask'
+ - 'for_each_node_state'
+ - 'for_each_node_with_cpus'
+ - 'for_each_node_with_property'
+ - 'for_each_nonreserved_multicast_dest_pgid'
+ - 'for_each_of_allnodes'
+ - 'for_each_of_allnodes_from'
+ - 'for_each_of_cpu_node'
+ - 'for_each_of_pci_range'
+ - 'for_each_old_connector_in_state'
+ - 'for_each_old_crtc_in_state'
+ - 'for_each_old_mst_mgr_in_state'
+ - 'for_each_oldnew_connector_in_state'
+ - 'for_each_oldnew_crtc_in_state'
+ - 'for_each_oldnew_mst_mgr_in_state'
+ - 'for_each_oldnew_plane_in_state'
+ - 'for_each_oldnew_plane_in_state_reverse'
+ - 'for_each_oldnew_private_obj_in_state'
+ - 'for_each_old_plane_in_state'
+ - 'for_each_old_private_obj_in_state'
+ - 'for_each_online_cpu'
+ - 'for_each_online_node'
+ - 'for_each_online_pgdat'
+ - 'for_each_pci_bridge'
+ - 'for_each_pci_dev'
+ - 'for_each_pci_msi_entry'
+ - 'for_each_pcm_streams'
+ - 'for_each_physmem_range'
+ - 'for_each_populated_zone'
+ - 'for_each_possible_cpu'
+ - 'for_each_present_cpu'
+ - 'for_each_prime_number'
+ - 'for_each_prime_number_from'
+ - 'for_each_process'
+ - 'for_each_process_thread'
+ - 'for_each_property_of_node'
+ - 'for_each_registered_fb'
+ - 'for_each_requested_gpio'
+ - 'for_each_requested_gpio_in_range'
+ - 'for_each_reserved_mem_range'
+ - 'for_each_reserved_mem_region'
+ - 'for_each_rtd_codec_dais'
+ - 'for_each_rtd_codec_dais_rollback'
+ - 'for_each_rtd_components'
+ - 'for_each_rtd_cpu_dais'
+ - 'for_each_rtd_cpu_dais_rollback'
+ - 'for_each_rtd_dais'
+ - 'for_each_set_bit'
+ - 'for_each_set_bit_from'
+ - 'for_each_set_clump8'
+ - 'for_each_sg'
+ - 'for_each_sg_dma_page'
+ - 'for_each_sg_page'
+ - 'for_each_sgtable_dma_page'
+ - 'for_each_sgtable_dma_sg'
+ - 'for_each_sgtable_page'
+ - 'for_each_sgtable_sg'
+ - 'for_each_sibling_event'
+ - 'for_each_subelement'
+ - 'for_each_subelement_extid'
+ - 'for_each_subelement_id'
+ - '__for_each_thread'
+ - 'for_each_thread'
+ - 'for_each_unicast_dest_pgid'
+ - 'for_each_wakeup_source'
+ - 'for_each_zone'
+ - 'for_each_zone_zonelist'
+ - 'for_each_zone_zonelist_nodemask'
+ - 'fwnode_for_each_available_child_node'
+ - 'fwnode_for_each_child_node'
+ - 'fwnode_graph_for_each_endpoint'
+ - 'gadget_for_each_ep'
+ - 'genradix_for_each'
+ - 'genradix_for_each_from'
+ - 'hash_for_each'
+ - 'hash_for_each_possible'
+ - 'hash_for_each_possible_rcu'
+ - 'hash_for_each_possible_rcu_notrace'
+ - 'hash_for_each_possible_safe'
+ - 'hash_for_each_rcu'
+ - 'hash_for_each_safe'
+ - 'hctx_for_each_ctx'
+ - 'hlist_bl_for_each_entry'
+ - 'hlist_bl_for_each_entry_rcu'
+ - 'hlist_bl_for_each_entry_safe'
+ - 'hlist_for_each'
+ - 'hlist_for_each_entry'
+ - 'hlist_for_each_entry_continue'
+ - 'hlist_for_each_entry_continue_rcu'
+ - 'hlist_for_each_entry_continue_rcu_bh'
+ - 'hlist_for_each_entry_from'
+ - 'hlist_for_each_entry_from_rcu'
+ - 'hlist_for_each_entry_rcu'
+ - 'hlist_for_each_entry_rcu_bh'
+ - 'hlist_for_each_entry_rcu_notrace'
+ - 'hlist_for_each_entry_safe'
+ - '__hlist_for_each_rcu'
+ - 'hlist_for_each_safe'
+ - 'hlist_nulls_for_each_entry'
+ - 'hlist_nulls_for_each_entry_from'
+ - 'hlist_nulls_for_each_entry_rcu'
+ - 'hlist_nulls_for_each_entry_safe'
+ - 'i3c_bus_for_each_i2cdev'
+ - 'i3c_bus_for_each_i3cdev'
+ - 'ide_host_for_each_port'
+ - 'ide_port_for_each_dev'
+ - 'ide_port_for_each_present_dev'
+ - 'idr_for_each_entry'
+ - 'idr_for_each_entry_continue'
+ - 'idr_for_each_entry_continue_ul'
+ - 'idr_for_each_entry_ul'
+ - 'in_dev_for_each_ifa_rcu'
+ - 'in_dev_for_each_ifa_rtnl'
+ - 'inet_bind_bucket_for_each'
+ - 'inet_lhash2_for_each_icsk_rcu'
+ - 'key_for_each'
+ - 'key_for_each_safe'
+ - 'klp_for_each_func'
+ - 'klp_for_each_func_safe'
+ - 'klp_for_each_func_static'
+ - 'klp_for_each_object'
+ - 'klp_for_each_object_safe'
+ - 'klp_for_each_object_static'
+ - 'kunit_suite_for_each_test_case'
+ - 'kvm_for_each_memslot'
+ - 'kvm_for_each_vcpu'
+ - 'list_for_each'
+ - 'list_for_each_codec'
+ - 'list_for_each_codec_safe'
+ - 'list_for_each_continue'
+ - 'list_for_each_entry'
+ - 'list_for_each_entry_continue'
+ - 'list_for_each_entry_continue_rcu'
+ - 'list_for_each_entry_continue_reverse'
+ - 'list_for_each_entry_from'
+ - 'list_for_each_entry_from_rcu'
+ - 'list_for_each_entry_from_reverse'
+ - 'list_for_each_entry_lockless'
+ - 'list_for_each_entry_rcu'
+ - 'list_for_each_entry_reverse'
+ - 'list_for_each_entry_safe'
+ - 'list_for_each_entry_safe_continue'
+ - 'list_for_each_entry_safe_from'
+ - 'list_for_each_entry_safe_reverse'
+ - 'list_for_each_prev'
+ - 'list_for_each_prev_safe'
+ - 'list_for_each_safe'
+ - 'llist_for_each'
+ - 'llist_for_each_entry'
+ - 'llist_for_each_entry_safe'
+ - 'llist_for_each_safe'
+ - 'mci_for_each_dimm'
+ - 'media_device_for_each_entity'
+ - 'media_device_for_each_intf'
+ - 'media_device_for_each_link'
+ - 'media_device_for_each_pad'
+ - 'nanddev_io_for_each_page'
+ - 'netdev_for_each_lower_dev'
+ - 'netdev_for_each_lower_private'
+ - 'netdev_for_each_lower_private_rcu'
+ - 'netdev_for_each_mc_addr'
+ - 'netdev_for_each_uc_addr'
+ - 'netdev_for_each_upper_dev_rcu'
+ - 'netdev_hw_addr_list_for_each'
+ - 'nft_rule_for_each_expr'
+ - 'nla_for_each_attr'
+ - 'nla_for_each_nested'
+ - 'nlmsg_for_each_attr'
+ - 'nlmsg_for_each_msg'
+ - 'nr_neigh_for_each'
+ - 'nr_neigh_for_each_safe'
+ - 'nr_node_for_each'
+ - 'nr_node_for_each_safe'
+ - 'of_for_each_phandle'
+ - 'of_property_for_each_string'
+ - 'of_property_for_each_u32'
+ - 'pci_bus_for_each_resource'
+ - 'pcm_for_each_format'
+ - 'ping_portaddr_for_each_entry'
+ - 'plist_for_each'
+ - 'plist_for_each_continue'
+ - 'plist_for_each_entry'
+ - 'plist_for_each_entry_continue'
+ - 'plist_for_each_entry_safe'
+ - 'plist_for_each_safe'
+ - 'pnp_for_each_card'
+ - 'pnp_for_each_dev'
+ - 'protocol_for_each_card'
+ - 'protocol_for_each_dev'
+ - 'queue_for_each_hw_ctx'
+ - 'radix_tree_for_each_slot'
+ - 'radix_tree_for_each_tagged'
+ - 'rbtree_postorder_for_each_entry_safe'
+ - 'rdma_for_each_block'
+ - 'rdma_for_each_port'
+ - 'rdma_umem_for_each_dma_block'
+ - 'resource_list_for_each_entry'
+ - 'resource_list_for_each_entry_safe'
+ - 'rhl_for_each_entry_rcu'
+ - 'rhl_for_each_rcu'
+ - 'rht_for_each'
+ - 'rht_for_each_entry'
+ - 'rht_for_each_entry_from'
+ - 'rht_for_each_entry_rcu'
+ - 'rht_for_each_entry_rcu_from'
+ - 'rht_for_each_entry_safe'
+ - 'rht_for_each_from'
+ - 'rht_for_each_rcu'
+ - 'rht_for_each_rcu_from'
+ - '__rq_for_each_bio'
+ - 'rq_for_each_bvec'
+ - 'rq_for_each_segment'
+ - 'scsi_for_each_prot_sg'
+ - 'scsi_for_each_sg'
+ - 'sctp_for_each_hentry'
+ - 'sctp_skb_for_each'
+ - 'shdma_for_each_chan'
+ - '__shost_for_each_device'
+ - 'shost_for_each_device'
+ - 'sk_for_each'
+ - 'sk_for_each_bound'
+ - 'sk_for_each_entry_offset_rcu'
+ - 'sk_for_each_from'
+ - 'sk_for_each_rcu'
+ - 'sk_for_each_safe'
+ - 'sk_nulls_for_each'
+ - 'sk_nulls_for_each_from'
+ - 'sk_nulls_for_each_rcu'
+ - 'snd_array_for_each'
+ - 'snd_pcm_group_for_each_entry'
+ - 'snd_soc_dapm_widget_for_each_path'
+ - 'snd_soc_dapm_widget_for_each_path_safe'
+ - 'snd_soc_dapm_widget_for_each_sink_path'
+ - 'snd_soc_dapm_widget_for_each_source_path'
+ - 'tb_property_for_each'
+ - 'tcf_exts_for_each_action'
+ - 'udp_portaddr_for_each_entry'
+ - 'udp_portaddr_for_each_entry_rcu'
+ - 'usb_hub_for_each_child'
+ - 'v4l2_device_for_each_subdev'
+ - 'v4l2_m2m_for_each_dst_buf'
+ - 'v4l2_m2m_for_each_dst_buf_safe'
+ - 'v4l2_m2m_for_each_src_buf'
+ - 'v4l2_m2m_for_each_src_buf_safe'
+ - 'virtio_device_for_each_vq'
+ - 'while_for_each_ftrace_op'
+ - 'xa_for_each'
+ - 'xa_for_each_marked'
+ - 'xa_for_each_range'
+ - 'xa_for_each_start'
+ - 'xas_for_each'
+ - 'xas_for_each_conflict'
+ - 'xas_for_each_marked'
+ - 'xbc_array_for_each_value'
+ - 'xbc_for_each_key_value'
+ - 'xbc_node_for_each_array_value'
+ - 'xbc_node_for_each_child'
+ - 'xbc_node_for_each_key_value'
+ - 'zorro_for_each_dev'
+
+#IncludeBlocks: Preserve # Unknown to clang-format-5.0
+IncludeCategories:
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+#IndentPPDirectives: None # Unknown to clang-format-5.0
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
+ObjCBlockIndentWidth: 8
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+
+# Taken from git's rules
+#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: false
+#SortUsingDeclarations: false # Unknown to clang-format-4.0
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
+#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
+SpaceBeforeParens: ControlStatements
+#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp03
+TabWidth: 8
+UseTab: Always
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0c053d1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.cmd
+*.ko
+*.mod*
+*.o
+*.orig
+modules.order
+Module.symvers
+.cache.mk
+.thinlto-cache/
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..0ebee89
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0
+
+menu "GXP device"
+
+config GXP
+ tristate "Device driver for GXP"
+ default m
+ select GENERIC_ALLOCATOR
+ help
+ This driver supports the GXP device. Say Y if you want to
+ include this driver in the kernel.
+
+ To compile this driver as a module, choose M here. The module will be
+ called "gxp".
+
+choice GXP_PLATFORM
+ bool "Target platform to build GXP driver for"
+ depends on GXP
+ default GXP_CLOUDRIPPER
+
+config GXP_CLOUDRIPPER
+ bool "Build for Cloudripper development board"
+ help
+ Select this to build for the Cloudripper development board.
+
+config GXP_ZEBU
+ bool "Build for ZeBu emulation system"
+ help
+ Select this to build for the full-SoC ZeBu emulation platform.
+
+config GXP_IP_ZEBU
+ bool "Build for an IP-ZeBu emulation system"
+ help
+ Select this to build for the Aurora IP-ZeBu emulation platform.
+
+endchoice
+
+endmenu
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..77db0fb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for GXP driver.
+#
+
+obj-$(CONFIG_GXP) += gxp.o
+
+gxp-objs += \
+ gxp-bpm.o \
+ gxp-debug-dump.o \
+ gxp-debugfs.o \
+ gxp-doorbell.o \
+ gxp-firmware.o \
+ gxp-firmware-data.o \
+ gxp-lpm.o \
+ gxp-mailbox.o \
+ gxp-mapping.o \
+ gxp-platform.o \
+ gxp-range-alloc.o \
+ gxp-vd.o
+
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+# If building via make directly, specify target platform by adding
+# "GXP_PLATFORM=<target>"
+# With one of the following values:
+# - CLOUDRIPPER
+# - ZEBU
+# - IP_ZEBU
+# Defaults to building for CLOUDRIPPER if not otherwise specified.
+GXP_PLATFORM ?= CLOUDRIPPER
+
+# Default to using the HW mailbox and SysMMU
+GXP_SW_MAILBOX ?= 0
+GXP_HAS_SYSMMU ?= 1
+
+# Setup the linked mailbox implementation and definitions.
+ifeq ($(GXP_SW_MAILBOX),1)
+ ccflags-y += -DCONFIG_GXP_USE_SW_MAILBOX
+ gxp-objs += gxp-sw-mailbox-driver.o
+else
+ gxp-objs += gxp-hw-mailbox-driver.o
+endif
+
+# Setup which version of the gxp-dma interface is used.
+ifeq ($(GXP_HAS_SYSMMU),1)
+ ccflags-y += -DCONFIG_GXP_HAS_SYSMMU
+ gxp-objs += gxp-dma-iommu.o
+else
+ gxp-objs += gxp-dma-rmem.o
+endif
+
+ccflags-y += -DCONFIG_GXP_$(GXP_PLATFORM)
+
+KBUILD_OPTIONS += CONFIG_GXP=m
+
+modules modules_install clean:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) W=1 $(KBUILD_OPTIONS) $(@)
diff --git a/gxp-bpm.c b/gxp-bpm.c
new file mode 100644
index 0000000..c440bf5
--- /dev/null
+++ b/gxp-bpm.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP bus performance monitor interface.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include "gxp-bpm.h"
+#include "gxp-config.h"
+
+#define BPM_EVENT_TYPE_BIT 2
+#define BPM_EVENT_TYPE_MASK 0x1F
+
+#define BPM_START_BIT 10
+#define BPM_STOP_BIT 11
+
+#define BPM_CONFIG_OFFSET 0x00
+#define BPM_CNTR_CONFIG_OFFSET 0x18
+#define BPM_SNAPSHOT_CNTR_OFFSET 0x98
+
+void gxp_bpm_configure(struct gxp_dev *gxp, u8 core, u32 bpm_offset, u32 event)
+{
+ u32 val =
+ ((event & BPM_EVENT_TYPE_MASK) << BPM_EVENT_TYPE_BIT) | ENABLE;
+ u32 bpm_base = GXP_REG_INST_BPM + bpm_offset;
+
+ /* Configure event */
+ gxp_write_32_core(gxp, core, bpm_base + BPM_CNTR_CONFIG_OFFSET, val);
+ /* Arm counter */
+ gxp_write_32_core(gxp, core, bpm_base + BPM_CONFIG_OFFSET, ENABLE);
+}
+
+void gxp_bpm_start(struct gxp_dev *gxp, u8 core)
+{
+ gxp_write_32_core(gxp, core, GXP_REG_PROFILING_CONDITION,
+ ENABLE << BPM_START_BIT);
+}
+
+void gxp_bpm_stop(struct gxp_dev *gxp, u8 core)
+{
+ gxp_write_32_core(gxp, core, GXP_REG_PROFILING_CONDITION,
+ ENABLE << BPM_STOP_BIT);
+}
+
+u32 gxp_bpm_read_counter(struct gxp_dev *gxp, u8 core, u32 bpm_offset)
+{
+ u32 bpm_base = GXP_REG_INST_BPM + bpm_offset;
+
+ /* Disarm counter */
+ gxp_write_32_core(gxp, core, bpm_base + BPM_CONFIG_OFFSET, DISABLE);
+ /* Read final counter value */
+ return gxp_read_32_core(gxp, core, bpm_base + BPM_SNAPSHOT_CNTR_OFFSET);
+}
diff --git a/gxp-bpm.h b/gxp-bpm.h
new file mode 100644
index 0000000..1281bce
--- /dev/null
+++ b/gxp-bpm.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP bus performance monitor interface.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_BPM_H__
+#define __GXP_BPM_H__
+
+#include "gxp-internal.h"
+
+/*
+ * Available BPMs
+ *
+ * Passed to gxp_bpm_configure() and gxp_bpm_read_counter() to specify
+ * which BPM to use: instruction, data, or DMA.
+ */
+#define INST_BPM_OFFSET 0x0000
+#define DATA_BPM_OFFSET 0x1000
+#define IDMA_BPM_OFFSET 0x2000
+
+/*
+ * Available BPM Events
+ *
+ * Passed to gxp_bpm_configure() to specify the type of event being counted.
+ */
+#define BPM_EVENT_ADDR_READ 0x0
+#define BPM_EVENT_ADDR_WRITE 0x1
+#define BPM_EVENT_WRITE_XFER 0x2
+#define BPM_EVENT_READ_XFER 0x3
+
+void gxp_bpm_configure(struct gxp_dev *gxp, u8 core, u32 bpm_offset, u32 event);
+void gxp_bpm_start(struct gxp_dev *gxp, u8 core);
+void gxp_bpm_stop(struct gxp_dev *gxp, u8 core);
+u32 gxp_bpm_read_counter(struct gxp_dev *gxp, u8 core, u32 bpm_offset);
+
+#endif /* __GXP_BPM_H__ */
diff --git a/gxp-config.h b/gxp-config.h
new file mode 100644
index 0000000..cc06d1f
--- /dev/null
+++ b/gxp-config.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Include all configuration files for GXP.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+
+#ifndef __GXP_CONFIG_H__
+#define __GXP_CONFIG_H__
+
+#define GXP_DRIVER_NAME "gxp_platform"
+#define GXP_NUM_CORES 4
+
+#if defined(CONFIG_GXP_ZEBU) || defined(CONFIG_GXP_IP_ZEBU)
+#define GXP_TIME_DELAY_FACTOR 20
+#else
+#define GXP_TIME_DELAY_FACTOR 1
+#endif
+
+#include "gxp-csrs.h"
+
+/* Core address space starts at Inst_BPM block */
+#define GXP_CORE_0_BASE GXP_REG_CORE_0_INST_BPM
+#define GXP_CORE_SIZE (GXP_REG_CORE_1_INST_BPM - GXP_REG_CORE_0_INST_BPM)
+
+/* LPM address space starts at lpm_version register */
+#define GXP_LPM_BASE GXP_REG_LPM_VERSION
+#define GXP_LPM_PSM_0_BASE GXP_REG_LPM_PSM_0
+#define GXP_LPM_PSM_SIZE (GXP_REG_LPM_PSM_1 - GXP_REG_LPM_PSM_0)
+
+#endif /* __GXP_CONFIG_H__ */
diff --git a/gxp-csrs.h b/gxp-csrs.h
new file mode 100644
index 0000000..f1d710b
--- /dev/null
+++ b/gxp-csrs.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP CSR definitions.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_CSRS_H__
+#define __GXP_CSRS_H__
+
+#define GXP_REG_DOORBELLS_SET_WRITEMASK 0x1
+#define GXP_REG_DOORBELLS_CLEAR_WRITEMASK 0x1
+
+enum gxp_csrs {
+ GXP_REG_LPM_VERSION = 0x40000,
+ GXP_REG_LPM_PSM_0 = 0x41000,
+ GXP_REG_LPM_PSM_1 = 0x42000,
+ GXP_REG_LPM_PSM_2 = 0x43000,
+ GXP_REG_LPM_PSM_3 = 0x44000,
+ GXP_REG_LPM_PSM_4 = 0x45000,
+ GXP_REG_AURORA_REVISION = 0x80000,
+ GXP_REG_COMMON_INT_POL_0 = 0x81000,
+ GXP_REG_COMMON_INT_POL_1 = 0x81004,
+ GXP_REG_DEDICATED_INT_POL = 0x81008,
+ GXP_REG_RAW_EXT_INT = 0x82000,
+ GXP_REG_CORE_PD = 0x82800,
+ GXP_REG_GLOBAL_COUNTER_LOW = 0x83000,
+ GXP_REG_GLOBAL_COUNTER_HIGH = 0x83004,
+ GXP_REG_WDOG_CONTROL = 0x86000,
+ GXP_REG_WDOG_VALUE = 0x86008,
+ GXP_REG_TIMER_COMPARATOR = 0x90000,
+ GXP_REG_TIMER_CONTROL = 0x90004,
+ GXP_REG_TIMER_VALUE = 0x90008,
+ GXP_REG_DOORBELL_0_STATUS = 0xC0000,
+ GXP_REG_DOORBELL_0_SET = 0xC0004,
+ GXP_REG_DOORBELL_0_CLEAR = 0xC0008,
+ GXP_REG_DOORBELL_1_STATUS = 0xC1000,
+ GXP_REG_DOORBELL_1_SET = 0xC1004,
+ GXP_REG_DOORBELL_1_CLEAR = 0xC1008,
+ GXP_REG_CORE_0_INST_BPM = 0x200000,
+ GXP_REG_CORE_1_INST_BPM = 0x210000,
+ GXP_REG_CORE_2_INST_BPM = 0x220000,
+ GXP_REG_CORE_3_INST_BPM = 0x230000,
+};
+
+#define GXP_REG_COMMON_INT_MASK_0_DOORBELLS_MASK 0xFFFFFFFF
+
+enum gxp_core_csrs {
+ GXP_REG_INST_BPM = 0x0000,
+ GXP_REG_PROFILING_CONDITION = 0x4000,
+ GXP_REG_PROCESSOR_ID = 0x4004,
+ GXP_REG_ALT_RESET_VECTOR = 0x4008,
+ GXP_REG_COMMON_INT_MASK_0 = 0x4010,
+};
+
+#define DOORBELL_COUNT 32
+
+#define SYNC_BARRIER_COUNT 16
+#define SYNC_BARRIER_SHADOW_OFFSET 0x800
+
+#define CORE_PD_BASE(_x_) ((_x_) << 2)
+#define CORE_PD_COUNT 4
+
+#define TIMER_BASE(_x_) ((_x_) << 12)
+#define TIMER_COMPARATOR_OFFSET 0x0
+#define TIMER_CONTROL_OFFSET 0x4
+#define TIMER_VALUE_OFFSET 0x8
+#define TIMER_COUNT 8
+
+/* LPM Registers */
+#define LPM_VERSION_OFFSET 0x0
+#define TRIGGER_CSR_START_OFFSET 0x4
+#define IMEM_START_OFFSET 0x8
+#define LPM_CONFIG_OFFSET 0xC
+#define PSM_DESCRIPTOR_OFFSET 0x10
+#define EVENTS_EN_OFFSET 0x100
+#define EVENTS_INV_OFFSET 0x140
+#define FUNCTION_SELECT_OFFSET 0x180
+#define TRIGGER_STATUS_OFFSET 0x184
+#define EVENT_STATUS_OFFSET 0x188
+#define OPS_OFFSET 0x800
+#define PSM_DESCRIPTOR_BASE(_x_) ((_x_) << 2)
+#define PSM_DESCRIPTOR_COUNT 5
+#define EVENTS_EN_BASE(_x_) ((_x_) << 2)
+#define EVENTS_EN_COUNT 16
+#define EVENTS_INV_BASE(_x_) ((_x_) << 2)
+#define EVENTS_INV_COUNT 16
+#define OPS_BASE(_x_) ((_x_) << 2)
+#define OPS_COUNT 128
+#define PSM_COUNT 5
+#define PSM_STATE_TABLE_BASE(_x_) ((_x_) << 8)
+#define PSM_STATE_TABLE_COUNT 6
+#define PSM_TRANS_BASE(_x_) ((_x_) << 5)
+#define PSM_TRANS_COUNT 4
+#define PSM_DMEM_BASE(_x_) ((_x_) << 2)
+#define PSM_DATA_COUNT 32
+#define PSM_NEXT_STATE_OFFSET 0x0
+#define PSM_SEQ_ADDR_OFFSET 0x4
+#define PSM_TIMER_VAL_OFFSET 0x8
+#define PSM_TIMER_EN_OFFSET 0xC
+#define PSM_TRIGGER_NUM_OFFSET 0x10
+#define PSM_TRIGGER_EN_OFFSET 0x14
+#define PSM_ENABLE_STATE_OFFSET 0x80
+#define PSM_DATA_OFFSET 0x600
+#define PSM_CFG_OFFSET 0x680
+#define PSM_START_OFFSET 0x684
+#define PSM_STATUS_OFFSET 0x688
+#define PSM_DEBUG_CFG_OFFSET 0x68C
+#define PSM_BREAK_ADDR_OFFSET 0x694
+#define PSM_GPIN_LO_RD_OFFSET 0x6A0
+#define PSM_GPIN_HI_RD_OFFSET 0x6A4
+#define PSM_GPOUT_LO_RD_OFFSET 0x6B0
+#define PSM_GPOUT_HI_RD_OFFSET 0x6B4
+#define PSM_DEBUG_STATUS_OFFSET 0x6B8
+
+#endif /* __GXP_CSRS_H__ */
diff --git a/gxp-debug-dump.c b/gxp-debug-dump.c
new file mode 100644
index 0000000..ea0339d
--- /dev/null
+++ b/gxp-debug-dump.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP debug dump handler
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+
+#ifdef CONFIG_ANDROID
+#include <linux/platform_data/sscoredump.h>
+#endif
+
+#include "gxp-debug-dump.h"
+#include "gxp-doorbell.h"
+#include "gxp-internal.h"
+#include "gxp-lpm.h"
+#include "gxp-mailbox.h"
+#include "gxp-tmp.h"
+
+#define GXP_COREDUMP_PENDING 0xF
+#define KERNEL_INIT_DUMP_TIMEOUT (10000 * GXP_TIME_DELAY_FACTOR)
+
+/* Enum indicating the debug dump request reason. */
+enum gxp_debug_dump_init_type {
+ DEBUG_DUMP_FW_INIT,
+ DEBUG_DUMP_KERNEL_INIT
+};
+
+enum gxp_common_segments_idx {
+ GXP_COMMON_REGISTERS_IDX,
+ GXP_LPM_REGISTERS_IDX
+};
+
+static void gxp_debug_dump_cache_invalidate(struct gxp_dev *gxp)
+{
+ /* Debug dump carveout is currently coherent. NO-OP. */
+ return;
+}
+
+static void gxp_debug_dump_cache_flush(struct gxp_dev *gxp)
+{
+ /* Debug dump carveout is currently coherent. NO-OP. */
+ return;
+}
+
+
+static void
+gxp_get_common_registers(struct gxp_dev *gxp, struct gxp_seg_header *seg_header,
+ struct gxp_common_registers *common_regs)
+{
+ int i;
+ u32 addr;
+
+ dev_dbg(gxp->dev, "Getting common registers\n");
+
+ strscpy(seg_header->name, "Common Registers", sizeof(seg_header->name));
+ seg_header->valid = 1;
+ seg_header->size = sizeof(*common_regs);
+
+ /* Get Aurora Top registers */
+ common_regs->aurora_revision =
+ gxp_read_32(gxp, GXP_REG_AURORA_REVISION);
+ common_regs->common_int_pol_0 =
+ gxp_read_32(gxp, GXP_REG_COMMON_INT_POL_0);
+ common_regs->common_int_pol_1 =
+ gxp_read_32(gxp, GXP_REG_COMMON_INT_POL_1);
+ common_regs->dedicated_int_pol =
+ gxp_read_32(gxp, GXP_REG_DEDICATED_INT_POL);
+ common_regs->raw_ext_int = gxp_read_32(gxp, GXP_REG_RAW_EXT_INT);
+
+ for (i = 0; i < CORE_PD_COUNT; i++) {
+ common_regs->core_pd[i] =
+ gxp_read_32(gxp, GXP_REG_CORE_PD + CORE_PD_BASE(i));
+ }
+
+ common_regs->global_counter_low =
+ gxp_read_32(gxp, GXP_REG_GLOBAL_COUNTER_LOW);
+ common_regs->global_counter_high =
+ gxp_read_32(gxp, GXP_REG_GLOBAL_COUNTER_HIGH);
+ common_regs->wdog_control = gxp_read_32(gxp, GXP_REG_WDOG_CONTROL);
+ common_regs->wdog_value = gxp_read_32(gxp, GXP_REG_WDOG_VALUE);
+
+ for (i = 0; i < TIMER_COUNT; i++) {
+ addr = GXP_REG_TIMER_COMPARATOR + TIMER_BASE(i);
+ common_regs->timer[i].comparator =
+ gxp_read_32(gxp, addr + TIMER_COMPARATOR_OFFSET);
+ common_regs->timer[i].control =
+ gxp_read_32(gxp, addr + TIMER_CONTROL_OFFSET);
+ common_regs->timer[i].value =
+ gxp_read_32(gxp, addr + TIMER_VALUE_OFFSET);
+ }
+
+ /* Get Doorbell registers */
+ for (i = 0; i < DOORBELL_COUNT; i++)
+ common_regs->doorbell[i] = gxp_doorbell_status(gxp, i);
+
+ /* Get Sync Barrier registers */
+ for (i = 0; i < SYNC_BARRIER_COUNT; i++)
+ common_regs->sync_barrier[i] =
+ gxp_read_sync_barrier_shadow(gxp, i);
+
+ dev_dbg(gxp->dev, "Done getting common registers\n");
+}
+
+static void gxp_get_lpm_psm_registers(struct gxp_dev *gxp,
+ struct gxp_lpm_psm_registers *psm_regs,
+ int psm)
+{
+ struct gxp_lpm_state_table_registers *state_table_regs;
+ int i, j;
+ uint offset;
+
+ /* Get State Table registers */
+ for (i = 0; i < PSM_STATE_TABLE_COUNT; i++) {
+ state_table_regs = &psm_regs->state_table[i];
+
+ /* Get Trans registers */
+ for (j = 0; j < PSM_TRANS_COUNT; j++) {
+ offset = PSM_STATE_TABLE_BASE(i) + PSM_TRANS_BASE(j);
+ state_table_regs->trans[j].next_state =
+ lpm_read_32_psm(gxp, psm, offset +
+ PSM_NEXT_STATE_OFFSET);
+ state_table_regs->trans[j].seq_addr =
+ lpm_read_32_psm(gxp, psm, offset +
+ PSM_SEQ_ADDR_OFFSET);
+ state_table_regs->trans[j].timer_val =
+ lpm_read_32_psm(gxp, psm, offset +
+ PSM_TIMER_VAL_OFFSET);
+ state_table_regs->trans[j].timer_en =
+ lpm_read_32_psm(gxp, psm, offset +
+ PSM_TIMER_EN_OFFSET);
+ state_table_regs->trans[j].trigger_num =
+ lpm_read_32_psm(gxp, psm, offset +
+ PSM_TRIGGER_NUM_OFFSET);
+ state_table_regs->trans[j].trigger_en =
+ lpm_read_32_psm(gxp, psm, offset +
+ PSM_TRIGGER_EN_OFFSET);
+ }
+
+ state_table_regs->enable_state =
+ lpm_read_32_psm(gxp, psm, PSM_STATE_TABLE_BASE(i) +
+ PSM_ENABLE_STATE_OFFSET);
+ }
+
+ /* Get DMEM registers */
+ for (i = 0; i < PSM_DATA_COUNT; i++) {
+ offset = PSM_DMEM_BASE(i) + PSM_DATA_OFFSET;
+ psm_regs->data[i] = lpm_read_32_psm(gxp, psm, offset);
+ }
+
+ psm_regs->cfg = lpm_read_32_psm(gxp, psm, PSM_CFG_OFFSET);
+ psm_regs->status = lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET);
+
+ /* Get Debug CSR registers */
+ psm_regs->debug_cfg = lpm_read_32_psm(gxp, psm, PSM_DEBUG_CFG_OFFSET);
+ psm_regs->break_addr = lpm_read_32_psm(gxp, psm, PSM_BREAK_ADDR_OFFSET);
+ psm_regs->gpin_lo_rd = lpm_read_32_psm(gxp, psm, PSM_GPIN_LO_RD_OFFSET);
+ psm_regs->gpin_hi_rd = lpm_read_32_psm(gxp, psm, PSM_GPIN_HI_RD_OFFSET);
+ psm_regs->gpout_lo_rd =
+ lpm_read_32_psm(gxp, psm, PSM_GPOUT_LO_RD_OFFSET);
+ psm_regs->gpout_hi_rd =
+ lpm_read_32_psm(gxp, psm, PSM_GPOUT_HI_RD_OFFSET);
+ psm_regs->debug_status =
+ lpm_read_32_psm(gxp, psm, PSM_DEBUG_STATUS_OFFSET);
+}
+
+static void
+gxp_get_lpm_registers(struct gxp_dev *gxp, struct gxp_seg_header *seg_header,
+ struct gxp_lpm_registers *lpm_regs)
+{
+ int i;
+ uint offset;
+
+ dev_dbg(gxp->dev, "Getting LPM registers\n");
+
+ strscpy(seg_header->name, "LPM Registers", sizeof(seg_header->name));
+ seg_header->valid = 1;
+ seg_header->size = sizeof(*lpm_regs);
+
+ /* Get LPM Descriptor registers */
+ lpm_regs->lpm_version = lpm_read_32(gxp, LPM_VERSION_OFFSET);
+ lpm_regs->trigger_csr_start =
+ lpm_read_32(gxp, TRIGGER_CSR_START_OFFSET);
+ lpm_regs->imem_start = lpm_read_32(gxp, IMEM_START_OFFSET);
+ lpm_regs->lpm_config = lpm_read_32(gxp, LPM_CONFIG_OFFSET);
+
+ for (i = 0; i < PSM_DESCRIPTOR_COUNT; i++) {
+ offset = PSM_DESCRIPTOR_OFFSET + PSM_DESCRIPTOR_BASE(i);
+ lpm_regs->psm_descriptor[i] = lpm_read_32(gxp, offset);
+ }
+
+ /* Get Trigger CSR registers */
+ for (i = 0; i < EVENTS_EN_COUNT; i++) {
+ offset = EVENTS_EN_OFFSET + EVENTS_EN_BASE(i);
+ lpm_regs->events_en[i] = lpm_read_32(gxp, offset);
+ }
+
+ for (i = 0; i < EVENTS_INV_COUNT; i++) {
+ offset = EVENTS_INV_OFFSET + EVENTS_INV_BASE(i);
+ lpm_regs->events_inv[i] = lpm_read_32(gxp, offset);
+ }
+
+ lpm_regs->function_select = lpm_read_32(gxp, FUNCTION_SELECT_OFFSET);
+ lpm_regs->trigger_status = lpm_read_32(gxp, TRIGGER_STATUS_OFFSET);
+ lpm_regs->event_status = lpm_read_32(gxp, EVENT_STATUS_OFFSET);
+
+ /* Get IMEM registers */
+ for (i = 0; i < OPS_COUNT; i++) {
+ offset = OPS_OFFSET + OPS_BASE(i);
+ lpm_regs->ops[i] = lpm_read_32(gxp, offset);
+ }
+
+ /* Get PSM registers */
+ for (i = 0; i < PSM_COUNT; i++)
+ gxp_get_lpm_psm_registers(gxp, &lpm_regs->psm_regs[i], i);
+
+ dev_dbg(gxp->dev, "Done getting LPM registers\n");
+}
+
+static void gxp_get_common_dump(struct gxp_dev *gxp,
+ struct gxp_seg_header *common_seg_header,
+ struct gxp_common_dump_data *common_dump_data)
+{
+ gxp_get_common_registers(gxp,
+ &common_seg_header[GXP_COMMON_REGISTERS_IDX],
+ &common_dump_data->common_regs);
+ gxp_get_lpm_registers(gxp, &common_seg_header[GXP_LPM_REGISTERS_IDX],
+ &common_dump_data->lpm_regs);
+
+ dev_dbg(gxp->dev, "Segment Header for Common Segment\n");
+ dev_dbg(gxp->dev, "Name: %s, Size: 0x%0x bytes, Valid :%0x\n",
+ common_seg_header->name, common_seg_header->size,
+ common_seg_header->valid);
+ dev_dbg(gxp->dev, "Register aurora_revision: 0x%0x\n",
+ common_dump_data->common_regs.aurora_revision);
+}
+
+static void gxp_handle_debug_dump(struct gxp_dev *gxp,
+ struct gxp_core_dump *core_dump,
+ struct gxp_common_dump *common_dump,
+ enum gxp_debug_dump_init_type init_type,
+ uint core_bits)
+{
+ struct gxp_core_dump_header *core_dump_header;
+ struct gxp_core_header *core_header;
+ int i;
+#ifdef CONFIG_ANDROID
+ struct sscd_platform_data *pdata =
+ (struct sscd_platform_data *)gxp->debug_dump_mgr->sscd_pdata;
+ struct sscd_segment *segs;
+ int segs_num = GXP_NUM_COMMON_SEGMENTS;
+ int seg_idx = 0;
+ int core_dump_num = 0;
+ int j;
+ void *data_addr;
+
+ for (i = 0; i < GXP_NUM_CORES; i++) {
+ if (core_bits & BIT(i))
+ core_dump_num++;
+ }
+
+ /*
+ * segs_num include the common segments, core segments for each core,
+ * core header for each core
+ */
+ if (init_type == DEBUG_DUMP_FW_INIT)
+ segs_num += GXP_NUM_CORE_SEGMENTS + 1;
+ else
+ segs_num += GXP_NUM_CORE_SEGMENTS * core_dump_num +
+ core_dump_num;
+
+ segs = kmalloc_array(segs_num, sizeof(struct sscd_segment),
+ GFP_KERNEL);
+ if (!segs)
+ goto out;
+
+ /* Common */
+ data_addr = &common_dump->common_dump_data.common_regs;
+ for (i = 0; i < GXP_NUM_COMMON_SEGMENTS; i++) {
+ segs[seg_idx].addr = data_addr;
+ segs[seg_idx].size = common_dump->seg_header[i].size;
+ data_addr += segs[seg_idx].size;
+ seg_idx++;
+ }
+#endif // CONFIG_ANDROID
+
+ /* Core */
+ for (i = 0; i < GXP_NUM_CORES; i++) {
+ if ((core_bits & BIT(i)) == 0)
+ continue;
+
+ core_dump_header = &core_dump->core_dump_header[i];
+ core_header = &core_dump_header->core_header;
+ if (!core_header->dump_available) {
+ dev_err(gxp->dev,
+ "Core dump should have been available\n");
+ goto out;
+ }
+
+#ifdef CONFIG_ANDROID
+ /* Core Header */
+ segs[seg_idx].addr = core_header;
+ segs[seg_idx].size = sizeof(struct gxp_core_header);
+ seg_idx++;
+
+ data_addr = &core_dump->dump_data[i *
+ core_header->core_dump_size /
+ sizeof(u32)];
+
+ for (j = 0; j < GXP_NUM_CORE_SEGMENTS; j++) {
+ segs[seg_idx].addr = data_addr;
+ segs[seg_idx].size =
+ core_dump_header->seg_header[j].size;
+ data_addr += segs[seg_idx].size;
+ seg_idx++;
+ }
+
+ dev_notice(gxp->dev, "Passing dump data to SSCD daemon\n");
+ if (!pdata->sscd_report) {
+ dev_err(gxp->dev,
+ "Failed to generate coredump\n");
+ goto out;
+ }
+
+ mutex_lock(&gxp->debug_dump_mgr->sscd_lock);
+ if (pdata->sscd_report(gxp->debug_dump_mgr->sscd_dev, segs,
+ segs_num, SSCD_FLAGS_ELFARM64HDR,
+ "gxp debug dump")) {
+ dev_err(gxp->dev,
+ "Unable to send the report to SSCD daemon\n");
+ mutex_unlock(&gxp->debug_dump_mgr->sscd_lock);
+ goto out;
+ }
+
+ /*
+ * This delay is needed to ensure there's sufficient time
+ * in between sscd_report() being called, as the file name of
+ * the core dump files generated by the SSCD daemon includes a
+ * time format with a seconds precision.
+ */
+ msleep(1000);
+ mutex_unlock(&gxp->debug_dump_mgr->sscd_lock);
+#endif // CONFIG_ANDROID
+
+ /* This bit signals that core dump has been processed */
+ core_header->dump_available = 0;
+
+ if (init_type == DEBUG_DUMP_FW_INIT)
+ goto out;
+ }
+
+out:
+#ifdef CONFIG_ANDROID
+ kfree(segs);
+#endif
+ return;
+}
+
+static int gxp_generate_coredump(struct gxp_dev *gxp,
+ enum gxp_debug_dump_init_type init_type,
+ uint core_bits)
+{
+ struct gxp_core_dump *core_dump;
+ struct gxp_common_dump *common_dump;
+ struct gxp_seg_header *common_seg_header;
+ struct gxp_common_dump_data *common_dump_data;
+
+ if (!gxp->debug_dump_mgr->core_dump) {
+ dev_err(gxp->dev, "Core dump not allocated\n");
+ return -EINVAL;
+ }
+
+ if (core_bits == 0) {
+ dev_err(gxp->dev, "The number of core dumps requested is 0.\n");
+ return -EINVAL;
+ } else if (core_bits > GXP_COREDUMP_PENDING) {
+ dev_err(gxp->dev,
+ "The number of core dumps requested (%0x) is greater than expected (%0x)\n",
+ core_bits, GXP_COREDUMP_PENDING);
+ return -EINVAL;
+ }
+
+ gxp_debug_dump_cache_invalidate(gxp);
+
+ core_dump = gxp->debug_dump_mgr->core_dump;
+ common_dump = kmalloc(sizeof(*common_dump), GFP_KERNEL);
+ if (!common_dump)
+ return -ENOMEM;
+
+ common_seg_header = common_dump->seg_header;
+ common_dump_data = &common_dump->common_dump_data;
+
+ gxp_get_common_dump(gxp, common_seg_header, common_dump_data);
+
+ gxp_handle_debug_dump(gxp, core_dump, common_dump, init_type,
+ core_bits);
+
+ /* Mark the common segments as read */
+ common_seg_header->valid = 0;
+
+ gxp_debug_dump_cache_flush(gxp);
+
+ return 0;
+}
+
+static void gxp_wait_kernel_init_dump_work(struct work_struct *work)
+{
+ struct gxp_debug_dump_manager *mgr =
+ container_of(work, struct gxp_debug_dump_manager,
+ wait_kernel_init_dump_work);
+
+ wait_event_timeout(mgr->kernel_init_dump_waitq,
+ mgr->kernel_init_dump_pending ==
+ GXP_COREDUMP_PENDING,
+ msecs_to_jiffies(KERNEL_INIT_DUMP_TIMEOUT));
+
+ mutex_lock(&mgr->lock);
+ gxp_generate_coredump(mgr->gxp, DEBUG_DUMP_KERNEL_INIT,
+ mgr->kernel_init_dump_pending);
+ mgr->kernel_init_dump_pending = 0;
+ mutex_unlock(&mgr->lock);
+}
+
+void gxp_debug_dump_process_dump(struct work_struct *work)
+{
+ struct gxp_mailbox *mailbox = container_of(work, struct gxp_mailbox,
+ debug_dump_work);
+ uint core_id = mailbox->core_id;
+ struct gxp_dev *gxp = mailbox->gxp;
+ struct gxp_debug_dump_manager *mgr;
+ struct gxp_core_dump *core_dump;
+ struct gxp_core_dump_header *core_dump_header;
+ struct gxp_core_header *core_header;
+ int *kernel_init_dump_pending;
+
+ mgr = gxp->debug_dump_mgr;
+ if (!mgr) {
+ dev_err(gxp->dev,
+ "gxp->debug_dump_mgr has not been initialized\n");
+ return;
+ }
+
+ core_dump = mgr->core_dump;
+ if (!core_dump) {
+ dev_err(gxp->dev,
+ "mgr->core_dump has not been initialized\n");
+ return;
+ }
+
+ core_dump_header = &core_dump->core_dump_header[core_id];
+ core_header = &core_dump_header->core_header;
+ kernel_init_dump_pending = &mgr->kernel_init_dump_pending;
+
+ switch (core_header->dump_req_reason) {
+ case DEBUG_DUMP_FW_INIT:
+ gxp_generate_coredump(gxp, DEBUG_DUMP_FW_INIT, BIT(core_id));
+ break;
+ case DEBUG_DUMP_KERNEL_INIT:
+ mutex_lock(&mgr->lock);
+ if (*kernel_init_dump_pending == 0)
+ schedule_work(&mgr->wait_kernel_init_dump_work);
+ *kernel_init_dump_pending |= BIT(core_id);
+ wake_up(&mgr->kernel_init_dump_waitq);
+ mutex_unlock(&mgr->lock);
+ break;
+ }
+}
+
+int gxp_debug_dump_init(struct gxp_dev *gxp, void *sscd_dev, void *sscd_pdata)
+{
+ struct resource r;
+ struct gxp_debug_dump_manager *mgr;
+ struct gxp_core_dump_header *core_dump_header;
+ int core;
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+ gxp->debug_dump_mgr = mgr;
+ mgr->gxp = gxp;
+
+ /* Find and map the memory reserved for the debug dump */
+ if (gxp_acquire_rmem_resource(gxp, &r, "gxp-debug-dump-region")) {
+ dev_err(gxp->dev,
+ "Unable to acquire debug dump reserved memory\n");
+ return -ENODEV;
+ }
+ gxp->coredumpbuf.paddr = r.start;
+ gxp->coredumpbuf.size = resource_size(&r);
+ /*
+ * TODO (b/193069216) allocate a dynamic buffer and let
+ * `gxp_dma_map_resources()` map it to the expected paddr
+ */
+ /*
+ * TODO (b/200169232) Using memremap until devm_memremap is added to
+ * the GKI ABI
+ */
+ gxp->coredumpbuf.vaddr = memremap(gxp->coredumpbuf.paddr,
+ gxp->coredumpbuf.size, MEMREMAP_WC);
+ if (IS_ERR(gxp->coredumpbuf.vaddr)) {
+ dev_err(gxp->dev, "Failed to map core dump\n");
+ return -ENODEV;
+ }
+ mgr->core_dump = (struct gxp_core_dump *)gxp->coredumpbuf.vaddr;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ core_dump_header = &mgr->core_dump->core_dump_header[core];
+ core_dump_header->core_header.dump_available = 0;
+ }
+
+ /* No need for a DMA handle since the carveout is coherent */
+ mgr->debug_dump_dma_handle = 0;
+ mgr->kernel_init_dump_pending = 0;
+ mgr->sscd_dev = sscd_dev;
+ mgr->sscd_pdata = sscd_pdata;
+ mutex_init(&mgr->lock);
+ mutex_init(&mgr->sscd_lock);
+
+ INIT_WORK(&mgr->wait_kernel_init_dump_work,
+ gxp_wait_kernel_init_dump_work);
+
+ init_waitqueue_head(&mgr->kernel_init_dump_waitq);
+
+ return 0;
+}
+
+void gxp_debug_dump_exit(struct gxp_dev *gxp)
+{
+ struct gxp_debug_dump_manager *mgr = gxp->debug_dump_mgr;
+
+ if (!mgr) {
+ dev_dbg(gxp->dev, "Debug dump manager was not allocated\n");
+ return;
+ }
+
+ cancel_work_sync(&mgr->wait_kernel_init_dump_work);
+ /* TODO (b/200169232) Remove this once we're using devm_memremap */
+ memunmap(gxp->coredumpbuf.vaddr);
+
+ mutex_destroy(&mgr->lock);
+ mutex_destroy(&mgr->sscd_lock);
+ devm_kfree(mgr->gxp->dev, mgr);
+ gxp->debug_dump_mgr = NULL;
+}
diff --git a/gxp-debug-dump.h b/gxp-debug-dump.h
new file mode 100644
index 0000000..cd6bc23
--- /dev/null
+++ b/gxp-debug-dump.h
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP debug dump handler
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_DEBUG_DUMP_H__
+#define __GXP_DEBUG_DUMP_H__
+
+#include <linux/bitops.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "gxp-internal.h"
+
+#define GXP_NUM_COMMON_SEGMENTS 2
+#define GXP_NUM_CORE_SEGMENTS 7
+#define GXP_SEG_HEADER_NAME_LENGTH 32
+
+#define GXP_Q7_ICACHE_SIZE 131072 /* I-cache size in bytes */
+#define GXP_Q7_ICACHE_LINESIZE 64 /* I-cache line size in bytes */
+#define GXP_Q7_ICACHE_WAYS 4
+#define GXP_Q7_ICACHE_SETS ((GXP_Q7_ICACHE_SIZE / GXP_Q7_ICACHE_WAYS) / \
+ GXP_Q7_ICACHE_LINESIZE)
+#define GXP_Q7_ICACHE_WORDS_PER_LINE (GXP_Q7_ICACHE_LINESIZE / sizeof(u32))
+
+#define GXP_Q7_DCACHE_SIZE 65536 /* D-cache size in bytes */
+#define GXP_Q7_DCACHE_LINESIZE 64 /* D-cache line size in bytes */
+#define GXP_Q7_DCACHE_WAYS 4
+#define GXP_Q7_DCACHE_SETS ((GXP_Q7_DCACHE_SIZE / GXP_Q7_DCACHE_WAYS) / \
+ GXP_Q7_DCACHE_LINESIZE)
+#define GXP_Q7_DCACHE_WORDS_PER_LINE (GXP_Q7_DCACHE_LINESIZE / sizeof(u32))
+#define GXP_Q7_NUM_AREGS 64
+#define GXP_Q7_DCACHE_TAG_RAMS 2
+
+#define GXP_DEBUG_DUMP_INT 0x1
+#define GXP_DEBUG_DUMP_INT_MASK BIT(GXP_DEBUG_DUMP_INT)
+#define GXP_DEBUG_DUMP_RETRY_NUM 5
+
+struct gxp_timer_registers {
+ u32 comparator;
+ u32 control;
+ u32 value;
+};
+
+struct gxp_lpm_transition_registers {
+ u32 next_state;
+ u32 seq_addr;
+ u32 timer_val;
+ u32 timer_en;
+ u32 trigger_num;
+ u32 trigger_en;
+};
+
+struct gxp_lpm_state_table_registers {
+ struct gxp_lpm_transition_registers trans[PSM_TRANS_COUNT];
+ u32 enable_state;
+};
+
+struct gxp_lpm_psm_registers {
+ struct gxp_lpm_state_table_registers state_table[PSM_STATE_TABLE_COUNT];
+ u32 data[PSM_DATA_COUNT];
+ u32 cfg;
+ u32 status;
+ u32 debug_cfg;
+ u32 break_addr;
+ u32 gpin_lo_rd;
+ u32 gpin_hi_rd;
+ u32 gpout_lo_rd;
+ u32 gpout_hi_rd;
+ u32 debug_status;
+};
+
+struct gxp_common_registers {
+ u32 aurora_revision;
+ u32 common_int_pol_0;
+ u32 common_int_pol_1;
+ u32 dedicated_int_pol;
+ u32 raw_ext_int;
+ u32 core_pd[CORE_PD_COUNT];
+ u32 global_counter_low;
+ u32 global_counter_high;
+ u32 wdog_control;
+ u32 wdog_value;
+ struct gxp_timer_registers timer[TIMER_COUNT];
+ u32 doorbell[DOORBELL_COUNT];
+ u32 sync_barrier[SYNC_BARRIER_COUNT];
+};
+
+struct gxp_lpm_registers {
+ u32 lpm_version;
+ u32 trigger_csr_start;
+ u32 imem_start;
+ u32 lpm_config;
+ u32 psm_descriptor[PSM_DESCRIPTOR_COUNT];
+ u32 events_en[EVENTS_EN_COUNT];
+ u32 events_inv[EVENTS_INV_COUNT];
+ u32 function_select;
+ u32 trigger_status;
+ u32 event_status;
+ u32 ops[OPS_COUNT];
+ struct gxp_lpm_psm_registers psm_regs[PSM_COUNT];
+};
+
+struct gxp_core_header {
+ u32 core_id; /* Aurora core ID */
+ u32 dump_available; /* Dump data is available for core*/
+ u32 dump_req_reason; /* Code indicating reason for debug dump request */
+ u32 crash_reason; /* Error code identifying crash reason */
+ u32 fw_version; /* Firmware version */
+ u32 core_dump_size; /* Size of core dump */
+};
+
+struct gxp_seg_header {
+ char name[GXP_SEG_HEADER_NAME_LENGTH]; /* Name of data type */
+ u32 size; /* Size of segment data */
+ u32 valid; /* Validity of segment data */
+};
+
+struct gxp_core_dump_header {
+ struct gxp_core_header core_header;
+ struct gxp_seg_header seg_header[GXP_NUM_CORE_SEGMENTS];
+};
+
+struct gxp_common_dump_data {
+ struct gxp_common_registers common_regs; /* Seg 0 */
+ struct gxp_lpm_registers lpm_regs; /* Seg 1 */
+};
+
+struct gxp_common_dump {
+ struct gxp_seg_header seg_header[GXP_NUM_COMMON_SEGMENTS];
+ struct gxp_common_dump_data common_dump_data;
+};
+
+struct gxp_core_dump {
+ struct gxp_core_dump_header core_dump_header[GXP_NUM_CORES];
+ /*
+ * A collection of 'GXP_NUM_CORES' core dumps;
+ * Each is core_dump_header[i].core_dump_size bytes long.
+ */
+ uint32_t dump_data[];
+};
+
+struct gxp_debug_dump_manager {
+ struct gxp_dev *gxp;
+ struct gxp_core_dump *core_dump; /* start of the core dump */
+ void *sscd_dev;
+ void *sscd_pdata;
+ dma_addr_t debug_dump_dma_handle; /* dma handle for debug dump */
+ /* Lock protects kernel_init_dump_pending and kernel_init_dump_waitq */
+ struct mutex lock;
+ /* Keep track of which cores have kernel-initiated core dump ready */
+ int kernel_init_dump_pending;
+ wait_queue_head_t kernel_init_dump_waitq;
+ struct work_struct wait_kernel_init_dump_work;
+ /* SSCD lock to ensure SSCD is only processing one report at a time */
+ struct mutex sscd_lock;
+};
+
+int gxp_debug_dump_init(struct gxp_dev *gxp, void *sscd_dev, void *sscd_pdata);
+void gxp_debug_dump_exit(struct gxp_dev *gxp);
+void gxp_debug_dump_process_dump(struct work_struct *work);
+
+#endif /* __GXP_DEBUG_DUMP_H__ */
diff --git a/gxp-debugfs.c b/gxp-debugfs.c
new file mode 100644
index 0000000..e8991ec
--- /dev/null
+++ b/gxp-debugfs.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP debugfs support.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include "gxp.h"
+#include "gxp-debug-dump.h"
+#include "gxp-debugfs.h"
+#include "gxp-firmware.h"
+#include "gxp-firmware-data.h"
+#include "gxp-internal.h"
+#include "gxp-lpm.h"
+#include "gxp-mailbox.h"
+#include "gxp-vd.h"
+
+static int gxp_debugfs_lpm_test(void *data, u64 val)
+{
+ struct gxp_dev *gxp = (struct gxp_dev *) data;
+
+ dev_info(gxp->dev, "%llu\n", val);
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(gxp_lpm_test_fops, NULL, gxp_debugfs_lpm_test,
+ "%llu\n");
+
+static int gxp_debugfs_mailbox(void *data, u64 val)
+{
+ int core;
+ struct gxp_command cmd;
+ struct gxp_response resp;
+ struct gxp_dev *gxp = (struct gxp_dev *)data;
+
+ core = val / 1000;
+ if (core >= GXP_NUM_CORES) {
+ dev_notice(gxp->dev,
+ "Mailbox for core %d doesn't exist.\n", core);
+ return -EINVAL;
+ }
+
+ if (gxp->mailbox_mgr == NULL ||
+ gxp->mailbox_mgr->mailboxes[core] == NULL) {
+ dev_notice(gxp->dev,
+ "Unable to send mailbox command -- mailbox %d not ready\n",
+ core);
+ return -EINVAL;
+ }
+
+ cmd.code = (u16) val;
+ cmd.priority = 0;
+ cmd.buffer_descriptor.address = 0;
+ cmd.buffer_descriptor.size = 0;
+ cmd.buffer_descriptor.flags = 0;
+
+ gxp_mailbox_execute_cmd(gxp->mailbox_mgr->mailboxes[core], &cmd, &resp);
+
+ dev_info(gxp->dev,
+ "Mailbox Command Sent: cmd.code=%d, resp.status=%d, resp.retval=%d\n",
+ cmd.code, resp.status, resp.retval);
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(gxp_mailbox_fops, NULL, gxp_debugfs_mailbox, "%llu\n");
+
+static int gxp_debugfs_pingpong(void *data, u64 val)
+{
+ int core;
+ struct gxp_command cmd;
+ struct gxp_response resp;
+ struct gxp_dev *gxp = (struct gxp_dev *)data;
+
+ core = val / 1000;
+ if (core >= GXP_NUM_CORES) {
+ dev_notice(gxp->dev,
+ "Mailbox for core %d doesn't exist.\n", core);
+ return -EINVAL;
+ }
+
+ if (gxp->mailbox_mgr == NULL ||
+ gxp->mailbox_mgr->mailboxes[core] == NULL) {
+ dev_notice(
+ gxp->dev,
+ "Unable to send mailbox pingpong -- mailbox %d not ready\n",
+ core);
+ return -EINVAL;
+ }
+
+ cmd.code = GXP_MBOX_CODE_PINGPONG;
+ cmd.priority = 0;
+ cmd.buffer_descriptor.address = 0;
+ cmd.buffer_descriptor.size = 0;
+ cmd.buffer_descriptor.flags = (u32) val;
+
+ gxp_mailbox_execute_cmd(gxp->mailbox_mgr->mailboxes[core], &cmd, &resp);
+
+ dev_info(
+ gxp->dev,
+ "Mailbox Pingpong Sent to core %d: val=%d, resp.status=%d, resp.retval=%d\n",
+ core, cmd.buffer_descriptor.flags, resp.status, resp.retval);
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(gxp_pingpong_fops, NULL, gxp_debugfs_pingpong,
+ "%llu\n");
+
+static int gxp_firmware_run_set(void *data, u64 val)
+{
+ struct gxp_dev *gxp = (struct gxp_dev *) data;
+ int ret = 0;
+
+ if (val) {
+ if (gxp->debugfs_client) {
+ dev_err(gxp->dev, "Firmware already running!\n");
+ return -EIO;
+ }
+
+ /*
+ * Cleanup any bad state or corruption the device might've
+ * caused
+ */
+ gxp_fw_data_destroy(gxp);
+ gxp_fw_data_init(gxp);
+
+ gxp->debugfs_client = gxp_client_create(gxp);
+ if (IS_ERR(gxp->debugfs_client)) {
+ dev_err(gxp->dev, "Failed to create client\n");
+ ret = PTR_ERR(gxp->debugfs_client);
+ gxp->debugfs_client = NULL;
+ return ret;
+ }
+
+ ret = gxp_vd_allocate(gxp->debugfs_client, GXP_NUM_CORES);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to allocate VD\n");
+ gxp_client_destroy(gxp->debugfs_client);
+ gxp->debugfs_client = NULL;
+ return ret;
+ }
+ } else {
+ if (!gxp->debugfs_client) {
+ dev_err(gxp->dev, "Firmware not running!\n");
+ return -EIO;
+ }
+ gxp_client_destroy(gxp->debugfs_client);
+ gxp->debugfs_client = NULL;
+ }
+
+ return ret;
+}
+
+static int gxp_firmware_run_get(void *data, u64 *val)
+{
+ struct gxp_dev *gxp = (struct gxp_dev *) data;
+
+ *val = gxp->firmware_running;
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(gxp_firmware_run_fops, gxp_firmware_run_get,
+ gxp_firmware_run_set, "%llx\n");
+
+static int gxp_blk_powerstate_set(void *data, u64 val)
+{
+ struct gxp_dev *gxp = (struct gxp_dev *)data;
+ int ret = 0;
+
+ if (val >= AUR_DVFS_MIN_STATE) {
+ ret = gxp_blk_set_state(gxp, val);
+ } else {
+ ret = -EINVAL;
+ dev_err(gxp->dev, "Incorrect state %llu\n", val);
+ }
+ return ret;
+}
+
+static int gxp_blk_powerstate_get(void *data, u64 *val)
+{
+ struct gxp_dev *gxp = (struct gxp_dev *)data;
+
+ *val = gxp_blk_get_state(gxp);
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(gxp_blk_powerstate_fops, gxp_blk_powerstate_get,
+ gxp_blk_powerstate_set, "%llx\n");
+
+static int gxp_debugfs_coredump(void *data, u64 val)
+{
+ return gxp_debugfs_mailbox(data, GXP_MBOX_CODE_COREDUMP);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(gxp_coredump_fops, NULL, gxp_debugfs_coredump,
+ "%llu\n");
+
+void gxp_create_debugfs(struct gxp_dev *gxp)
+{
+ gxp->d_entry = debugfs_create_dir("gxp", NULL);
+ if (IS_ERR_OR_NULL(gxp->d_entry))
+ return;
+
+ debugfs_create_file("lpm_test", 0200, gxp->d_entry, gxp,
+ &gxp_lpm_test_fops);
+ debugfs_create_file("mailbox", 0200, gxp->d_entry, gxp,
+ &gxp_mailbox_fops);
+ debugfs_create_file("pingpong", 0200, gxp->d_entry, gxp,
+ &gxp_pingpong_fops);
+ debugfs_create_file("firmware_run", 0600, gxp->d_entry, gxp,
+ &gxp_firmware_run_fops);
+ debugfs_create_file("blk_powerstate", 0600, gxp->d_entry, gxp,
+ &gxp_blk_powerstate_fops);
+ debugfs_create_file("coredump", 0200, gxp->d_entry, gxp,
+ &gxp_coredump_fops);
+}
+
+void gxp_remove_debugfs(struct gxp_dev *gxp)
+{
+ if (gxp->debugfs_client)
+ gxp_client_destroy(gxp->debugfs_client);
+
+ debugfs_remove_recursive(gxp->d_entry);
+}
diff --git a/gxp-debugfs.h b/gxp-debugfs.h
new file mode 100644
index 0000000..4b42546
--- /dev/null
+++ b/gxp-debugfs.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP debugfs support.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_DEBUGFS_H__
+#define __GXP_DEBUGFS_H__
+
+#include "gxp-internal.h"
+
+void gxp_create_debugfs(struct gxp_dev *gxp);
+void gxp_remove_debugfs(struct gxp_dev *gxp);
+
+#endif /* __GXP_DEBUGFS_H__ */
diff --git a/gxp-dma-iommu.c b/gxp-dma-iommu.c
new file mode 100644
index 0000000..a81d9b1
--- /dev/null
+++ b/gxp-dma-iommu.c
@@ -0,0 +1,826 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP DMA implemented via IOMMU.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include "gxp-config.h"
+#include "gxp-dma.h"
+#include "gxp-dma-iommu.h"
+#include "gxp-iova.h"
+#include "gxp-mapping.h"
+
+struct gxp_dma_iommu_manager {
+ struct gxp_dma_manager dma_mgr;
+ struct iommu_domain *default_domain;
+ struct iommu_domain *core_domains[GXP_NUM_CORES];
+ int core_vids[GXP_NUM_CORES];
+ void __iomem *idma_ssmt_base;
+ void __iomem *inst_data_ssmt_base;
+};
+
+/**
+ * dma_info_to_prot - Translate DMA API directions and attributes to IOMMU API
+ * page flags.
+ * @dir: Direction of DMA transfer
+ * @coherent: Is the DMA master cache-coherent?
+ * @attrs: DMA attributes for the mapping
+ *
+ * From drivers/iommu/dma-iommu.c
+ *
+ * Return: corresponding IOMMU API page protection flags
+ */
+static int dma_info_to_prot(enum dma_data_direction dir, bool coherent,
+ unsigned long attrs)
+{
+ int prot = coherent ? IOMMU_CACHE : 0;
+
+ if (attrs & DMA_ATTR_PRIVILEGED)
+ prot |= IOMMU_PRIV;
+ switch (dir) {
+ case DMA_BIDIRECTIONAL:
+ return prot | IOMMU_READ | IOMMU_WRITE;
+ case DMA_TO_DEVICE:
+ return prot | IOMMU_READ;
+ case DMA_FROM_DEVICE:
+ return prot | IOMMU_WRITE;
+ default:
+ return 0;
+ }
+}
+
+/* SSMT handling */
+
+#define INST_SID_FOR_CORE(_x_) ((1 << 6) | ((_x_) << 4) | (0 << 3))
+#define DATA_SID_FOR_CORE(_x_) ((1 << 6) | ((_x_) << 4) | (1 << 3))
+#define IDMA_SID_FOR_CORE(_x_) ((1 << 6) | ((_x_) << 4))
+
+static inline void ssmt_set_vid_for_sid(void __iomem *ssmt, int vid, u8 sid)
+{
+ /* NS_READ_STREAM_VID_<sid> */
+ writel(vid, (ssmt) + 0x1000u + (0x4u * (sid)));
+ /* NS_WRITE_STREAM_VID_<sid> */
+ writel(vid, (ssmt) + 0x1200u + (0x4u * (sid)));
+}
+
+static inline int ssmt_init(struct gxp_dev *gxp,
+ struct gxp_dma_iommu_manager *mgr)
+{
+ struct platform_device *pdev =
+ container_of(gxp->dev, struct platform_device, dev);
+ struct resource *r;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ssmt_idma");
+ if (!r) {
+ dev_err(gxp->dev, "Failed to find IDMA SSMT register base\n");
+ return -EINVAL;
+ }
+
+ mgr->idma_ssmt_base = devm_ioremap_resource(gxp->dev, r);
+ if (IS_ERR(mgr->idma_ssmt_base)) {
+ dev_err(gxp->dev,
+ "Failed to map IDMA SSMT register base (%ld)\n",
+ PTR_ERR(mgr->idma_ssmt_base));
+ return PTR_ERR(mgr->idma_ssmt_base);
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "ssmt_inst_data");
+ if (!r) {
+ dev_err(gxp->dev,
+ "Failed to find instruction/data SSMT register base\n");
+ return -EINVAL;
+ }
+
+ mgr->inst_data_ssmt_base = devm_ioremap_resource(gxp->dev, r);
+ if (IS_ERR(mgr->inst_data_ssmt_base)) {
+ dev_err(gxp->dev,
+ "Failed to map instruction/data SSMT register base (%ld)\n",
+ PTR_ERR(mgr->inst_data_ssmt_base));
+ return PTR_ERR(mgr->inst_data_ssmt_base);
+ }
+
+ return 0;
+}
+
+/* Fault handler */
+
+static int sysmmu_fault_handler(struct iommu_fault *fault, void *token)
+{
+ struct gxp_dev *gxp = (struct gxp_dev *)token;
+
+ switch (fault->type) {
+ case IOMMU_FAULT_DMA_UNRECOV:
+ dev_err(gxp->dev, "Unrecoverable IOMMU fault!\n");
+ break;
+ case IOMMU_FAULT_PAGE_REQ:
+ dev_err(gxp->dev, "IOMMU page request fault!\n");
+ break;
+ default:
+ dev_err(gxp->dev, "Unexpected IOMMU fault type (%d)\n",
+ fault->type);
+ return -EAGAIN;
+ }
+
+ /*
+ * Normally the iommu driver should fill out the `event` struct for
+ * unrecoverable errors, and the `prm` struct for page request faults.
+ * The SysMMU driver, instead, always fills out the `event` struct.
+ *
+ * Note that the `fetch_addr` and `perm` fields are never filled out,
+ * so we skip printing them.
+ */
+ dev_err(gxp->dev, "reason = %08X\n", fault->event.reason);
+ dev_err(gxp->dev, "flags = %08X\n", fault->event.flags);
+ dev_err(gxp->dev, "pasid = %08X\n", fault->event.pasid);
+ dev_err(gxp->dev, "addr = %llX\n", fault->event.addr);
+
+ // Tell the IOMMU driver to carry on
+ return -EAGAIN;
+}
+
+/* gxp-dma.h Interface */
+
+int gxp_dma_init(struct gxp_dev *gxp)
+{
+ struct gxp_dma_iommu_manager *mgr;
+ unsigned int core;
+ int ret;
+
+ /* GXP can only address 32-bit IOVAs */
+ ret = dma_set_mask_and_coherent(gxp->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(gxp->dev, "Failed to set DMA mask\n");
+ return ret;
+ }
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+
+/* TODO(b/201505925): remove this and prepare a of_node in unittests */
+/* SSMT is not supported in unittests */
+#ifndef CONFIG_GXP_TEST
+ ret = ssmt_init(gxp, mgr);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to find SSMT\n");
+ return ret;
+ }
+#endif
+
+ mgr->default_domain = iommu_get_domain_for_dev(gxp->dev);
+ if (!mgr->default_domain) {
+ dev_err(gxp->dev, "Failed to find default IOMMU domain\n");
+ return -EIO;
+ }
+
+ if (iommu_register_device_fault_handler(gxp->dev, sysmmu_fault_handler,
+ gxp)) {
+ dev_err(gxp->dev, "Failed to register iommu fault handler\n");
+ return -EIO;
+ }
+
+ iommu_dev_enable_feature(gxp->dev, IOMMU_DEV_FEAT_AUX);
+ if (!iommu_dev_feature_enabled(gxp->dev, IOMMU_DEV_FEAT_AUX)) {
+ dev_err(gxp->dev, "Failed to enable aux support in SysMMU\n");
+ goto err_unreg_fault_handler;
+ }
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ mgr->core_domains[core] = iommu_domain_alloc(gxp->dev->bus);
+ if (iommu_aux_attach_device(mgr->core_domains[core],
+ gxp->dev)) {
+ iommu_domain_free(mgr->core_domains[core]);
+ goto err_detach_aux_domains;
+ }
+ mgr->core_vids[core] =
+ iommu_aux_get_pasid(mgr->core_domains[core], gxp->dev);
+ dev_notice(gxp->dev, "SysMMU: core%u assigned vid %d\n", core,
+ mgr->core_vids[core]);
+/* SSMT is not supported in unittests */
+#ifndef CONFIG_GXP_TEST
+ /*
+ * TODO(b/194347483) SSMT programming must happen each time
+ * BLK_AURORA is powered on, but currently BLK_AURORA is
+ * turned on at probe and left on until removal.
+ */
+ ssmt_set_vid_for_sid(mgr->idma_ssmt_base, mgr->core_vids[core],
+ IDMA_SID_FOR_CORE(core));
+ ssmt_set_vid_for_sid(mgr->inst_data_ssmt_base,
+ mgr->core_vids[core],
+ INST_SID_FOR_CORE(core));
+ ssmt_set_vid_for_sid(mgr->inst_data_ssmt_base,
+ mgr->core_vids[core],
+ DATA_SID_FOR_CORE(core));
+#endif
+ }
+
+ gxp->dma_mgr = &(mgr->dma_mgr);
+
+ return 0;
+
+err_detach_aux_domains:
+ /* Detach and free any aux domains successfully setup */
+ for (core -= 1; core >= 0; core--) {
+ iommu_aux_detach_device(mgr->core_domains[core], gxp->dev);
+ iommu_domain_free(mgr->core_domains[core]);
+ }
+
+err_unreg_fault_handler:
+ if (iommu_unregister_device_fault_handler(gxp->dev))
+ dev_err(gxp->dev,
+ "Failed to unregister SysMMU fault handler\n");
+
+ return -EIO;
+}
+
+void gxp_dma_exit(struct gxp_dev *gxp)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ unsigned int core;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ iommu_aux_detach_device(mgr->core_domains[core], gxp->dev);
+ iommu_domain_free(mgr->core_domains[core]);
+ }
+
+ if (iommu_unregister_device_fault_handler(gxp->dev))
+ dev_err(gxp->dev,
+ "Failed to unregister SysMMU fault handler\n");
+}
+
+#define SYNC_BARRIERS_SIZE 0x100000
+#define SYNC_BARRIERS_TOP_OFFSET 0x100000
+#define EXT_TPU_MBX_SIZE 0x2000
+
+/* Offset from mailbox base to the device interface that needs to be mapped */
+#define MAILBOX_DEVICE_INTERFACE_OFFSET 0x10000
+
+int gxp_dma_map_resources(struct gxp_dev *gxp)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ unsigned int core;
+ int ret = 0;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ ret = iommu_map(mgr->core_domains[core], GXP_IOVA_AURORA_TOP,
+ gxp->regs.paddr, gxp->regs.size,
+ IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ /*
+ * Firmware expects to access the sync barriers at a separate
+ * address, lower than the rest of the AURORA_TOP registers.
+ */
+ ret = iommu_map(mgr->core_domains[core], GXP_IOVA_SYNC_BARRIERS,
+ gxp->regs.paddr + SYNC_BARRIERS_TOP_OFFSET,
+ SYNC_BARRIERS_SIZE, IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ ret = iommu_map(mgr->core_domains[core], GXP_IOVA_MAILBOX(core),
+ gxp->mbx[core].paddr +
+ MAILBOX_DEVICE_INTERFACE_OFFSET,
+ gxp->mbx[core].size, IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ /*
+ * TODO(b/202213606): Map FW regions of all cores in a VD for
+ * each other at VD creation.
+ */
+ ret = iommu_map(mgr->core_domains[core], GXP_IOVA_FIRMWARE(0),
+ gxp->fwbufs[0].paddr,
+ gxp->fwbufs[0].size * GXP_NUM_CORES,
+ IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ ret = iommu_map(mgr->core_domains[core], GXP_IOVA_CORE_DUMP,
+ gxp->coredumpbuf.paddr, gxp->coredumpbuf.size,
+ IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ ret = iommu_map(mgr->core_domains[core], GXP_IOVA_FW_DATA,
+ gxp->fwdatabuf.paddr, gxp->fwdatabuf.size,
+ IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ /* Only map the TPU mailboxes if they were found on probe */
+ if (gxp->tpu_dev.mbx_paddr) {
+ ret = iommu_map(
+ mgr->core_domains[core],
+ GXP_IOVA_EXT_TPU_MBX + core * EXT_TPU_MBX_SIZE,
+ gxp->tpu_dev.mbx_paddr +
+ core * EXT_TPU_MBX_SIZE,
+ EXT_TPU_MBX_SIZE, IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ goto err;
+ }
+ gxp->mbx[core].daddr = GXP_IOVA_MAILBOX(core);
+ gxp->fwbufs[core].daddr = GXP_IOVA_FIRMWARE(core);
+ }
+ gxp->regs.daddr = GXP_IOVA_AURORA_TOP;
+ gxp->coredumpbuf.daddr = GXP_IOVA_CORE_DUMP;
+ gxp->fwdatabuf.daddr = GXP_IOVA_FW_DATA;
+
+ return ret;
+
+err:
+ /*
+ * Attempt to unmap all resources.
+ * Any resource that hadn't been mapped yet will cause `iommu_unmap()`
+ * to return immediately, so its safe to try to unmap everything.
+ */
+ gxp_dma_unmap_resources(gxp);
+ return ret;
+}
+
+void gxp_dma_unmap_resources(struct gxp_dev *gxp)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ unsigned int core;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ iommu_unmap(mgr->core_domains[core], GXP_IOVA_AURORA_TOP,
+ gxp->regs.size);
+ iommu_unmap(mgr->core_domains[core], GXP_IOVA_SYNC_BARRIERS,
+ SYNC_BARRIERS_SIZE);
+ iommu_unmap(mgr->core_domains[core], GXP_IOVA_MAILBOX(core),
+ gxp->mbx[core].size);
+ /*
+ * TODO(b/202213606): A core should only have access to the FW
+ * of other cores if they're in the same VD, and have the FW
+ * region unmapped on VD destruction.
+ */
+ iommu_unmap(mgr->core_domains[core], GXP_IOVA_FIRMWARE(0),
+ gxp->fwbufs[0].size * GXP_NUM_CORES);
+ iommu_unmap(mgr->core_domains[core], GXP_IOVA_CORE_DUMP,
+ gxp->coredumpbuf.size);
+ iommu_unmap(mgr->core_domains[core], GXP_IOVA_FW_DATA,
+ gxp->fwdatabuf.size);
+ /* Only unmap the TPU mailboxes if they were found on probe */
+ if (gxp->tpu_dev.mbx_paddr) {
+ iommu_unmap(mgr->core_domains[core],
+ GXP_IOVA_EXT_TPU_MBX +
+ core * EXT_TPU_MBX_SIZE,
+ EXT_TPU_MBX_SIZE);
+ }
+ }
+}
+
+static inline struct sg_table *
+alloc_sgt_for_buffer(void *ptr, size_t size,
+ struct iommu_domain *domain,
+ dma_addr_t daddr)
+{
+ struct sg_table *sgt;
+ ulong offset;
+ uint num_ents;
+ int ret;
+ struct scatterlist *next;
+ size_t size_in_page;
+ struct page *page;
+ void *va_base = ptr;
+
+ /* Calculate the number of entries needed in the table */
+ offset = offset_in_page(va_base);
+ if (unlikely((size + offset) / PAGE_SIZE >= UINT_MAX - 1 ||
+ size + offset < size))
+ return ERR_PTR(-EINVAL);
+ num_ents = (size + offset) / PAGE_SIZE;
+ if ((size + offset) % PAGE_SIZE)
+ num_ents++;
+
+ /* Allocate and setup the table for filling out */
+ sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
+ if (!sgt)
+ return ERR_PTR(-ENOMEM);
+
+ ret = sg_alloc_table(sgt, num_ents, GFP_KERNEL);
+ if (ret) {
+ kfree(sgt);
+ return ERR_PTR(ret);
+ }
+ next = sgt->sgl;
+
+ /*
+ * Fill in the first scatterlist entry.
+ * This is the only one which may start at a non-page-aligned address.
+ */
+ size_in_page = size > (PAGE_SIZE - offset_in_page(ptr)) ?
+ PAGE_SIZE - offset_in_page(ptr) :
+ size;
+ page = phys_to_page(iommu_iova_to_phys(domain, daddr));
+ sg_set_page(next, page, size_in_page, offset_in_page(ptr));
+ size -= size_in_page;
+ ptr += size_in_page;
+ next = sg_next(next);
+
+ while (size > 0) {
+ /*
+ * Fill in and link the next scatterlist entry.
+ * `ptr` is now page-aligned, so it is only necessary to check
+ * if this entire page is part of the buffer, or if the buffer
+ * ends part way through the page (which means this is the last
+ * entry in the list).
+ */
+ size_in_page = size > PAGE_SIZE ? PAGE_SIZE : size;
+ page = phys_to_page(iommu_iova_to_phys(
+ domain, daddr + (unsigned long long)(ptr - va_base)));
+ sg_set_page(next, page, size_in_page, 0);
+
+ size -= size_in_page;
+ ptr += size_in_page;
+ next = sg_next(next);
+ }
+
+ return sgt;
+}
+
+void *gxp_dma_alloc_coherent(struct gxp_dev *gxp, uint core_list, size_t size,
+ dma_addr_t *dma_handle, gfp_t flag,
+ uint gxp_dma_flags)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ void *buf;
+ struct sg_table *sgt;
+ dma_addr_t daddr;
+ int core;
+ int ret;
+
+ size = size < PAGE_SIZE ? PAGE_SIZE : size;
+
+ /* Allocate a coherent buffer in the default domain */
+ buf = dma_alloc_coherent(gxp->dev, size, &daddr, flag);
+ if (!buf) {
+ dev_err(gxp->dev, "Failed to allocate coherent buffer\n");
+ return NULL;
+ }
+
+ sgt = alloc_sgt_for_buffer(buf, size, mgr->default_domain, daddr);
+ if (IS_ERR(sgt)) {
+ dev_err(gxp->dev,
+ "Failed to allocate sgt for coherent buffer\n");
+ dma_free_coherent(gxp->dev, size, buf, daddr);
+ return NULL;
+ }
+
+ /* Create identical mappings in the specified cores' domains */
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+
+ ret = iommu_map_sg(mgr->core_domains[core], daddr, sgt->sgl,
+ sgt->nents, IOMMU_READ | IOMMU_WRITE);
+ if (ret != size)
+ goto err;
+ }
+
+ if (dma_handle)
+ *dma_handle = daddr;
+
+ sg_free_table(sgt);
+ kfree(sgt);
+
+ return buf;
+
+err:
+ for (core -= 1; core >= 0; core--)
+ iommu_unmap(mgr->core_domains[core], daddr, size);
+ dma_free_coherent(gxp->dev, size, buf, daddr);
+
+ sg_free_table(sgt);
+ kfree(sgt);
+
+ return NULL;
+}
+
+void gxp_dma_free_coherent(struct gxp_dev *gxp, uint core_list, size_t size,
+ void *cpu_addr, dma_addr_t dma_handle)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ int core;
+
+ size = size < PAGE_SIZE ? PAGE_SIZE : size;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+
+ if (size !=
+ iommu_unmap(mgr->core_domains[core], dma_handle, size))
+ dev_warn(gxp->dev, "Failed to unmap coherent buffer\n");
+ }
+
+ dma_free_coherent(gxp->dev, size, cpu_addr, dma_handle);
+}
+
+dma_addr_t gxp_dma_map_single(struct gxp_dev *gxp, uint core_list,
+ void *cpu_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ dma_addr_t daddr;
+ phys_addr_t paddr;
+ int prot = dma_info_to_prot(direction, 0, attrs);
+ int core;
+
+ daddr = dma_map_single_attrs(gxp->dev, cpu_addr, size, direction,
+ attrs);
+ if (dma_mapping_error(gxp->dev, daddr))
+ return DMA_MAPPING_ERROR;
+
+ paddr = iommu_iova_to_phys(mgr->default_domain, daddr);
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+
+ if (iommu_map(mgr->core_domains[core], daddr, paddr, size,
+ prot))
+ goto err;
+ }
+
+ return daddr;
+
+err:
+ for (core -= 1; core >= 0; core--)
+ iommu_unmap(mgr->core_domains[core], daddr, size);
+ dma_unmap_single_attrs(gxp->dev, daddr, size, direction,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ return DMA_MAPPING_ERROR;
+}
+
+void gxp_dma_unmap_single(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ int core;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+ if (size !=
+ iommu_unmap(mgr->core_domains[core], dma_addr, size))
+ dev_warn(gxp->dev, "Failed to unmap single\n");
+ }
+
+ dma_unmap_single_attrs(gxp->dev, dma_addr, size, direction, attrs);
+}
+
+dma_addr_t gxp_dma_map_page(struct gxp_dev *gxp, uint core_list,
+ struct page *page, unsigned long offset,
+ size_t size, enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ dma_addr_t daddr;
+ phys_addr_t paddr;
+ int prot = dma_info_to_prot(direction, 0, attrs);
+ int core;
+
+ daddr = dma_map_page_attrs(gxp->dev, page, offset, size, direction,
+ attrs);
+ if (dma_mapping_error(gxp->dev, daddr))
+ return DMA_MAPPING_ERROR;
+
+ paddr = iommu_iova_to_phys(mgr->default_domain, daddr);
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+
+ if (iommu_map(mgr->core_domains[core], daddr, paddr, size,
+ prot))
+ goto err;
+ }
+
+ return daddr;
+
+err:
+ for (core -= 1; core >= 0; core--)
+ iommu_unmap(mgr->core_domains[core], daddr, size);
+ dma_unmap_page_attrs(gxp->dev, daddr, size, direction,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ return DMA_MAPPING_ERROR;
+}
+
+void gxp_dma_unmap_page(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction, unsigned long attrs)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ int core;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+ if (size !=
+ iommu_unmap(mgr->core_domains[core], dma_addr, size))
+ dev_warn(gxp->dev, "Failed to unmap page\n");
+ }
+
+ dma_unmap_page_attrs(gxp->dev, dma_addr, size, direction, attrs);
+}
+
+dma_addr_t gxp_dma_map_resource(struct gxp_dev *gxp, uint core_list,
+ phys_addr_t phys_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ dma_addr_t daddr;
+ int prot = dma_info_to_prot(direction, 0, attrs);
+ int core;
+
+ daddr = dma_map_resource(gxp->dev, phys_addr, size, direction, attrs);
+ if (dma_mapping_error(gxp->dev, daddr))
+ return DMA_MAPPING_ERROR;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+
+ if (iommu_map(mgr->core_domains[core], daddr, phys_addr, size,
+ prot))
+ goto err;
+ }
+
+ return daddr;
+
+err:
+ for (core -= 1; core >= 0; core--)
+ iommu_unmap(mgr->core_domains[core], daddr, size);
+ dma_unmap_resource(gxp->dev, daddr, size, direction,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ return DMA_MAPPING_ERROR;
+}
+
+void gxp_dma_unmap_resource(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ int core;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+ if (size !=
+ iommu_unmap(mgr->core_domains[core], dma_addr, size))
+ dev_warn(gxp->dev, "Failed to unmap resource\n");
+ }
+
+ dma_unmap_resource(gxp->dev, dma_addr, size, direction, attrs);
+}
+
+int gxp_dma_map_sg(struct gxp_dev *gxp, uint core_list, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ int nents_mapped;
+ dma_addr_t daddr;
+ int prot = dma_info_to_prot(direction, 0, attrs);
+ int core;
+ /* Variables needed to cleanup if an error occurs */
+ struct scatterlist *s;
+ int i;
+ size_t size = 0;
+
+ nents_mapped = dma_map_sg_attrs(gxp->dev, sg, nents, direction, attrs);
+ if (!nents_mapped)
+ return 0;
+
+ daddr = sg_dma_address(sg);
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+
+ if (!iommu_map_sg(mgr->core_domains[core], daddr, sg, nents,
+ prot))
+ goto err;
+ }
+
+ return nents_mapped;
+
+err:
+ for_each_sg(sg, s, nents, i) {
+ size += sg_dma_len(s);
+ }
+
+ for (core -= 1; core >= 0; core--)
+ iommu_unmap(mgr->core_domains[core], daddr, size);
+ dma_unmap_sg_attrs(gxp->dev, sg, nents, direction, attrs);
+ return 0;
+}
+
+void gxp_dma_unmap_sg(struct gxp_dev *gxp, uint core_list,
+ struct scatterlist *sg, int nents,
+ enum dma_data_direction direction, unsigned long attrs)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+ struct scatterlist *s;
+ int i;
+ size_t size = 0;
+ int core;
+
+ for_each_sg(sg, s, nents, i) {
+ size += sg_dma_len(s);
+ }
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (!(core_list & BIT(core)))
+ continue;
+ if (!iommu_unmap(mgr->core_domains[core], sg_dma_address(sg),
+ size))
+ dev_warn(gxp->dev, "Failed to unmap sg\n");
+ }
+
+ dma_unmap_sg_attrs(gxp->dev, sg, nents, direction, attrs);
+}
+
+void gxp_dma_sync_single_for_cpu(struct gxp_dev *gxp, dma_addr_t dma_handle,
+ size_t size,
+ enum dma_data_direction direction)
+{
+ /* Syncing is not domain specific. Just call through to DMA API */
+ dma_sync_single_for_cpu(gxp->dev, dma_handle, size, direction);
+}
+
+void gxp_dma_sync_single_for_device(struct gxp_dev *gxp, dma_addr_t dma_handle,
+ size_t size,
+ enum dma_data_direction direction)
+{
+ /* Syncing is not domain specific. Just call through to DMA API */
+ dma_sync_single_for_device(gxp->dev, dma_handle, size, direction);
+}
+
+void gxp_dma_sync_sg_for_cpu(struct gxp_dev *gxp, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction)
+{
+ /* Syncing is not domain specific. Just call through to DMA API */
+ dma_sync_sg_for_cpu(gxp->dev, sg, nents, direction);
+}
+
+void gxp_dma_sync_sg_for_device(struct gxp_dev *gxp, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction)
+{
+ /* Syncing is not domain specific. Just call through to DMA API */
+ dma_sync_sg_for_device(gxp->dev, sg, nents, direction);
+}
+
+#ifdef CONFIG_GXP_TEST
+/*
+ * gxp-dma-iommu.h interface
+ * These APIs expose gxp-dma-iommu implementation details for unit testing.
+ * They are not meant to be used by other components fo the driver.
+ */
+
+struct iommu_domain *gxp_dma_iommu_get_default_domain(struct gxp_dev *gxp)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+
+ if (!mgr)
+ return ERR_PTR(-ENODEV);
+
+ return mgr->default_domain;
+}
+
+struct iommu_domain *gxp_dma_iommu_get_core_domain(struct gxp_dev *gxp,
+ uint core)
+{
+ struct gxp_dma_iommu_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
+
+ if (!mgr)
+ return ERR_PTR(-ENODEV);
+
+ if (core >= GXP_NUM_CORES)
+ return ERR_PTR(-EINVAL);
+
+ return mgr->core_domains[core];
+}
+#endif // CONFIG_GXP_TEST
diff --git a/gxp-dma-iommu.h b/gxp-dma-iommu.h
new file mode 100644
index 0000000..971c45f
--- /dev/null
+++ b/gxp-dma-iommu.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP DMA IOMMU-specific interface.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_DMA_IOMMU_H__
+#define __GXP_DMA_IOMMU_H__
+
+#include <linux/iommu.h>
+
+#include "gxp-internal.h"
+
+#ifdef CONFIG_GXP_TEST
+struct iommu_domain *gxp_dma_iommu_get_default_domain(struct gxp_dev *gxp);
+struct iommu_domain *gxp_dma_iommu_get_core_domain(struct gxp_dev *gxp,
+ uint core);
+#endif
+
+#endif /* __GXP_DMA_IOMMU_H__ */
diff --git a/gxp-dma-rmem.c b/gxp-dma-rmem.c
new file mode 100644
index 0000000..4e1c2ef
--- /dev/null
+++ b/gxp-dma-rmem.c
@@ -0,0 +1,576 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP DMA implemented via reserved memory carveouts.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/genalloc.h>
+#include <linux/highmem.h>
+#include <linux/mm_types.h>
+#include <linux/mutex.h>
+#include <linux/rbtree.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "gxp-config.h"
+#include "gxp-dma.h"
+#include "gxp-internal.h"
+#include "gxp-mapping.h"
+
+struct gxp_dma_bounce_buffer {
+ struct rb_node node;
+ dma_addr_t dma_handle;
+ struct page *page;
+ size_t size;
+ unsigned long offset;
+ void *buf;
+};
+
+struct gxp_dma_rmem_manager {
+ struct gxp_dma_manager dma_mgr;
+ struct gxp_mapped_resource poolbuf;
+ struct gen_pool *pool;
+ struct rb_root bounce_buffers;
+ struct mutex bounce_lock;
+};
+
+/* RB Tree Management Functions for the Bounce Buffer tree */
+
+static int bounce_buffer_put(struct gxp_dma_rmem_manager *mgr,
+ struct gxp_dma_bounce_buffer *bounce)
+{
+ struct rb_node **link;
+ struct rb_node *parent = NULL;
+ struct gxp_dma_bounce_buffer *this;
+
+ link = &mgr->bounce_buffers.rb_node;
+
+ mutex_lock(&mgr->bounce_lock);
+
+ while (*link) {
+ parent = *link;
+ this = rb_entry(parent, struct gxp_dma_bounce_buffer, node);
+
+ if (this->dma_handle > bounce->dma_handle)
+ link = &(*link)->rb_left;
+ else if (this->dma_handle < bounce->dma_handle)
+ link = &(*link)->rb_right;
+ else
+ goto out;
+ }
+
+ rb_link_node(&bounce->node, parent, link);
+ rb_insert_color(&bounce->node, &mgr->bounce_buffers);
+
+ mutex_unlock(&mgr->bounce_lock);
+
+ return 0;
+
+out:
+ mutex_unlock(&mgr->bounce_lock);
+ return -EINVAL;
+}
+
+static struct gxp_dma_bounce_buffer *
+bounce_buffer_get(struct gxp_dma_rmem_manager *mgr, dma_addr_t dma_handle)
+{
+ struct rb_node *node;
+ struct gxp_dma_bounce_buffer *this;
+
+ mutex_lock(&mgr->bounce_lock);
+
+ node = mgr->bounce_buffers.rb_node;
+
+ while (node) {
+ this = rb_entry(node, struct gxp_dma_bounce_buffer, node);
+
+ if (this->dma_handle > dma_handle) {
+ node = node->rb_left;
+ } else if (this->dma_handle + this->size <= dma_handle) {
+ node = node->rb_right;
+ } else {
+ mutex_unlock(&mgr->bounce_lock);
+ return this;
+ }
+ }
+
+ mutex_unlock(&mgr->bounce_lock);
+
+ return NULL;
+}
+
+static void bounce_buffer_remove(struct gxp_dma_rmem_manager *mgr,
+ struct gxp_dma_bounce_buffer *bounce)
+{
+ rb_erase(&bounce->node, &mgr->bounce_buffers);
+}
+
+/* gxp-dma.h Interface */
+
+int gxp_dma_init(struct gxp_dev *gxp)
+{
+ struct gxp_dma_rmem_manager *mgr;
+ struct resource r;
+ int ret;
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+
+ /* Map the reserved memory for the pool from the device tree */
+ if (gxp_acquire_rmem_resource(gxp, &r, "gxp-pool-region")) {
+ dev_err(gxp->dev, "Unable to acquire pool reserved memory\n");
+ return -ENODEV;
+ }
+
+ mgr->poolbuf.paddr = r.start;
+ mgr->poolbuf.size = resource_size(&r);
+ mgr->poolbuf.vaddr = devm_memremap(gxp->dev, mgr->poolbuf.paddr,
+ mgr->poolbuf.size, MEMREMAP_WC);
+ if (IS_ERR_OR_NULL(mgr->poolbuf.vaddr)) {
+ dev_err(gxp->dev, "Failed to map pool\n");
+ return -ENODEV;
+ }
+
+ /* Create the gen pool for mappings/coherent allocations */
+ mgr->pool = devm_gen_pool_create(gxp->dev, PAGE_SHIFT, -1, "gxp-pool");
+ if (!mgr->pool) {
+ dev_err(gxp->dev, "Failed to create memory pool\n");
+ return -ENOMEM;
+ }
+
+ ret = gen_pool_add_virt(mgr->pool, (unsigned long)mgr->poolbuf.vaddr,
+ mgr->poolbuf.paddr, mgr->poolbuf.size, -1);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to add memory to pool (ret = %d)\n",
+ ret);
+ return ret;
+ }
+
+ mgr->dma_mgr.mapping_tree = RB_ROOT;
+ mgr->bounce_buffers = RB_ROOT;
+
+ gxp->dma_mgr = &(mgr->dma_mgr);
+
+ return 0;
+}
+
+void gxp_dma_exit(struct gxp_dev *gxp)
+{
+ /* no cleanup */
+}
+
+int gxp_dma_map_resources(struct gxp_dev *gxp)
+{
+ unsigned int core;
+
+ /* all resources are accessed via PA if there's no iommu */
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ gxp->mbx[core].daddr = gxp->mbx[core].paddr;
+ gxp->fwbufs[core].daddr = gxp->fwbufs[core].paddr;
+ }
+ gxp->regs.daddr = gxp->regs.paddr;
+ gxp->coredumpbuf.daddr = gxp->coredumpbuf.paddr;
+
+ return 0;
+}
+
+void gxp_dma_unmap_resources(struct gxp_dev *gxp)
+{
+ /* no mappings to undo */
+}
+
+void *gxp_dma_alloc_coherent(struct gxp_dev *gxp, uint core_list, size_t size,
+ dma_addr_t *dma_handle, gfp_t flag,
+ uint gxp_dma_flags)
+{
+ /* Allocate the buffer from the cache-coherent pool */
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ void *vaddr = (void *)gen_pool_alloc(mgr->pool, size);
+
+ if (!vaddr) {
+ dev_err(gxp->dev, "Unable to allocate coherent buffer\n");
+ return NULL;
+ }
+
+ /*
+ * On SysMMU-less systems, all GXP cores access DRAM directly, so set
+ * the dma_handle to the buffer's physical address.
+ */
+ if (dma_handle) {
+ *dma_handle =
+ gen_pool_virt_to_phys(mgr->pool, (unsigned long)vaddr);
+
+ if (*dma_handle == -1) {
+ dev_err(gxp->dev,
+ "Unable to get dma_addr_t for coherent buffer\n");
+ gen_pool_free(mgr->pool, (unsigned long)vaddr, size);
+ return NULL;
+ }
+ }
+
+ /* `core_list` is unused, since no SysMMU means there's no mappings */
+ return vaddr;
+}
+
+void gxp_dma_free_coherent(struct gxp_dev *gxp, uint core_list, size_t size,
+ void *cpu_addr, dma_addr_t dma_handle)
+{
+ /* No unmapping required since there's no SysMMU */
+
+ /* Clean up the buffer */
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ gen_pool_free(mgr->pool, (unsigned long)cpu_addr, size);
+}
+
+dma_addr_t gxp_dma_map_single(struct gxp_dev *gxp, uint core_list,
+ void *cpu_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ return gxp_dma_map_page(gxp, core_list, virt_to_page(cpu_addr),
+ offset_in_page(cpu_addr), size, direction,
+ attrs, gxp_dma_flags);
+}
+
+void gxp_dma_unmap_single(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs)
+{
+ return gxp_dma_unmap_page(gxp, core_list, dma_addr, size, direction,
+ attrs);
+}
+
+dma_addr_t gxp_dma_map_page(struct gxp_dev *gxp, uint core_list,
+ struct page *page, unsigned long offset,
+ size_t size, enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce;
+ void *page_buf;
+ int ret;
+
+ bounce = kzalloc(sizeof(struct gxp_dma_bounce_buffer), GFP_KERNEL);
+ if (!bounce) {
+ dev_err(gxp->dev,
+ "Failed to allocate tracking struct for mapping page\n");
+ return DMA_MAPPING_ERROR;
+ }
+
+ bounce->offset = offset;
+ bounce->size = size;
+ bounce->page = page;
+
+ bounce->buf = (void *)gen_pool_alloc(mgr->pool, size);
+ if (!bounce->buf) {
+ dev_err(gxp->dev,
+ "Failed to allocate bounce buffer for mapping page\n");
+ goto pool_alloc_error;
+ }
+
+ bounce->dma_handle =
+ gen_pool_virt_to_phys(mgr->pool, (unsigned long)bounce->buf);
+ if (bounce->dma_handle == -1) {
+ dev_err(gxp->dev, "Unable to get dma_addr_t for mapped page\n");
+ goto error;
+ }
+
+ page_buf = kmap(page);
+ if (!page_buf) {
+ dev_err(gxp->dev,
+ "Failed to map page for copying to bounce buffer\n");
+ goto error;
+ }
+ memcpy(bounce->buf, page_buf + offset, size);
+ kunmap(page);
+
+ ret = bounce_buffer_put(mgr, bounce);
+ if (ret) {
+ dev_err(gxp->dev,
+ "Unable to put bounce buffer!\n");
+ goto error;
+ }
+
+ return bounce->dma_handle;
+
+error:
+ gen_pool_free(mgr->pool, (unsigned long)bounce->buf, bounce->size);
+pool_alloc_error:
+ kfree(bounce);
+ return DMA_MAPPING_ERROR;
+}
+
+void gxp_dma_unmap_page(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction, unsigned long attrs)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce =
+ bounce_buffer_get(mgr, dma_addr);
+ void *page_buf = NULL;
+
+ if (!bounce || !bounce->page) {
+ dev_err(gxp->dev, "No page to unmap for IOVA %pad\n",
+ &dma_addr);
+ return;
+ }
+
+ bounce_buffer_remove(mgr, bounce);
+
+ page_buf = kmap(bounce->page);
+ if (!page_buf) {
+ dev_warn(
+ gxp->dev,
+ "Failed to map page for copying from bounce buffer on unmap\n");
+ } else {
+ memcpy(page_buf + bounce->offset, bounce->buf, bounce->size);
+ }
+ kunmap(bounce->page);
+
+ gen_pool_free(mgr->pool, (unsigned long)bounce->buf, bounce->size);
+ kfree(bounce);
+}
+
+dma_addr_t gxp_dma_map_resource(struct gxp_dev *gxp, uint core_list,
+ phys_addr_t phys_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ dev_warn(gxp->dev, "%s: not yet supported!\n", __func__);
+ return 0;
+}
+
+void gxp_dma_unmap_resource(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs)
+{
+ dev_warn(gxp->dev, "%s: not yet supported!\n", __func__);
+}
+
+int gxp_dma_map_sg(struct gxp_dev *gxp, uint core_list, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce;
+ struct scatterlist *s;
+ size_t size_so_far = 0;
+ int i;
+
+ bounce = kzalloc(sizeof(struct gxp_dma_bounce_buffer), GFP_KERNEL);
+ if (!bounce) {
+ dev_err(gxp->dev,
+ "Failed to allocate tracking struct for mapping sg\n");
+ return 0;
+ }
+
+ for_each_sg(sg, s, nents, i)
+ bounce->size += s->length;
+
+ bounce->buf = (void *)gen_pool_alloc(mgr->pool, bounce->size);
+ if (!bounce->buf) {
+ dev_err(gxp->dev,
+ "Failed to allocate bounce buffer for mapping sg\n");
+ goto pool_alloc_error;
+ }
+
+ sg_copy_to_buffer(sg, nents, bounce->buf, bounce->size);
+
+ for_each_sg(sg, s, nents, i) {
+ s->dma_length = s->length;
+ s->dma_address = gen_pool_virt_to_phys(
+ mgr->pool, (unsigned long)bounce->buf) + size_so_far;
+ size_so_far += s->length;
+
+ if (s->dma_address == -1) {
+ dev_err(gxp->dev,
+ "Failed to get dma_addr_t while mapping sg\n");
+ goto error;
+ }
+ }
+
+ bounce->dma_handle = sg->dma_address;
+ /* SGs use the SG's internal page and offset values */
+ bounce->page = NULL;
+ bounce->offset = 0;
+
+ if (bounce_buffer_put(mgr, bounce)) {
+ dev_err(gxp->dev, "Unable to put bounce buffer for sg!\n");
+ goto error;
+ }
+
+ return nents;
+
+error:
+ /* TODO is this necessary? */
+ for_each_sg(sg, s, nents, i) {
+ s->dma_length = 0;
+ s->dma_address = 0;
+ }
+
+ gen_pool_free(mgr->pool, (unsigned long) bounce->buf, bounce->size);
+
+pool_alloc_error:
+ kfree(bounce);
+
+ return 0;
+}
+
+void gxp_dma_unmap_sg(struct gxp_dev *gxp, uint core_list,
+ struct scatterlist *sg, int nents,
+ enum dma_data_direction direction, unsigned long attrs)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce =
+ bounce_buffer_get(mgr, sg->dma_address);
+ struct scatterlist *s;
+ int i;
+
+ if (!bounce || bounce->page) {
+ dev_err(gxp->dev, "No sg to unmap for IOVA %pad\n",
+ &sg->dma_address);
+ return;
+ }
+
+ if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC))
+ gxp_dma_sync_sg_for_cpu(gxp, sg, nents, direction);
+
+ bounce_buffer_remove(mgr, bounce);
+
+ /* TODO is this necessary? */
+ for_each_sg(sg, s, nents, i) {
+ s->dma_length = 0;
+ s->dma_address = 0;
+ }
+
+ gen_pool_free(mgr->pool, (unsigned long)bounce->buf, bounce->size);
+ kfree(bounce);
+}
+
+void gxp_dma_sync_single_for_cpu(struct gxp_dev *gxp, dma_addr_t dma_handle,
+ size_t size, enum dma_data_direction direction)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce =
+ bounce_buffer_get(mgr, dma_handle);
+ void *page_buf = NULL;
+ unsigned long addr_offset;
+
+ if (!bounce || !bounce->page) {
+ dev_err(gxp->dev, "No single mapping to sync for IOVA %pad\n",
+ &dma_handle);
+ return;
+ }
+
+ addr_offset = dma_handle - bounce->dma_handle;
+
+ /* Copy the contents of the bounce buffer back to the mapped page */
+ page_buf = kmap(bounce->page);
+ if (!page_buf) {
+ dev_warn(gxp->dev,
+ "Failed to map page for syncing from bounce buffer\n");
+ return;
+ }
+ memcpy(page_buf + bounce->offset + addr_offset,
+ bounce->buf + addr_offset, bounce->size);
+ kunmap(bounce->page);
+}
+
+void gxp_dma_sync_single_for_device(struct gxp_dev *gxp, dma_addr_t dma_handle,
+ size_t size,
+ enum dma_data_direction direction)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce =
+ bounce_buffer_get(mgr, dma_handle);
+ void *page_buf = NULL;
+ unsigned long addr_offset;
+
+ if (!bounce || !bounce->page) {
+ dev_err(gxp->dev, "No single mapping to sync for IOVA %pad\n",
+ &dma_handle);
+ return;
+ }
+
+ addr_offset = dma_handle - bounce->dma_handle;
+
+ /* Copy the latest contents of the mapped page to the bounce buffer*/
+ page_buf = kmap(bounce->page);
+ if (!page_buf) {
+ dev_warn(gxp->dev,
+ "Failed to map page for syncing to bounce buffer\n");
+ return;
+ }
+ memcpy(bounce->buf + addr_offset,
+ page_buf + bounce->offset + addr_offset, bounce->size);
+ kunmap(bounce->page);
+}
+
+void gxp_dma_sync_sg_for_cpu(struct gxp_dev *gxp, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce =
+ bounce_buffer_get(mgr, sg->dma_address);
+ void *page_buf;
+ struct scatterlist *s;
+ unsigned int i;
+
+ if (!bounce || bounce->page) {
+ dev_err(gxp->dev, "No mapping to sync for sg\n");
+ return;
+ }
+
+ for_each_sg(sg, s, nents, i) {
+ page_buf = kmap(sg_page(s));
+ if (!page_buf) {
+ dev_warn(gxp->dev, "Failed to map page for sg sync\n");
+ continue;
+ }
+ memcpy(page_buf + s->offset,
+ bounce->buf + (s->dma_address - bounce->dma_handle),
+ s->dma_length);
+ kunmap(sg_page(s));
+ }
+}
+
+void gxp_dma_sync_sg_for_device(struct gxp_dev *gxp, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction)
+{
+ struct gxp_dma_rmem_manager *mgr = container_of(
+ gxp->dma_mgr, struct gxp_dma_rmem_manager, dma_mgr);
+ struct gxp_dma_bounce_buffer *bounce =
+ bounce_buffer_get(mgr, sg->dma_address);
+ void *page_buf;
+ struct scatterlist *s;
+ unsigned int i;
+
+ if (!bounce || bounce->page) {
+ dev_err(gxp->dev, "No mapping to sync for sg\n");
+ return;
+ }
+
+ for_each_sg(sg, s, nents, i) {
+ page_buf = kmap(sg_page(s));
+ if (!page_buf) {
+ dev_warn(gxp->dev, "Failed to map page for sg sync\n");
+ continue;
+ }
+ memcpy(bounce->buf + (s->dma_address - bounce->dma_handle),
+ page_buf + s->offset, s->dma_length);
+ kunmap(sg_page(s));
+ }
+}
diff --git a/gxp-dma.h b/gxp-dma.h
new file mode 100644
index 0000000..37692af
--- /dev/null
+++ b/gxp-dma.h
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP DMA interface.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_DMA_H__
+#define __GXP_DMA_H__
+
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
+#include <linux/types.h>
+
+#include "gxp-internal.h"
+
+struct gxp_dma_manager {
+ struct rb_root mapping_tree;
+};
+
+/*
+ * Error value to be returned in place of a dma_addr_t when a mapping fails.
+ *
+ * On newer kernels, this is defined in <linux/dma-mapping.h>. Redefined here
+ * for older kernels, so clients can check for this value without worrying
+ * which kernel version they're compiled for.
+ */
+#ifndef DMA_MAPPING_ERROR
+#define DMA_MAPPING_ERROR (~(dma_addr_t)0)
+#endif
+
+/**
+ * gxp_dma_init() - Initialize the GXP DMA subsystem
+ * @gxp: The GXP device to initialize DMA for
+ *
+ * Return:
+ * * 0 - DMA initialized successfully
+ * * -EIO - Failed to initialize underlying IOMMU hardware
+ * * -ENODEV - The necessary hardware or device tree entries are missing
+ * * -ENOMEM - Insufficient memory is available to initialize the interface
+ */
+int gxp_dma_init(struct gxp_dev *gxp);
+
+/**
+ * gxp_dma_exit() - Tear down the GXP DMA subsystem and release hardware
+ * @gxp: The GXP device to tear down DMA for
+ */
+void gxp_dma_exit(struct gxp_dev *gxp);
+
+/**
+ * gxp_dma_map_resources() - Map the various buffers/registers with fixed IOVAs
+ * @gxp: The GXP device to setup the mappings for
+ *
+ * GXP firmware expects several buffers and registers to be mapped to fixed
+ * locations in their IOVA space. This function initializes all those mappings.
+ *
+ * This function must not be called until after all the `vaddr` and `size`
+ * fields of every `struct gxp_mapped_resource` inside of @gxp have been
+ * initialized.
+ *
+ * Return:
+ * * 0 - Mappings created successfully
+ * * -EIO - Failed to create one or more of the mappings
+ */
+int gxp_dma_map_resources(struct gxp_dev *gxp);
+
+/**
+ * gxp_dma_unmap_resources() - Unmap the IOVAs mapped by gxp_dma_map_resources
+ * @gxp: The GXP device that was passed to gxp_dma_map_resources()
+ *
+ * GXP firmware expects several buffers and registers to be mapped to fixed
+ * locations in their IOVA space. This function releases all those mappings.
+ *
+ * This function should be called after gxp_dma_map_resources().
+ */
+void gxp_dma_unmap_resources(struct gxp_dev *gxp);
+
+/**
+ * gxp_dma_alloc_coherent() - Allocate and map a coherent buffer for a GXP core
+ * @gxp: The GXP device to map the allocated buffer for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @size: The size of the buffer to be allocated, in bytes
+ * @dma_handle: Reference to a variable to be set to the allocated IOVA
+ * @flag: The type of memory to allocate (see kmalloc)
+ * @gxp_dma_flags: The type of mapping to create; Currently unused
+ *
+ * Return: Kernel virtual address of the allocated/mapped buffer
+ */
+void *gxp_dma_alloc_coherent(struct gxp_dev *gxp, uint core_list, size_t size,
+ dma_addr_t *dma_handle, gfp_t flag,
+ uint gxp_dma_flags);
+/**
+ * gxp_dma_free_coherent() - Unmap and free a coherent buffer
+ * @gxp: The GXP device the buffer was allocated and mapped for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @size: The size of the buffer, in bytes, passed to `gxp_dma_alloc()`
+ * @cpu_addr: The kernel virtual address returned by `gxp_dma_alloc()`
+ * @dma_handle: The device IOVA, set by `gxp_dma_alloc()`
+ *
+ * If the buffer has been mirror-mapped via `gxp_dma_mirror_map()`, the buffer
+ * will not be freed until all mappings have been unmapped.
+ */
+void gxp_dma_free_coherent(struct gxp_dev *gxp, uint core_list, size_t size,
+ void *cpu_addr, dma_addr_t dma_handle);
+
+/**
+ * gxp_dma_map_single() - Create a mapping for a kernel buffer
+ * @gxp: The GXP device to map the buffer for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @cpu_addr: The kernel virtual address of the buffer to map
+ * @size: The size of the buffer to map, in bytes
+ * @direction: DMA direction
+ * @attrs: The same set of flags used by the base DMA API
+ * @gxp_dma_flags: The type of mapping to create; Currently unused
+ *
+ * Return: The IOVA the buffer was mapped to
+ */
+dma_addr_t gxp_dma_map_single(struct gxp_dev *gxp, uint core_list,
+ void *cpu_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags);
+/**
+ * gxp_dma_unmap_single() - Unmap a kernel buffer
+ * @gxp: The GXP device the buffer was mapped for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @dma_addr: The device IOVA, returned by `gxp_dma_map_single()`
+ * @size: The size of the mapping, which was passed to `gxp_dma_map_single()`
+ * @direction: DMA direction; same as passed to `gxp_dma_map_single()`
+ * @attrs: The same set of flags used by the base DMA API
+ */
+void gxp_dma_unmap_single(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs);
+
+/**
+ * gxp_dma_map_page() - Create a mapping for a physical page of memory
+ * @gxp: The GXP device to map the page for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @page: The `struct page` of the physical page to create a mapping for
+ * @offset: The offset into @page to begin the mapping at
+ * @size: The number of bytes in @page to map
+ * @direction: DMA direction
+ * @attrs: The same set of flags used by the base DMA API
+ * @gxp_dma_flags: The type of mapping to create; Currently unused
+ *
+ * Return: The IOVA the page was mapped to
+ */
+dma_addr_t gxp_dma_map_page(struct gxp_dev *gxp, uint core_list,
+ struct page *page, unsigned long offset,
+ size_t size, enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags);
+/**
+ * gxp_dma_unmap_page() - Unmap a physical page of memory
+ * @gxp: The GXP device the page was mapped for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @dma_addr: The device IOVA, returned by `gxp_dma_map_page()`
+ * @size: The size of the mapping, which was passed to `gxp_dma_map_page()`
+ * @direction: DMA direction; Same as passed to `gxp_dma_map_page()`
+ * @attrs: The same set of flags used by the base DMA API
+ */
+void gxp_dma_unmap_page(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction, unsigned long attrs);
+
+/**
+ * gxp_dma_map_resource() - Create a mapping for an MMIO resource
+ * @gxp: The GXP device to map the resource for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @phys_addr: The physical address of the MMIO resource to map
+ * @size: The size of the MMIO region to map, in bytes
+ * @direction: DMA direction
+ * @attrs: The same set of flags used by the base DMA API
+ * @gxp_dma_flags: The type of mapping to create; Currently unused
+ *
+ * Return: The IOVA the MMIO resource was mapped to
+ */
+dma_addr_t gxp_dma_map_resource(struct gxp_dev *gxp, uint core_list,
+ phys_addr_t phys_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags);
+/**
+ * gxp_dma_unmap_resource() - Unmap an MMIO resource
+ * @gxp: The GXP device the MMIO resource was mapped for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @dma_addr: The device IOVA, returned by `gxp_dma_map_resource()`
+ * @size: The size of the mapping, which was passed to `gxp_dma_map_resource()`
+ * @direction: DMA direction; Same as passed to `gxp_dma_map_resource()`
+ * @attrs: The same set of flags used by the base DMA API
+ */
+void gxp_dma_unmap_resource(struct gxp_dev *gxp, uint core_list,
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction direction,
+ unsigned long attrs);
+
+/**
+ * gxp_dma_map_sg() - Create a mapping for a scatter-gather list
+ * @gxp: The GXP device to map the scatter-gather list for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @sg: The scatter-gather list of the buffer to be mapped
+ * @nents: The number of entries in @sg
+ * @direction: DMA direction
+ * @attrs: The same set of flags used by the base DMA API
+ * @gxp_dma_flags: The type of mapping to create; Currently unused
+ *
+ * Return: The number of scatter-gather entries mapped to
+ */
+int gxp_dma_map_sg(struct gxp_dev *gxp, uint core_list, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction,
+ unsigned long attrs, uint gxp_dma_flags);
+/**
+ * gxp_dma_unmap_sg() - Unmap a scatter-gather list
+ * @gxp: The GXP device the scatter-gather list was mapped for
+ * @core_list: A bitfield enumerating the physical cores the mapping is for
+ * @sg: The scatter-gather list to unmap; The same one passed to
+ * `gxp_dma_map_sg()`
+ * @nents: The number of entries in @sg; Same value passed to `gxp_dma_map_sg()`
+ * @direction: DMA direction; Same as passed to `gxp_dma_map_sg()`
+ * @attrs: The same set of flags used by the base DMA API
+ */
+void gxp_dma_unmap_sg(struct gxp_dev *gxp, uint core_list,
+ struct scatterlist *sg, int nents,
+ enum dma_data_direction direction, unsigned long attrs);
+
+/**
+ * gxp_dma_sync_single_for_cpu() - Sync buffer for reading by the CPU
+ * @gxp: The GXP device the mapping was created for
+ * @dma_handle: The device IOVA, obtained from one of the `gxp_dma_map_*` APIs
+ * @size: The size of the mapped region to sync
+ * @direction: DMA direction
+ */
+void gxp_dma_sync_single_for_cpu(struct gxp_dev *gxp, dma_addr_t dma_handle,
+ size_t size,
+ enum dma_data_direction direction);
+/**
+ * gxp_dma_sync_single_for_device() - Sync buffer for reading by the device
+ * @gxp: The GXP device the mapping was created for
+ * @dma_handle: The device IOVA, obtained from one of the `gxp_dma_map_*` APIs
+ * @size: The size of the mapped region to sync
+ * @direction: DMA direction
+ */
+void gxp_dma_sync_single_for_device(struct gxp_dev *gxp, dma_addr_t dma_handle,
+ size_t size,
+ enum dma_data_direction direction);
+
+/**
+ * gxp_dma_sync_sg_for_cpu() - Sync sg list for reading by the CPU
+ * @gxp: The GXP device the mapping was created for
+ * @sg: The mapped scatter-gather list to be synced
+ * @nents: The number of entries in @sg
+ * @direction: DMA direction
+ */
+void gxp_dma_sync_sg_for_cpu(struct gxp_dev *gxp, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction);
+/**
+ * gxp_dma_sync_sg_for_device() - Sync sg list for reading by the device
+ * @gxp: The GXP device the mapping was created for
+ * @sg: The mapped scatter-gather list to be synced
+ * @nents: The number of entries in @sg
+ * @direction: DMA direction
+ */
+void gxp_dma_sync_sg_for_device(struct gxp_dev *gxp, struct scatterlist *sg,
+ int nents, enum dma_data_direction direction);
+
+#endif /* __GXP_DMA_H__ */
diff --git a/gxp-doorbell.c b/gxp-doorbell.c
new file mode 100644
index 0000000..40e956b
--- /dev/null
+++ b/gxp-doorbell.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP doorbell interface.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+
+#include <linux/bitops.h>
+
+#include "gxp-doorbell.h"
+#include "gxp-internal.h"
+
+#define GXP_DOORBELL_STRIDE (GXP_REG_DOORBELL_1_STATUS \
+ - GXP_REG_DOORBELL_0_STATUS)
+
+void gxp_doorbell_set_listening_core(struct gxp_dev *gxp, u32 doorbell_num,
+ uint core)
+{
+ uint i;
+ u32 val;
+
+ /* Disable DOORBELL_NUM on all cores */
+ for (i = 0; i < GXP_NUM_CORES; i++) {
+ val = gxp_read_32_core(gxp, i, GXP_REG_COMMON_INT_MASK_0);
+ val &= ~BIT(doorbell_num);
+ gxp_write_32_core(gxp, i, GXP_REG_COMMON_INT_MASK_0, val);
+ }
+
+ /* Enable DOORBELL_NUM on requested core */
+ val = gxp_read_32_core(gxp, core, GXP_REG_COMMON_INT_MASK_0);
+ val |= BIT(doorbell_num);
+ gxp_write_32_core(gxp, core, GXP_REG_COMMON_INT_MASK_0, val);
+}
+
+void gxp_doorbell_set(struct gxp_dev *gxp, u32 doorbell_num)
+{
+ uint offset = GXP_REG_DOORBELL_0_SET
+ + (GXP_DOORBELL_STRIDE * doorbell_num);
+
+ gxp_write_32(gxp, offset, GXP_REG_DOORBELLS_SET_WRITEMASK);
+}
+
+void gxp_doorbell_clear(struct gxp_dev *gxp, u32 doorbell_num)
+{
+ uint offset = GXP_REG_DOORBELL_0_CLEAR
+ + (GXP_DOORBELL_STRIDE * doorbell_num);
+
+ gxp_write_32(gxp, offset, GXP_REG_DOORBELLS_CLEAR_WRITEMASK);
+}
+
+u32 gxp_doorbell_status(struct gxp_dev *gxp, u32 doorbell_num)
+{
+ uint offset = GXP_REG_DOORBELL_0_STATUS
+ + (GXP_DOORBELL_STRIDE * doorbell_num);
+
+ return gxp_read_32(gxp, offset);
+}
diff --git a/gxp-doorbell.h b/gxp-doorbell.h
new file mode 100644
index 0000000..c8592fa
--- /dev/null
+++ b/gxp-doorbell.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP doorbell interface.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_DOORBELL_H__
+#define __GXP_DOORBELL_H__
+
+#include "gxp-internal.h"
+
+void gxp_doorbell_set_listening_core(struct gxp_dev *gxp, u32 doorbell_num,
+ uint core);
+void gxp_doorbell_set(struct gxp_dev *gxp, u32 doorbell_num);
+void gxp_doorbell_clear(struct gxp_dev *gxp, u32 doorbell_num);
+u32 gxp_doorbell_status(struct gxp_dev *gxp, u32 doorbell_num);
+
+#endif /* __GXP_DOORBELL_H__ */
diff --git a/gxp-firmware-data.c b/gxp-firmware-data.c
new file mode 100644
index 0000000..2af20e6
--- /dev/null
+++ b/gxp-firmware-data.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP firmware data manager.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/dma-mapping.h>
+#include <linux/genalloc.h>
+
+#include "gxp-firmware-data.h"
+#include "gxp-host-device-structs.h"
+#include "gxp-internal.h"
+#include "gxp-range-alloc.h"
+#include "gxp-tmp.h"
+
+/*
+ * The minimum alignment order (power of 2) of allocations in the firmware data
+ * region.
+ */
+#define FW_DATA_STORAGE_ORDER 3
+
+/* A byte pattern to pre-populate the FW region with */
+#define FW_DATA_DEBUG_PATTERN 0x66
+
+/* IDs for dedicated doorbells used by some system components */
+#define DOORBELL_ID_CORE_WAKEUP 0
+#define DOORBELL_ID_SW_MBX(_core_) (31 - _core_)
+
+/* IDs for dedicated sync barriers used by some system components */
+#define SYNC_BARRIER_ID_UART 1
+#define SYNC_BARRIER_ID_SW_MBX 15
+
+/* Default application parameters */
+#define DEFAULT_APP_ID 1
+#define DEFAULT_APP_USER_MEM_SIZE (120 * 1024)
+#define DEFAULT_APP_USER_MEM_ALIGNMENT 8
+#define DEFAULT_APP_THREAD_COUNT 2
+#define DEFAULT_APP_TCM_PER_BANK (100 * 1024)
+#define DEFAULT_APP_USER_DOORBELL_COUNT 2
+#define DEFAULT_APP_USER_BARRIER_COUNT 2
+
+/* Core-to-core mailbox communication constants */
+#define CORE_TO_CORE_MBX_CMD_COUNT 10
+#define CORE_TO_CORE_MBX_RSP_COUNT 10
+
+/* A block allocator managing and partitioning a memory region for device use */
+struct fw_memory_allocator {
+ struct gen_pool *pool;
+ struct gxp_dev *gxp;
+ void *base_host_addr;
+ uint32_t base_device_addr;
+};
+
+/* A memory region allocated for device use */
+struct fw_memory {
+ void *host_addr;
+ uint32_t device_addr;
+ size_t sz;
+};
+
+/*
+ * Holds information about system-wide HW and memory resources given to the FWs
+ * of GXP devices.
+ */
+struct gxp_fw_data_manager {
+ /* Host-side pointers for book keeping */
+ void *fw_data_virt;
+ struct gxp_system_descriptor *system_desc;
+
+ /* Doorbells allocator and reserved doorbell IDs */
+ struct range_alloc *doorbell_allocator;
+ int cores_wakeup_doorbell;
+#ifdef CONFIG_GXP_USE_SW_MAILBOX
+ int core_sw_mailbox_doorbells[NUM_CORES];
+#endif // CONFIG_GXP_USE_SW_MAILBOX
+ int semaphore_doorbells[NUM_CORES];
+
+ /* Sync barriers allocator and reserved sync barrier IDs */
+ struct range_alloc *sync_barrier_allocator;
+#ifdef CONFIG_GXP_USE_SW_MAILBOX
+ int sw_mailbox_barrier;
+#endif // CONFIG_GXP_USE_SW_MAILBOX
+ int uart_sync_barrier;
+ int timer_regions_barrier;
+ int watchdog_region_barrier;
+ int uart_region_barrier;
+ int doorbell_regions_barrier;
+ int sync_barrier_regions_barrier;
+ int semaphores_regions_barrier;
+
+ /* System-wide device memory resources */
+ struct fw_memory_allocator *allocator;
+ struct fw_memory sys_desc_mem;
+ struct fw_memory wdog_mem;
+ struct fw_memory logging_tracing_mem;
+};
+
+/* A container holding information for a single GXP application. */
+struct app_metadata {
+ struct gxp_fw_data_manager *mgr;
+ uint application_id;
+ uint core_count;
+ uint core_list; /* bitmap of cores allocated to this app */
+
+ /* Per-app doorbell IDs */
+ int user_doorbells_count;
+ int *user_doorbells;
+
+ /* Per-app sync barrier IDs */
+ int user_barriers_count;
+ int *user_barriers;
+
+ /* Per-app memory regions */
+ struct fw_memory user_mem;
+ struct fw_memory doorbells_mem;
+ struct fw_memory sync_barriers_mem;
+ struct fw_memory semaphores_mem;
+ struct fw_memory cores_mem;
+ struct fw_memory core_cmd_queues_mem[NUM_CORES];
+ struct fw_memory core_rsp_queues_mem[NUM_CORES];
+ struct fw_memory app_mem;
+};
+
+static struct fw_memory_allocator *mem_alloc_create(struct gxp_dev *gxp,
+ void *host_base,
+ uint32_t device_base,
+ size_t size)
+{
+ struct fw_memory_allocator *allocator;
+ int ret = 0;
+
+ allocator = kzalloc(sizeof(*allocator), GFP_KERNEL);
+ if (!allocator)
+ return ERR_PTR(-ENOMEM);
+
+ /*
+ * Use a genpool to allocate and free chunks of the virtual address
+ * space reserved for FW data. The genpool doesn't use the passed
+ * addresses internally to access any data, thus it is safe to use it to
+ * manage memory that the host may not be able to access directly.
+ * The allocator also records the host-side address so that the code
+ * here can access and populate data in this region.
+ */
+ allocator->gxp = gxp;
+ allocator->pool = gen_pool_create(FW_DATA_STORAGE_ORDER, /*nid=*/-1);
+ if (!allocator->pool) {
+ dev_err(gxp->dev, "Failed to create memory pool\n");
+ kfree(allocator);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ret = gen_pool_add(allocator->pool, device_base, size, /*nid=*/-1);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to add memory to pool (ret = %d)\n",
+ ret);
+ gen_pool_destroy(allocator->pool);
+ kfree(allocator);
+ return ERR_PTR(ret);
+ }
+ allocator->base_host_addr = host_base;
+ allocator->base_device_addr = device_base;
+
+ return allocator;
+}
+
+static int mem_alloc_allocate(struct fw_memory_allocator *allocator,
+ struct fw_memory *mem, size_t size,
+ uint8_t alignment)
+{
+ struct genpool_data_align data = { .align = alignment };
+ uint32_t dev_addr;
+
+ dev_addr = gen_pool_alloc_algo(allocator->pool, size,
+ gen_pool_first_fit_align, &data);
+ if (!dev_addr)
+ return -ENOMEM;
+
+ mem->host_addr = allocator->base_host_addr +
+ (dev_addr - allocator->base_device_addr);
+ mem->device_addr = dev_addr;
+ mem->sz = size;
+
+ return 0;
+}
+
+static void mem_alloc_free(struct fw_memory_allocator *allocator,
+ struct fw_memory *mem)
+{
+ gen_pool_free(allocator->pool, mem->device_addr, mem->sz);
+}
+
+static void mem_alloc_destroy(struct fw_memory_allocator *allocator)
+{
+ WARN_ON(gen_pool_avail(allocator->pool) !=
+ gen_pool_size(allocator->pool));
+ gen_pool_destroy(allocator->pool);
+ kfree(allocator);
+}
+
+static struct fw_memory init_doorbells(struct app_metadata *app)
+{
+ struct gxp_doorbells_descriptor *db_region;
+ struct fw_memory mem;
+ uint32_t mem_size;
+ uint32_t doorbell_count;
+ int i;
+
+ doorbell_count = app->user_doorbells_count;
+ mem_size = sizeof(*db_region) +
+ doorbell_count * sizeof(db_region->doorbells[0]);
+
+ mem_alloc_allocate(app->mgr->allocator, &mem, mem_size,
+ __alignof__(struct gxp_doorbells_descriptor));
+
+ db_region = mem.host_addr;
+ db_region->application_id = app->application_id;
+ db_region->protection_barrier = app->mgr->doorbell_regions_barrier;
+ db_region->num_items = doorbell_count;
+ for (i = 0; i < doorbell_count; i++) {
+ db_region->doorbells[i].users_count = 0;
+ db_region->doorbells[i].hw_doorbell_idx =
+ app->user_doorbells[i];
+ }
+
+ return mem;
+}
+
+static struct fw_memory init_sync_barriers(struct app_metadata *app)
+{
+ struct gxp_sync_barriers_descriptor *sb_region;
+ struct fw_memory mem;
+ uint32_t mem_size;
+ uint32_t barrier_count;
+ int i;
+
+ barrier_count = app->user_barriers_count;
+ mem_size = sizeof(*sb_region) +
+ barrier_count * sizeof(sb_region->barriers[0]);
+
+ mem_alloc_allocate(app->mgr->allocator, &mem, mem_size,
+ __alignof__(struct gxp_sync_barriers_descriptor));
+
+ sb_region = mem.host_addr;
+ sb_region->application_id = app->application_id;
+ sb_region->protection_barrier = app->mgr->sync_barrier_regions_barrier;
+ sb_region->num_items = barrier_count;
+ for (i = 0; i < barrier_count; i++) {
+ sb_region->barriers[i].users_count = 0;
+ sb_region->barriers[i].hw_barrier_idx = app->user_barriers[i];
+ }
+
+ return mem;
+}
+
+static struct fw_memory init_watchdog(struct gxp_fw_data_manager *mgr)
+{
+ struct gxp_watchdog_descriptor *wd_region;
+ struct fw_memory mem;
+
+ mem_alloc_allocate(mgr->allocator, &mem, sizeof(*wd_region),
+ __alignof__(struct gxp_watchdog_descriptor));
+
+ wd_region = mem.host_addr;
+ wd_region->protection_barrier = mgr->watchdog_region_barrier;
+ wd_region->target_value = 0;
+ wd_region->participating_cores = 0;
+ wd_region->responded_cores = 0;
+ wd_region->tripped = 0;
+
+ return mem;
+}
+
+static struct fw_memory init_logging_tracing(struct gxp_fw_data_manager *mgr)
+{
+ struct gxp_logging_tracing_descriptor *lt_region;
+ struct fw_memory mem;
+
+ mem_alloc_allocate(mgr->allocator, &mem, sizeof(*lt_region),
+ __alignof__(struct gxp_logging_tracing_descriptor));
+
+ lt_region = mem.host_addr;
+
+ /*
+ * Logging and tracing is disabled for now.
+ * Subsuequent calls to the FW data module can be used to populate or
+ * depopulate the descriptor pointers on demand.
+ */
+ memset(lt_region, 0x00, sizeof(*lt_region));
+
+ return mem;
+}
+
+static struct fw_memory init_app_user_memory(struct app_metadata *app,
+ int memory_size)
+{
+ struct fw_memory mem;
+
+ mem_alloc_allocate(app->mgr->allocator, &mem, memory_size,
+ DEFAULT_APP_USER_MEM_ALIGNMENT);
+
+ return mem;
+}
+
+static struct fw_memory init_app_semaphores(struct app_metadata *app)
+{
+ struct gxp_semaphores_descriptor *sm_region;
+ struct fw_memory mem;
+ uint32_t mem_size;
+ uint32_t semaphore_count;
+ int core;
+ int i;
+
+ semaphore_count = NUM_SYSTEM_SEMAPHORES;
+ mem_size = sizeof(*sm_region) +
+ semaphore_count * sizeof(sm_region->semaphores[0]);
+
+ mem_alloc_allocate(app->mgr->allocator, &mem, mem_size,
+ __alignof__(struct gxp_semaphores_descriptor));
+
+ sm_region = mem.host_addr;
+ sm_region->application_id = app->application_id;
+ sm_region->protection_barrier = app->mgr->semaphores_regions_barrier;
+
+ core = 0;
+ for (i = 0; i < NUM_CORES; i++) {
+ if (app->core_list & BIT(i))
+ sm_region->wakeup_doorbells[core++] =
+ app->mgr->semaphore_doorbells[i];
+ sm_region->woken_pending_semaphores[i] = 0;
+ }
+
+ sm_region->num_items = semaphore_count;
+ for (i = 0; i < semaphore_count; i++) {
+ sm_region->semaphores[i].users_count = 0;
+ sm_region->semaphores[i].count = 0;
+ sm_region->semaphores[i].waiters = 0;
+ }
+
+ return mem;
+}
+
+static struct fw_memory init_app_cores(struct app_metadata *app)
+{
+ struct gxp_cores_descriptor *cd_region;
+ struct gxp_queue_info *q_info;
+ struct fw_memory mem;
+ uint32_t mem_size;
+ int semaphore_id;
+ int core_count;
+ int i;
+ const int cmd_queue_items = CORE_TO_CORE_MBX_CMD_COUNT;
+ const int resp_queue_items = CORE_TO_CORE_MBX_RSP_COUNT;
+
+ /* Core info structures. */
+ core_count = app->core_count;
+ mem_size =
+ sizeof(*cd_region) + core_count * sizeof(cd_region->cores[0]);
+
+ mem_alloc_allocate(app->mgr->allocator, &mem, mem_size,
+ __alignof__(struct gxp_cores_descriptor));
+
+ cd_region = mem.host_addr;
+ cd_region->num_items = core_count;
+
+ /* Command and response queues. */
+ semaphore_id = 0;
+ for (i = 0; i < core_count; i++) {
+ /* Allocate per-core command queue storage. */
+ mem_size = cmd_queue_items *
+ sizeof(struct gxp_core_to_core_command);
+ mem_alloc_allocate(
+ app->mgr->allocator, &app->core_cmd_queues_mem[i],
+ mem_size, __alignof__(struct gxp_core_to_core_command));
+
+ /* Update per-core command queue info. */
+ q_info = &cd_region->cores[i].incoming_commands_queue;
+ q_info->header.storage =
+ app->core_cmd_queues_mem[i].device_addr;
+ q_info->header.head_idx = 0;
+ q_info->header.tail_idx = 0;
+ q_info->header.element_size =
+ sizeof(struct gxp_core_to_core_command);
+ q_info->header.elements_count = cmd_queue_items;
+ q_info->access_sem_id = semaphore_id++;
+ q_info->posted_slots_sem_id = semaphore_id++;
+ q_info->free_slots_sem_id = semaphore_id++;
+
+ /* Allocate per-core response queue storage. */
+ mem_size = resp_queue_items *
+ sizeof(struct gxp_core_to_core_response);
+ mem_alloc_allocate(
+ app->mgr->allocator, &app->core_rsp_queues_mem[i],
+ mem_size,
+ __alignof__(struct gxp_core_to_core_response));
+
+ /* Update per-core response queue info. */
+ q_info = &cd_region->cores[i].incoming_responses_queue;
+ q_info->header.storage =
+ app->core_rsp_queues_mem[i].device_addr;
+ q_info->header.head_idx = 0;
+ q_info->header.tail_idx = 0;
+ q_info->header.element_size =
+ sizeof(struct gxp_core_to_core_response);
+ q_info->header.elements_count = resp_queue_items;
+ q_info->access_sem_id = semaphore_id++;
+ q_info->posted_slots_sem_id = semaphore_id++;
+ q_info->free_slots_sem_id = semaphore_id++;
+ }
+
+ return mem;
+}
+
+static struct fw_memory init_application(struct app_metadata *app)
+{
+ struct gxp_application_descriptor *app_region;
+ struct fw_memory mem;
+ const int user_mem_size = DEFAULT_APP_USER_MEM_SIZE;
+
+ /* App's system memory. */
+ app->user_mem = init_app_user_memory(app, user_mem_size);
+
+ /* App's doorbells region. */
+ app->doorbells_mem = init_doorbells(app);
+
+ /* App's sync barriers region. */
+ app->sync_barriers_mem = init_sync_barriers(app);
+
+ /* App's semaphores region. */
+ app->semaphores_mem = init_app_semaphores(app);
+
+ /* App's cores info and core-to-core queues. */
+ app->cores_mem = init_app_cores(app);
+
+ /* App's descriptor. */
+ mem_alloc_allocate(app->mgr->allocator, &mem, sizeof(*app_region),
+ __alignof__(struct gxp_application_descriptor));
+ app_region = mem.host_addr;
+ app_region->application_id = app->application_id;
+ app_region->core_count = app->core_count;
+ app_region->cores_mask = app->core_list;
+ app_region->threads_count = DEFAULT_APP_THREAD_COUNT;
+ app_region->tcm_memory_per_bank = DEFAULT_APP_TCM_PER_BANK;
+ app_region->system_memory_size = user_mem_size;
+ app_region->system_memory_addr = app->user_mem.device_addr;
+ app_region->doorbells_dev_addr = app->doorbells_mem.device_addr;
+ app_region->sync_barriers_dev_addr = app->sync_barriers_mem.device_addr;
+ app_region->semaphores_dev_addr = app->semaphores_mem.device_addr;
+ app_region->cores_info_dev_addr = app->cores_mem.device_addr;
+
+ return mem;
+}
+
+int gxp_fw_data_init(struct gxp_dev *gxp)
+{
+ struct gxp_fw_data_manager *mgr;
+ int res;
+ int i;
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+ gxp->data_mgr = mgr;
+
+ /*
+ * TODO (b/200169232) Using memremap until devm_memremap is added to
+ * the GKI ABI
+ */
+ mgr->fw_data_virt = memremap(gxp->fwdatabuf.paddr, gxp->fwdatabuf.size,
+ MEMREMAP_WC);
+
+ if (IS_ERR_OR_NULL(mgr->fw_data_virt)) {
+ dev_err(gxp->dev, "Failed to map fw data region\n");
+ res = -ENODEV;
+ goto err;
+ }
+
+ /* Instantiate the doorbells allocator with all doorbells */
+ mgr->doorbell_allocator =
+ range_alloc_create(/*start=*/0, DOORBELL_COUNT);
+ if (IS_ERR(mgr->doorbell_allocator)) {
+ dev_err(gxp->dev, "Failed to create doorbells allocator\n");
+ res = PTR_ERR(mgr->doorbell_allocator);
+ mgr->doorbell_allocator = NULL;
+ goto err;
+ }
+
+ /* Instantiate the sync barriers allocator with all sync barriers */
+ mgr->sync_barrier_allocator =
+ range_alloc_create(/*start=*/0, SYNC_BARRIER_COUNT);
+ if (IS_ERR(mgr->sync_barrier_allocator)) {
+ dev_err(gxp->dev, "Failed to create sync barriers allocator\n");
+ res = PTR_ERR(mgr->sync_barrier_allocator);
+ mgr->sync_barrier_allocator = NULL;
+ goto err;
+ }
+
+ /* Allocate doorbells */
+
+ /* Pinned: Cores wakeup doorbell */
+ mgr->cores_wakeup_doorbell = DOORBELL_ID_CORE_WAKEUP;
+ res = range_alloc_get(mgr->doorbell_allocator,
+ mgr->cores_wakeup_doorbell);
+ if (res)
+ goto err;
+
+#ifdef CONFIG_GXP_USE_SW_MAILBOX
+ /* Pinned: SW mailbox doorbells */
+ for (i = 0; i < NUM_CORES; i++) {
+ mgr->core_sw_mailbox_doorbells[i] = DOORBELL_ID_SW_MBX(i);
+ range_alloc_get(mgr->doorbell_allocator,
+ mgr->core_sw_mailbox_doorbells[i]);
+ }
+#endif // CONFIG_GXP_USE_SW_MAILBOX
+
+ /* Semaphores operation doorbells */
+ for (i = 0; i < NUM_CORES; i++) {
+ range_alloc_get_any(mgr->doorbell_allocator,
+ &mgr->semaphore_doorbells[i]);
+ }
+
+ /* Allocate sync barriers */
+
+ /* Pinned: UART sync barrier */
+ mgr->uart_sync_barrier = SYNC_BARRIER_ID_UART;
+ mgr->uart_region_barrier = SYNC_BARRIER_ID_UART;
+ res = range_alloc_get(mgr->sync_barrier_allocator,
+ mgr->uart_sync_barrier);
+ if (res)
+ goto err;
+
+#ifdef CONFIG_GXP_USE_SW_MAILBOX
+ /* Pinned: SW MBX sync barrier */
+ mgr->sw_mailbox_barrier = SYNC_BARRIER_ID_SW_MBX;
+ res = range_alloc_get(mgr->sync_barrier_allocator,
+ mgr->sw_mailbox_barrier);
+ if (res)
+ goto err;
+#endif // CONFIG_GXP_USE_SW_MAILBOX
+
+ /* Doorbell regions for all apps */
+ res = range_alloc_get_any(mgr->sync_barrier_allocator,
+ &mgr->doorbell_regions_barrier);
+ if (res)
+ goto err;
+
+ /* Sync barrier regions for all apps */
+ res = range_alloc_get_any(mgr->sync_barrier_allocator,
+ &mgr->sync_barrier_regions_barrier);
+ if (res)
+ goto err;
+
+ /* Timer regions for all apps */
+ res = range_alloc_get_any(mgr->sync_barrier_allocator,
+ &mgr->timer_regions_barrier);
+ if (res)
+ goto err;
+
+ /* Watchdog regions for all apps */
+ res = range_alloc_get_any(mgr->sync_barrier_allocator,
+ &mgr->watchdog_region_barrier);
+ if (res)
+ goto err;
+
+ /* Semaphore regions for all apps */
+ // TODO: make this per-app to improve performance?
+ res = range_alloc_get_any(mgr->sync_barrier_allocator,
+ &mgr->semaphores_regions_barrier);
+ if (res)
+ goto err;
+
+ /* Shared firmware data memory region */
+ mgr->allocator = mem_alloc_create(gxp, mgr->fw_data_virt,
+ gxp->fwdatabuf.daddr,
+ gxp->fwdatabuf.size);
+ if (IS_ERR(mgr->allocator)) {
+ dev_err(gxp->dev,
+ "Failed to create the FW data memory allocator\n");
+ res = PTR_ERR(mgr->allocator);
+ mgr->allocator = NULL;
+ goto err;
+ }
+
+ /* Populate the region with a pre-defined pattern. */
+ memset(mgr->fw_data_virt, FW_DATA_DEBUG_PATTERN, gxp->fwdatabuf.size);
+
+ /* Allocate the root system descriptor from the region */
+ mem_alloc_allocate(mgr->allocator, &mgr->sys_desc_mem,
+ sizeof(struct gxp_system_descriptor),
+ __alignof__(struct gxp_system_descriptor));
+ mgr->system_desc = mgr->sys_desc_mem.host_addr;
+
+ /* Allocate the watchdog descriptor from the region */
+ mgr->wdog_mem = init_watchdog(mgr);
+ mgr->system_desc->watchdog_dev_addr = mgr->wdog_mem.device_addr;
+
+ /* Allocate the descriptor for device-side logging and tracing */
+ mgr->logging_tracing_mem = init_logging_tracing(mgr);
+ mgr->system_desc->logging_tracing_dev_addr =
+ mgr->logging_tracing_mem.device_addr;
+
+ return res;
+
+err:
+ range_alloc_destroy(mgr->sync_barrier_allocator);
+ range_alloc_destroy(mgr->doorbell_allocator);
+ devm_kfree(gxp->dev, mgr);
+ return res;
+}
+
+void *gxp_fw_data_create_app(struct gxp_dev *gxp, uint core_list)
+{
+ struct gxp_fw_data_manager *mgr = gxp->data_mgr;
+ struct app_metadata *app;
+ int i;
+
+ app = kzalloc(sizeof(struct app_metadata), GFP_KERNEL);
+ if (!app)
+ return ERR_PTR(-ENOMEM);
+
+ /* Create resource and memory allocations for new app */
+ app->mgr = mgr;
+ app->application_id = DEFAULT_APP_ID;
+ app->core_count = hweight_long(core_list);
+ app->core_list = core_list;
+
+ /* User doorbells */
+ app->user_doorbells_count = DEFAULT_APP_USER_DOORBELL_COUNT;
+ app->user_doorbells =
+ kcalloc(app->user_doorbells_count, sizeof(int), GFP_KERNEL);
+ for (i = 0; i < app->user_doorbells_count; i++) {
+ range_alloc_get_any(mgr->doorbell_allocator,
+ &app->user_doorbells[i]);
+ }
+
+ /* User sync barrier */
+ app->user_barriers_count = DEFAULT_APP_USER_BARRIER_COUNT;
+ app->user_barriers =
+ kcalloc(app->user_barriers_count, sizeof(int), GFP_KERNEL);
+ for (i = 0; i < app->user_barriers_count; i++) {
+ range_alloc_get_any(mgr->sync_barrier_allocator,
+ &app->user_barriers[i]);
+ }
+
+ /* Application region. */
+ app->app_mem = init_application(app);
+ for (i = 0; i < NUM_CORES; i++) {
+ if (core_list & BIT(i)) {
+ mgr->system_desc->app_descriptor_dev_addr[i] =
+ app->app_mem.device_addr;
+ }
+ }
+
+ return app;
+}
+
+void gxp_fw_data_destroy_app(struct gxp_dev *gxp, void *application)
+{
+ struct app_metadata *app = application;
+ struct gxp_fw_data_manager *mgr = gxp->data_mgr;
+ int i;
+
+ for (i = 0; i < app->user_doorbells_count; i++)
+ range_alloc_put(mgr->doorbell_allocator,
+ app->user_doorbells[i]);
+ kfree(app->user_doorbells);
+
+ for (i = 0; i < app->user_barriers_count; i++)
+ range_alloc_put(mgr->sync_barrier_allocator,
+ app->user_barriers[i]);
+ kfree(app->user_barriers);
+
+ mem_alloc_free(mgr->allocator, &app->user_mem);
+ mem_alloc_free(mgr->allocator, &app->doorbells_mem);
+ mem_alloc_free(mgr->allocator, &app->sync_barriers_mem);
+ mem_alloc_free(mgr->allocator, &app->semaphores_mem);
+ mem_alloc_free(mgr->allocator, &app->cores_mem);
+ for (i = 0; i < app->core_count; i++) {
+ mem_alloc_free(mgr->allocator, &app->core_cmd_queues_mem[i]);
+ mem_alloc_free(mgr->allocator, &app->core_rsp_queues_mem[i]);
+ }
+ mem_alloc_free(mgr->allocator, &app->app_mem);
+
+ kfree(app);
+}
+
+void gxp_fw_data_destroy(struct gxp_dev *gxp)
+{
+ struct gxp_fw_data_manager *mgr = gxp->data_mgr;
+
+ mem_alloc_free(mgr->allocator, &mgr->logging_tracing_mem);
+ mem_alloc_free(mgr->allocator, &mgr->wdog_mem);
+ mem_alloc_free(mgr->allocator, &mgr->sys_desc_mem);
+ mem_alloc_destroy(mgr->allocator);
+
+ range_alloc_destroy(mgr->sync_barrier_allocator);
+ range_alloc_destroy(mgr->doorbell_allocator);
+
+ /* TODO (b/200169232) Remove this once we're using devm_memremap */
+ if (mgr->fw_data_virt) {
+ memunmap(mgr->fw_data_virt);
+ mgr->fw_data_virt = NULL;
+ }
+
+ if (gxp->data_mgr) {
+ devm_kfree(gxp->dev, gxp->data_mgr);
+ gxp->data_mgr = NULL;
+ }
+}
diff --git a/gxp-firmware-data.h b/gxp-firmware-data.h
new file mode 100644
index 0000000..9894971
--- /dev/null
+++ b/gxp-firmware-data.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP firmware data manager.
+ * A sub-module responsible for managing the resources/data regions shared
+ * between the GXP driver and FW.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_FIRMWARE_DATA_H__
+#define __GXP_FIRMWARE_DATA_H__
+
+#include "gxp-internal.h"
+
+/**
+ * gxp_fw_data_init() - Initializes the FW data manager submodule.
+ * @gxp: The parent GXP device
+ *
+ * Return:
+ * 0 - Successfully initialized submodule
+ * -ENOMEM - Insufficient memory to create the submodule
+ * -ENODEV - Failed to locate the shared driver-device region
+ * -Other - Error codes propagated from internal functions.
+ */
+int gxp_fw_data_init(struct gxp_dev *gxp);
+
+/**
+ * gxp_fw_data_create_app() - Allocates HW and memory resources needed to create
+ * a GXP device application (1:1 with a GXP driver
+ * virtual device) used by the specified physical
+ * cores.
+ * @gxp: The parent GXP device
+ * @core_list: A bitmap of the physical cores used in this application
+ *
+ * Return:
+ * ptr - A pointer of the newly created application handle, an error pointer
+ * (PTR_ERR) otherwise.
+ * -ENOMEM - Insufficient memory to create the application
+ */
+void *gxp_fw_data_create_app(struct gxp_dev *gxp, uint core_list);
+
+/**
+ * gxp_fw_data_destroy_app() - Deallocates the HW and memory resources used by
+ * the specified application.
+ * @gxp: The parent GXP device
+ * @application: The handle to the application to deallocate
+ */
+void gxp_fw_data_destroy_app(struct gxp_dev *gxp, void *application);
+
+/**
+ * gxp_fw_data_destroy() - Destroys the FW data manager submodule and free all
+ * its resources.
+ * @gxp: The parent GXP device
+ */
+void gxp_fw_data_destroy(struct gxp_dev *gxp);
+
+#endif /* __GXP_FIRMWARE_DATA_H__ */
diff --git a/gxp-firmware.c b/gxp-firmware.c
new file mode 100644
index 0000000..0c32749
--- /dev/null
+++ b/gxp-firmware.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP firmware loader.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/elf.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "gxp-bpm.h"
+#include "gxp-debug-dump.h"
+#include "gxp-doorbell.h"
+#include "gxp-firmware.h"
+#include "gxp-internal.h"
+#include "gxp-lpm.h"
+#include "gxp-mailbox.h"
+#include "gxp-tmp.h"
+
+/* TODO (b/176984045): Clean up gxp-firmware.c */
+
+/* Files need to be copied to /lib/firmware */
+#define Q7_ELF_FILE0 "gxp_fw_core0"
+#define Q7_ELF_FILE1 "gxp_fw_core1"
+#define Q7_ELF_FILE2 "gxp_fw_core2"
+#define Q7_ELF_FILE3 "gxp_fw_core3"
+
+static const struct firmware *fw[GXP_NUM_CORES];
+static void __iomem *aurora_base;
+
+static char *fw_elf[] = {Q7_ELF_FILE0, Q7_ELF_FILE1, Q7_ELF_FILE2,
+ Q7_ELF_FILE3};
+
+static int elf_load_segments(struct gxp_dev *gxp, const struct firmware *fw,
+ u8 core)
+{
+ struct elf32_hdr *ehdr;
+ struct elf32_phdr *phdr;
+ int i, ret = 0;
+ const u8 *elf_data = fw->data;
+
+ ehdr = (struct elf32_hdr *)elf_data;
+ phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff);
+
+ if ((ehdr->e_ident[EI_MAG0] != ELFMAG0) ||
+ (ehdr->e_ident[EI_MAG1] != ELFMAG1) ||
+ (ehdr->e_ident[EI_MAG2] != ELFMAG2) ||
+ (ehdr->e_ident[EI_MAG3] != ELFMAG3)) {
+ dev_err(gxp->dev, "Cannot load FW! Invalid ELF format.\n");
+ return -EINVAL;
+ }
+
+ /* go through the available ELF segments */
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ u64 da = phdr->p_paddr;
+ u32 memsz = phdr->p_memsz;
+ u32 filesz = phdr->p_filesz;
+ u32 offset = phdr->p_offset;
+ void *ptr;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ if (!phdr->p_flags)
+ continue;
+
+ if (!memsz)
+ continue;
+
+ if (!((da >= (u32)gxp->fwbufs[core].daddr) &&
+ ((da + memsz) <= ((u32)gxp->fwbufs[core].daddr +
+ (u32)gxp->fwbufs[core].size - 1)))) {
+ /*
+ * Some BSS data may be referenced from TCM, and can be
+ * skipped while loading
+ */
+ dev_err(gxp->dev, "Segment out of bounds: da 0x%llx mem 0x%x. Skipping...\n",
+ da, memsz);
+ continue;
+ }
+
+ dev_notice(gxp->dev, "phdr: type %d da 0x%llx memsz 0x%x filesz 0x%x\n",
+ phdr->p_type, da, memsz, filesz);
+
+ if (filesz > memsz) {
+ dev_err(gxp->dev, "Bad phdr filesz 0x%x memsz 0x%x\n",
+ filesz, memsz);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (offset + filesz > fw->size) {
+ dev_err(gxp->dev, "Truncated fw: need 0x%x avail 0x%zx\n",
+ offset + filesz, fw->size);
+ ret = -EINVAL;
+ break;
+ }
+
+ /* grab the kernel address for this device address */
+ ptr = gxp->fwbufs[core].vaddr + (da - gxp->fwbufs[core].daddr);
+ if (!ptr) {
+ dev_err(gxp->dev, "Bad phdr: da 0x%llx mem 0x%x\n",
+ da, memsz);
+ ret = -EINVAL;
+ break;
+ }
+
+ /* put the segment where the remote processor expects it */
+ if (phdr->p_filesz)
+ memcpy_toio(ptr, elf_data + phdr->p_offset, filesz);
+
+ /*
+ * Zero out remaining memory for this segment.
+ */
+ if (memsz > filesz)
+ memset(ptr + filesz, 0, memsz - filesz);
+ }
+
+ return ret;
+}
+
+static int gxp_firmware_load(struct gxp_dev *gxp, uint core)
+{
+ u32 reset_vec, offset;
+ void __iomem *core_scratchpad_base;
+ int ret;
+
+ dev_notice(gxp->dev, "Loading Q7 ELF file %s\n", fw_elf[core]);
+ ret = request_firmware(&fw[core], fw_elf[core], NULL);
+ if (ret < 0) {
+ dev_err(gxp->dev, "Loading ELF failed (ret=%d)\n", ret);
+ return ret;
+ }
+ dev_notice(gxp->dev, "Q7 ELF file loaded\n");
+
+ /*
+ * Currently, the Q7 FW needs to be statically linked to a base
+ * address where it would be loaded in memory. This requires the
+ * address (where the FW is to be loaded in DRAM) to be
+ * pre-defined, and hence not allocate-able dynamically (using
+ * the kernel's memory management system). Therefore, we are
+ * memremapping a static address and loading the FW there, while
+ * also having compiled the FW with this as the base address
+ * (used by the linker).
+ *
+ * FIXME: This should be fixed by compiling the FW in a
+ * re-locateable way so that it is independent of the load
+ * address, and then using the standard kernel APIs
+ * (kmalloc/dma_alloc_coherrent) to allocate memory and load the
+ * FW.
+ */
+ /*
+ * TODO (b/193069216) allocate a dynamic buffer and let
+ * `gxp_dma_map_resources()` map it to the expected paddr
+ */
+ gxp->fwbufs[core].vaddr = memremap(gxp->fwbufs[core].paddr,
+ gxp->fwbufs[core].size, MEMREMAP_WC);
+ if (!(gxp->fwbufs[core].vaddr)) {
+ dev_err(gxp->dev, "FW buf memremap failed\n");
+ return -EINVAL;
+ }
+
+ /* Load firmware to System RAM */
+ ret = elf_load_segments(gxp, fw[core], core);
+ if (ret) {
+ dev_err(gxp->dev, "Unable to load elf file\n");
+ return ret;
+ }
+
+ memset(gxp->fwbufs[core].vaddr + AURORA_SCRATCHPAD_OFF, 0,
+ AURORA_SCRATCHPAD_LEN);
+
+ core_scratchpad_base = gxp->fwbufs[core].vaddr + AURORA_SCRATCHPAD_OFF +
+ CORE_SCRATCHPAD_BASE(core);
+ offset = SCRATCHPAD_MSG_OFFSET(MSG_CORE_ALIVE);
+ writel(0, core_scratchpad_base + offset);
+ offset = SCRATCHPAD_MSG_OFFSET(MSG_TOP_ACCESS_OK);
+ writel(0, core_scratchpad_base + offset);
+
+ /* TODO(b/188970444): Cleanup logging of addresses */
+ dev_notice(gxp->dev,
+ "ELF loaded at virtual: %pK and physical: 0x%llx\n",
+ gxp->fwbufs[core].vaddr, gxp->fwbufs[core].paddr);
+
+ /* Program reset vector */
+ reset_vec = gxp_read_32_core(gxp, core,
+ GXP_REG_ALT_RESET_VECTOR);
+ dev_notice(gxp->dev, "Current Aurora reset vector for core %u: 0x%x\n",
+ core, reset_vec);
+ gxp_write_32_core(gxp, core, GXP_REG_ALT_RESET_VECTOR,
+ gxp->fwbufs[core].daddr);
+ dev_notice(gxp->dev, "New Aurora reset vector for core %u: 0x%llx\n",
+ core, gxp->fwbufs[core].daddr);
+
+ /* Configure bus performance monitors */
+ gxp_bpm_configure(gxp, core, INST_BPM_OFFSET, BPM_EVENT_READ_XFER);
+ gxp_bpm_configure(gxp, core, DATA_BPM_OFFSET, BPM_EVENT_WRITE_XFER);
+
+ return 0;
+}
+
+static int gxp_firmware_handshake(struct gxp_dev *gxp, uint core)
+{
+ u32 offset;
+ u32 psm_status;
+ u32 expected_top_value;
+ void __iomem *core_psm_base, *core_scratchpad_base, *addr;
+ uint state;
+ int ctr;
+
+ /* Raise wakeup doorbell */
+ dev_notice(gxp->dev, "Raising doorbell %d interrupt\n",
+ CORE_WAKEUP_DOORBELL);
+ gxp_doorbell_set_listening_core(gxp, CORE_WAKEUP_DOORBELL, core);
+ gxp_doorbell_set(gxp, CORE_WAKEUP_DOORBELL);
+
+ /* Wait for core to come up */
+ dev_notice(gxp->dev, "Waiting for core %u to power up...\n",
+ core);
+ core_psm_base = ((u8 *)aurora_base) + LPM_BLOCK
+ + CORE_PSM_BASE(core);
+ ctr = 1000;
+ while (ctr) {
+ addr = core_psm_base + PSM_STATUS_OFFSET;
+ psm_status = (u32) readl(addr); /* 0x60041688 */
+ if (psm_status & PSM_STATE_VALID_MASK) {
+ state = psm_status & PSM_CURR_STATE_MASK;
+ if ((state == PSM_STATE_ACTIVE)
+ || (state == PSM_STATE_CLK_GATED))
+ break;
+ }
+ cpu_relax();
+ ctr--;
+ }
+
+ if (!ctr) {
+ dev_notice(gxp->dev, "Failed!\n");
+ return -ETIMEDOUT;
+ }
+ dev_notice(gxp->dev, "Powered up!\n");
+
+ /* Wait for 500ms. Then check if Q7 core is alive */
+ dev_notice(gxp->dev, "Waiting for core %u to respond...\n",
+ core);
+
+ core_scratchpad_base = gxp->fwbufs[core].vaddr + AURORA_SCRATCHPAD_OFF;
+
+ ctr = 500;
+ offset = SCRATCHPAD_MSG_OFFSET(MSG_CORE_ALIVE);
+ while (ctr--) {
+ if (readl(core_scratchpad_base + offset) == Q7_ALIVE_MAGIC)
+ break;
+ msleep(1 * GXP_TIME_DELAY_FACTOR);
+ }
+ /*
+ * Currently, the hello_world FW writes a magic number
+ * (Q7_ALIVE_MAGIC) to offset MSG_CORE_ALIVE in the scratchpad
+ * space as an alive message
+ */
+ if (readl(core_scratchpad_base + offset) != Q7_ALIVE_MAGIC) {
+ dev_err(gxp->dev, "Core %u did not respond!\n", core);
+ return -EIO;
+ } else {
+ dev_notice(gxp->dev, "Core %u is alive!\n", core);
+ }
+
+ /*
+ * Currently, the hello_world FW reads the INT_MASK0 register
+ * (written by the driver) to validate TOP access. The value
+ * read is echoed back by the FW to offset MSG_TOP_ACCESS_OK in
+ * the scratchpad space, which must be compared to the value
+ * written in the INT_MASK0 register by the driver for
+ * confirmation.
+ */
+ /* TODO (b/182528386): Fix handshake for verifying TOP access */
+ offset = SCRATCHPAD_MSG_OFFSET(MSG_TOP_ACCESS_OK);
+ expected_top_value = BIT(0);
+#ifdef CONFIG_GXP_USE_SW_MAILBOX
+ expected_top_value |= BIT(31 - core);
+#endif // CONFIG_GXP_USE_SW_MAILBOX
+ if (readl(core_scratchpad_base + offset) != expected_top_value) {
+ dev_err(gxp->dev, "TOP access from core %u failed!\n", core);
+ return -EIO;
+ } else {
+ dev_notice(gxp->dev, "TOP access from core %u successful!\n",
+ core);
+ }
+
+ /* Stop bus performance monitors */
+ gxp_bpm_stop(gxp, core);
+ dev_notice(gxp->dev, "Core%u Instruction read transactions: 0x%x\n",
+ core, gxp_bpm_read_counter(gxp, core, INST_BPM_OFFSET));
+ dev_notice(gxp->dev, "Core%u Data write transactions: 0x%x\n", core,
+ gxp_bpm_read_counter(gxp, core, DATA_BPM_OFFSET));
+
+ return 0;
+}
+
+static void gxp_firmware_unload(struct gxp_dev *gxp, uint core)
+{
+ if (gxp->fwbufs[core].vaddr) {
+ memunmap(gxp->fwbufs[core].vaddr);
+ gxp->fwbufs[core].vaddr = NULL;
+ }
+
+ if (fw[core]) {
+ release_firmware(fw[core]);
+ fw[core] = NULL;
+ }
+}
+
+int gxp_fw_init(struct gxp_dev *gxp)
+{
+ u32 ver, proc_id;
+ uint core;
+ struct resource r;
+ int ret;
+
+ aurora_base = gxp->regs.vaddr;
+
+ ver = gxp_read_32(gxp, GXP_REG_AURORA_REVISION);
+ dev_notice(gxp->dev, "Aurora version: 0x%x\n", ver);
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ proc_id = gxp_read_32_core(gxp, core, GXP_REG_PROCESSOR_ID);
+ dev_notice(gxp->dev, "Aurora core %u processor ID: 0x%x\n",
+ core, proc_id);
+ }
+
+ ret = gxp_acquire_rmem_resource(gxp, &r, "gxp-fw-region");
+ if (ret) {
+ dev_err(gxp->dev,
+ "Unable to acquire firmware reserved memory\n");
+ return ret;
+ }
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ gxp->fwbufs[core].size =
+ (resource_size(&r) / GXP_NUM_CORES) & PAGE_MASK;
+ gxp->fwbufs[core].paddr =
+ r.start + (core * gxp->fwbufs[core].size);
+ /*
+ * Firmware buffers are not mapped into kernal VA space until
+ * firmware is ready to be loaded.
+ */
+ }
+
+ ret = gxp_acquire_rmem_resource(gxp, &r, "gxp-scratchpad-region");
+ if (ret) {
+ dev_err(gxp->dev,
+ "Unable to acquire shared FW data reserved memory\n");
+ return ret;
+ }
+ gxp->fwdatabuf.size = resource_size(&r);
+ gxp->fwdatabuf.paddr = r.start;
+ /*
+ * Scratchpad region is not mapped until the firmware data is
+ * initialized.
+ */
+
+ gxp->firmware_running = 0;
+ gxp_lpm_init(gxp);
+ return 0;
+}
+
+void gxp_fw_destroy(struct gxp_dev *gxp)
+{
+ gxp_lpm_destroy(gxp);
+}
+
+int gxp_firmware_run(struct gxp_dev *gxp, uint core)
+{
+ int ret = 0;
+
+ if (gxp->firmware_running & BIT(core)) {
+ dev_err(gxp->dev, "Firmware is already running on core %u\n",
+ core);
+ return -EBUSY;
+ }
+
+ ret = gxp_firmware_load(gxp, core);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to load firmware on core %u\n", core);
+ return ret;
+ }
+
+ ret = gxp_lpm_up(gxp, core);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to power up core %u\n", core);
+ goto out_firmware_unload;
+ }
+
+ ret = gxp_firmware_handshake(gxp, core);
+ if (ret) {
+ dev_err(gxp->dev, "Firmware handshake failed on core %u\n",
+ core);
+ /* TODO (b/176984045): Undo gxp_lpm_up() */
+ goto out_firmware_unload;
+ }
+
+ /* Initialize ioctl response queues */
+ INIT_LIST_HEAD(&(gxp->mailbox_resp_queues[core]));
+ init_waitqueue_head(&(gxp->mailbox_resp_waitqs[core]));
+
+ /* Initialize mailbox */
+ gxp->mailbox_mgr->mailboxes[core] = gxp_mailbox_alloc(gxp->mailbox_mgr,
+ core);
+ if (IS_ERR(gxp->mailbox_mgr->mailboxes[core])) {
+ dev_err(gxp->dev,
+ "Unable to allocate mailbox (core=%u, ret=%ld)\n", core,
+ PTR_ERR(gxp->mailbox_mgr->mailboxes[core]));
+ ret = PTR_ERR(gxp->mailbox_mgr->mailboxes[core]);
+ gxp->mailbox_mgr->mailboxes[core] = NULL;
+ goto out_firmware_unload;
+ }
+
+ gxp_mailbox_register_debug_handler(gxp->mailbox_mgr->mailboxes[core],
+ gxp_debug_dump_process_dump,
+ GXP_DEBUG_DUMP_INT_MASK);
+
+ /*
+ * Wait until the FW consumes the new mailbox register values before
+ * allowing messages to be sent thus manipulating the mailbox pointers.
+ */
+ msleep(25 * GXP_TIME_DELAY_FACTOR);
+ gxp->firmware_running |= BIT(core);
+
+ return ret;
+
+out_firmware_unload:
+ gxp_firmware_unload(gxp, core);
+ return ret;
+}
+
+void gxp_firmware_stop(struct gxp_dev *gxp, uint core)
+{
+ if (!(gxp->firmware_running & BIT(core)))
+ dev_err(gxp->dev, "Firmware is not running on core %u\n", core);
+
+ gxp->firmware_running &= ~BIT(core);
+
+ gxp_mailbox_release(gxp->mailbox_mgr,
+ gxp->mailbox_mgr->mailboxes[core]);
+ dev_notice(gxp->dev, "Mailbox %u released\n", core);
+
+ gxp_lpm_down(gxp, core);
+ gxp_firmware_unload(gxp, core);
+}
diff --git a/gxp-firmware.h b/gxp-firmware.h
new file mode 100644
index 0000000..616289f
--- /dev/null
+++ b/gxp-firmware.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP firmware loader.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_FIRMWARE_H__
+#define __GXP_FIRMWARE_H__
+
+#include <linux/bitops.h>
+
+#include "gxp-internal.h"
+
+static inline bool gxp_is_fw_running(struct gxp_dev *gxp, uint core)
+{
+ return (gxp->firmware_running & BIT(core)) != 0;
+}
+
+/*
+ * Initializes the firmware loading/unloading subsystem. This includes
+ * initializing the LPM and obtaining the memory regions needed to load the FW.
+ * The function needs to be called once after a block power up event.
+ */
+int gxp_fw_init(struct gxp_dev *gxp);
+/*
+ * Tears down the firmware loading/unloading subsystem in preparation for a
+ * block-level shutdown event. To be called once before a block shutdown.
+ */
+void gxp_fw_destroy(struct gxp_dev *gxp);
+/*
+ * Loads the firmware for the specified core in system memory and powers up the
+ * core to start FW execution.
+ */
+int gxp_firmware_run(struct gxp_dev *gxp, uint core);
+/*
+ * Shuts down the specified core.
+ */
+void gxp_firmware_stop(struct gxp_dev *gxp, uint core);
+
+#endif /* __GXP_FIRMWARE_H__ */
diff --git a/gxp-host-device-structs.h b/gxp-host-device-structs.h
new file mode 100644
index 0000000..be29da2
--- /dev/null
+++ b/gxp-host-device-structs.h
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP host-device interface structures.
+ *
+ * Copyright (C) 2021 Google LLC
+ *
+ * This header is shared with the GXP firmware. It establishes the format of the
+ * shared structures used by the GXP driver to describe to the GXP FW the HW
+ * setup and memory regions needed by the FW to operate.
+ * Since the header is shared with the FW, it cannot rely on kernel-specific
+ * headers or data structures.
+ *
+ */
+#ifndef __GXP_HOST_DEVICE_STRUCTURES_H__
+#define __GXP_HOST_DEVICE_STRUCTURES_H__
+
+#define NUM_CORES 4
+#define NUM_SYSTEM_SEMAPHORES 64
+
+/* A structure describing the state of the doorbells on the system. */
+struct gxp_doorbells_descriptor {
+ /* The app this descriptor belongs to. */
+ uint32_t application_id;
+ /* The physical ID of the sync barrier protecting this region. */
+ uint32_t protection_barrier;
+ /* The number of doorbells described in this region. */
+ uint32_t num_items;
+ /* The list of doorbells available for usage. */
+ struct dooorbell_metadata_t {
+ /*
+ * The number of users using this doorbell. 0 when it's
+ * available.
+ */
+ uint32_t users_count;
+ /* The 0-based index of the doorbell described by this entry. */
+ uint32_t hw_doorbell_idx;
+ } doorbells[];
+};
+
+/* A structure describing the state of the sync barriers on the system. */
+struct gxp_sync_barriers_descriptor {
+ /* The app this descriptor belongs to. */
+ uint32_t application_id;
+ /* The physical ID of the sync barrier protecting this region. */
+ uint32_t protection_barrier;
+ /* The number of sync barriers described in this region. */
+ uint32_t num_items;
+ /* The list of sync barriers available for usage. */
+ struct sync_barrier_metadata_t {
+ /*
+ * The number of users using this barrier. 0 when it's
+ * available.
+ */
+ uint32_t users_count;
+ /*
+ * The 0-based index of the sync barrier described by this
+ * entry.
+ */
+ uint32_t hw_barrier_idx;
+ } barriers[];
+};
+
+/* A structure describing the state of the watchdog on the system. */
+struct gxp_watchdog_descriptor {
+ /* The physical ID of the sync barrier protecting this region. */
+ uint32_t protection_barrier;
+ /*
+ * The number of timer ticks before the watchdog expires.
+ * This is in units of 244.14 ns.
+ */
+ uint32_t target_value;
+ /* A bit mask of the cores expected to tickle the watchdog. */
+ uint32_t participating_cores;
+ /* A bit mask of the cores that have tickled the watchdog. */
+ uint32_t responded_cores;
+ /* A flag indicating whether or not the watchdog has tripped. */
+ uint32_t tripped;
+};
+
+/* A structure describing the logging and tracing parameters and buffers. */
+struct gxp_logging_tracing_descriptor {
+ /* A struct for describing the parameters for log and trace buffers */
+ struct log_trace_descriptor {
+ /*
+ * The device address for the buffer used for storing events. A
+ * value of 0 indicates the corresponding buffer hasn't been
+ * allocated, or has been deallocated.
+ * The head and tail indeces are described inside the data
+ * pointed to by `buffer_addr`.
+ */
+ uint32_t buffer_addr;
+ /* The size of the buffer (in bytes) */
+ uint32_t buffer_size;
+ /* The watermark interrupt threshold (in bytes) */
+ uint32_t watermark_level;
+ } per_core_loggers[NUM_CORES], per_core_tracers[NUM_CORES];
+};
+
+/*
+ * A structure describing the state and allocations of the SW-based semaphores
+ * on the system.
+ */
+struct gxp_semaphores_descriptor {
+ /* The app this descriptor belongs to. */
+ uint32_t application_id;
+ /* The physical ID of the sync barrier protecting this region. */
+ uint32_t protection_barrier;
+ /*
+ * An array where each element is dedicated to a core. The element is a
+ * bit map describing of all the semaphores in the list below that have
+ * been unlocked but haven't been processed yet by the receiptient core.
+ */
+ uint64_t woken_pending_semaphores[NUM_CORES];
+ /*
+ * A mapping of which doorbells to use as a wakeup signal source per
+ * core.
+ */
+ uint32_t wakeup_doorbells[NUM_CORES];
+ /* The number of items described in this region. */
+ uint32_t num_items;
+ /* The list of semaphores available for usage. */
+ struct semaphore_metadata {
+ /*
+ * The number of users using this semaphore. 0 when it's for
+ * creation.
+ * Note: this is not the count value of the semaphore, but just
+ * an indication if this slot is available.
+ */
+ uint32_t users_count;
+ /*
+ * This is the semaphore count. Cores will block when they call
+ * 'Wait()' while this count is 0.
+ */
+ uint32_t count;
+ /*
+ * A bit map of 'NUM_DSP_CORES' bits indicating which cores are
+ * currently waiting on this semaphore to become available.
+ */
+ uint32_t waiters;
+ } semaphores[NUM_SYSTEM_SEMAPHORES];
+};
+
+/* A basic unidirectional queue. */
+struct gxp_queue_info {
+ /* A header describing the queue and its state. */
+ struct queue_header {
+ /* A device-side pointer of the storage managed by this queue */
+ uint32_t storage;
+ /* The index to the head of the queue. */
+ uint32_t head_idx;
+ /* The index to the tail of the queue. */
+ uint32_t tail_idx;
+ /* The size of an element stored this queue. */
+ uint32_t element_size;
+ /* The number of elements that can be stored in this queue. */
+ uint32_t elements_count;
+ } header;
+ /* The semaphore ID controlling exclusive access to this core. */
+ uint32_t access_sem_id;
+ /*
+ * The ID for the semaphore containing the number of unprocessed items
+ * pushed to this queue.
+ */
+ uint32_t posted_slots_sem_id;
+ /*
+ * The ID for the semaphore containing the number of free slots
+ * available to store data in this queue.
+ */
+ uint32_t free_slots_sem_id;
+};
+
+/* A struct describing a single core's set of incoming queues. */
+struct gxp_core_info {
+ /*
+ * The metadata for the queue holding incoming commands from other
+ * cores.
+ */
+ struct gxp_queue_info incoming_commands_queue;
+ /*
+ * The metadata for the queue holding incoming responses from other
+ * cores.
+ */
+ struct gxp_queue_info incoming_responses_queue;
+};
+
+/* A structure describing all the cores' per-core metadata. */
+struct gxp_cores_descriptor {
+ /* The number of cores described in this descriptor. */
+ uint32_t num_items;
+ /* The descriptors for each core. */
+ struct gxp_core_info cores[];
+};
+
+/*
+ * The top level descriptor describing memory regions used to access system-wide
+ * structures and resources.
+ */
+struct gxp_system_descriptor {
+ /* A device address for the application data descriptor. */
+ uint32_t app_descriptor_dev_addr[NUM_CORES];
+ /* A device address for the watchdog descriptor. */
+ uint32_t watchdog_dev_addr;
+ /* A device address for the logging/tracing descriptor */
+ uint32_t logging_tracing_dev_addr;
+};
+
+/* A structure describing the metadata belonging to a specific application. */
+struct gxp_application_descriptor {
+ /* The ID for this GXP application. */
+ uint32_t application_id;
+ /* The number of cores this application has. */
+ uint16_t core_count;
+ /*
+ * The cores mask; a bit at index `n` indicates that core `n` is part of
+ * this app.
+ */
+ uint16_t cores_mask;
+ /* The number of threads allocated for each core. */
+ uint16_t threads_count;
+ /* The size of system memory given to this app. */
+ uint32_t system_memory_size;
+ /* The device-address of the system memory given to this app. */
+ uint32_t system_memory_addr;
+ /* The size of TCM memory allocated per bank for this app. */
+ uint32_t tcm_memory_per_bank; /* in units of 4 kB */
+ /* A device address for the doorbells descriptor. */
+ uint32_t doorbells_dev_addr;
+ /* A device address for the sync barriers descriptor. */
+ uint32_t sync_barriers_dev_addr;
+ /* A device address for the semaphores descriptor. */
+ uint32_t semaphores_dev_addr;
+ /* A device address for the cores cmd/rsp queues descriptor. */
+ uint32_t cores_info_dev_addr;
+};
+
+/* The structure describing a core-to-core command. */
+struct gxp_core_to_core_command {
+ /* The source of port number (the core's virtual ID) of the command. */
+ uint32_t source;
+ /* The command's sequence number. */
+ uint64_t sequence_number;
+ /* The command payload device address. */
+ uint64_t device_address;
+ /* The size of the payload in bytes. */
+ uint32_t size;
+ /* The generic command flags. */
+ uint32_t flags;
+};
+
+/* The structure describing a core-to-core response. */
+struct gxp_core_to_core_response {
+ /* The source of port number (the core's virtual ID) of the response. */
+ uint32_t source;
+ /* The response's sequence number. */
+ uint64_t sequence_number;
+ /* The response error code (if any). */
+ uint16_t error_code;
+ /* The response return value (filled-in by the user). */
+ int32_t cmd_retval;
+};
+
+#endif /* __GXP_HOST_DEVICE_STRUCTURES_H__ */
diff --git a/gxp-hw-mailbox-driver.c b/gxp-hw-mailbox-driver.c
new file mode 100644
index 0000000..3019897
--- /dev/null
+++ b/gxp-hw-mailbox-driver.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP hardware-based mailbox driver implementation.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/of_irq.h>
+
+#include "gxp-tmp.h"
+#include "gxp-mailbox.h"
+#include "gxp-mailbox-driver.h"
+#include "gxp-mailbox-regs.h"
+
+static u32 csr_read(struct gxp_mailbox *mailbox, uint reg_offset)
+{
+ return readl(mailbox->csr_reg_base + reg_offset);
+}
+
+static void csr_write(struct gxp_mailbox *mailbox, uint reg_offset, u32 value)
+{
+ writel(value, mailbox->csr_reg_base + reg_offset);
+}
+
+static u32 data_read(struct gxp_mailbox *mailbox, uint reg_offset)
+{
+ return readl(mailbox->data_reg_base + reg_offset);
+}
+
+static void data_write(struct gxp_mailbox *mailbox, uint reg_offset,
+ u32 value)
+{
+ writel(value, mailbox->data_reg_base + reg_offset);
+}
+
+/* IRQ Handling */
+
+/* Interrupt to signal a response from the device to host */
+#define MBOX_DEVICE_TO_HOST_RESPONSE_IRQ_MASK BIT(0)
+
+static irqreturn_t mailbox_irq_handler(int irq, void *arg)
+{
+ u32 masked_status;
+ struct gxp_mailbox *mailbox = (struct gxp_mailbox *) arg;
+
+ /* Contains only the non-masked, pending interrupt bits */
+ masked_status = gxp_mailbox_get_host_mask_status(mailbox);
+
+ /* Clear all pending IRQ bits */
+ gxp_mailbox_clear_host_interrupt(mailbox, masked_status);
+
+ if (masked_status & MBOX_DEVICE_TO_HOST_RESPONSE_IRQ_MASK) {
+ mailbox->handle_irq(mailbox);
+ masked_status &= ~MBOX_DEVICE_TO_HOST_RESPONSE_IRQ_MASK;
+ }
+
+ if (masked_status & mailbox->debug_dump_int_mask) {
+ mailbox->handle_debug_dump_irq(mailbox);
+ masked_status &= ~mailbox->debug_dump_int_mask;
+ }
+
+ if (masked_status)
+ pr_err_ratelimited(
+ "mailbox%d: received unknown interrupt bits 0x%x\n",
+ mailbox->core_id, masked_status);
+
+ return IRQ_HANDLED;
+}
+
+static void register_irq(struct gxp_mailbox *mailbox)
+{
+ int err;
+ unsigned int virq;
+
+ virq = irq_of_parse_and_map(mailbox->gxp->dev->of_node,
+ mailbox->core_id);
+ if (!virq) {
+ pr_err("Unable to parse interrupt for core %d from the DT\n",
+ mailbox->core_id);
+ return;
+ }
+
+ err = request_irq(virq, mailbox_irq_handler, /*flags=*/ 0,
+ "aurora_mbx_irq", (void *) mailbox);
+ if (err) {
+ pr_err("Unable to register IRQ num=%d; error=%d\n", virq, err);
+ return;
+ }
+
+ mailbox->interrupt_virq = virq;
+ pr_debug("Core %d's mailbox interrupt registered as IRQ %u.\n",
+ mailbox->core_id, virq);
+}
+
+static void unregister_irq(struct gxp_mailbox *mailbox)
+{
+ if (mailbox->interrupt_virq) {
+ pr_debug("Freeing IRQ %d\n", mailbox->interrupt_virq);
+ free_irq(mailbox->interrupt_virq, mailbox);
+ mailbox->interrupt_virq = 0;
+ }
+}
+
+/* gxp-mailbox-driver.h interface */
+
+void gxp_mailbox_driver_init(struct gxp_mailbox *mailbox)
+{
+ register_irq(mailbox);
+ return;
+}
+
+void gxp_mailbox_driver_exit(struct gxp_mailbox *mailbox)
+{
+ unregister_irq(mailbox);
+ return;
+}
+
+void __iomem *gxp_mailbox_get_csr_base(struct gxp_dev *gxp, uint index)
+{
+ return gxp->mbx[index].vaddr;
+}
+
+void __iomem *gxp_mailbox_get_data_base(struct gxp_dev *gxp, uint index)
+{
+ return gxp->mbx[index].vaddr + 0x80;
+}
+
+/* gxp-mailbox-driver.h: CSR-based calls */
+
+void gxp_mailbox_reset_hw(struct gxp_mailbox *mailbox)
+{
+ csr_write(mailbox, MBOX_MCUCTLR_OFFSET, 1);
+}
+
+void gxp_mailbox_generate_device_interrupt(struct gxp_mailbox *mailbox,
+ u32 int_mask)
+{
+ csr_write(mailbox, MBOX_INTGR0_OFFSET, int_mask);
+}
+
+u32 gxp_mailbox_get_device_mask_status(struct gxp_mailbox *mailbox)
+{
+ return csr_read(mailbox, MBOX_INTMSR0_OFFSET);
+}
+
+void gxp_mailbox_clear_host_interrupt(struct gxp_mailbox *mailbox, u32 int_mask)
+{
+ csr_write(mailbox, MBOX_INTCR1_OFFSET, int_mask);
+}
+
+void gxp_mailbox_mask_host_interrupt(struct gxp_mailbox *mailbox, u32 int_mask)
+{
+ csr_write(mailbox, MBOX_INTMR1_OFFSET, int_mask);
+}
+
+u32 gxp_mailbox_get_host_mask_status(struct gxp_mailbox *mailbox)
+{
+ return csr_read(mailbox, MBOX_INTMSR1_OFFSET);
+}
+
+/* gxp-mailbox-driver.h: Data register-based calls */
+
+void gxp_mailbox_write_status(struct gxp_mailbox *mailbox, u32 status)
+{
+ data_write(mailbox, MBOX_STATUS_OFFSET, status);
+}
+
+void gxp_mailbox_write_descriptor(struct gxp_mailbox *mailbox,
+ dma_addr_t descriptor_addr)
+{
+ data_write(mailbox, MBOX_DESCRIPTOR_ADDR_OFFSET, (u32)descriptor_addr);
+}
+
+void gxp_mailbox_write_cmd_queue_tail(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_resp_head =
+ data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET) &
+ RESP_HEAD_MASK;
+ u32 new_cmd_tail = (u32)val << CMD_TAIL_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET,
+ new_cmd_tail | current_resp_head);
+}
+
+void gxp_mailbox_write_resp_queue_head(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_cmd_tail =
+ data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET) &
+ CMD_TAIL_MASK;
+ u32 new_resp_head = (u32)val << RESP_HEAD_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET,
+ current_cmd_tail | new_resp_head);
+}
+
+u16 gxp_mailbox_read_cmd_queue_head(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET);
+
+ return (u16)((reg_val & CMD_HEAD_MASK) >> CMD_HEAD_SHIFT);
+}
+
+u16 gxp_mailbox_read_resp_queue_tail(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET);
+
+ return (u16)((reg_val & RESP_TAIL_MASK) >> RESP_TAIL_SHIFT);
+}
+
+void gxp_mailbox_write_cmd_queue_head(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_resp_tail =
+ data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET) &
+ RESP_TAIL_MASK;
+ u32 new_cmd_head = (u32)val << CMD_HEAD_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET,
+ new_cmd_head | current_resp_tail);
+}
+
+void gxp_mailbox_write_resp_queue_tail(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_cmd_head =
+ data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET) &
+ CMD_HEAD_MASK;
+ u32 new_resp_tail = (u32)val << RESP_TAIL_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET,
+ current_cmd_head | new_resp_tail);
+}
+
+u16 gxp_mailbox_read_cmd_queue_tail(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET);
+
+ return (u16)((reg_val & CMD_TAIL_MASK) >> CMD_TAIL_SHIFT);
+}
+
+u16 gxp_mailbox_read_resp_queue_head(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET);
+
+ return (u16)((reg_val & RESP_HEAD_MASK) >> RESP_HEAD_SHIFT);
+}
diff --git a/gxp-internal.h b/gxp-internal.h
new file mode 100644
index 0000000..1893108
--- /dev/null
+++ b/gxp-internal.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP driver common internal definitions.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_INTERNAL_H__
+#define __GXP_INTERNAL_H__
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iommu.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/spinlock.h>
+
+#include "gxp-config.h"
+#include "gxp-tmp.h"
+
+/* Holds state belonging to a client */
+struct gxp_client {
+ struct gxp_dev *gxp;
+ void *app;
+ bool vd_allocated;
+};
+
+/* ioremapped resource */
+struct gxp_mapped_resource {
+ void __iomem *vaddr; /* starting virtual address */
+ phys_addr_t paddr; /* starting physical address */
+ dma_addr_t daddr; /* starting device address */
+ resource_size_t size; /* size in bytes */
+};
+
+struct mailbox_resp_list {
+ struct list_head list;
+ struct gxp_response *resp;
+};
+
+/* Structure to hold TPU device info */
+struct gxp_tpu_dev {
+ phys_addr_t mbx_paddr;
+};
+
+/* Forward declarations from submodules */
+struct gxp_mailbox_manager;
+struct gxp_debug_dump_manager;
+struct gxp_mapping_root;
+struct gxp_dma_manager;
+struct gxp_fw_data_manager;
+
+struct gxp_dev {
+ struct device *dev; /* platform bus device */
+ struct miscdevice misc_dev; /* misc device structure */
+ struct dentry *d_entry; /* debugfs dir for this device */
+ struct gxp_mapped_resource regs; /* ioremapped CSRs */
+ struct gxp_mapped_resource mbx[GXP_NUM_CORES]; /* mailbox CSRs */
+ struct gxp_mapped_resource fwbufs[GXP_NUM_CORES]; /* FW carveout */
+ struct gxp_mapped_resource fwdatabuf; /* Shared FW data carveout */
+ struct gxp_mapped_resource coredumpbuf; /* core dump carveout */
+ struct gxp_mailbox_manager *mailbox_mgr;
+ /*
+ * TODO(b/182416287): This should be a rb_tree of lists keyed by
+ * virtual device. For now, keep an array of one list per physical core
+ */
+ struct list_head mailbox_resp_queues[GXP_NUM_CORES];
+ wait_queue_head_t mailbox_resp_waitqs[GXP_NUM_CORES];
+ spinlock_t mailbox_resps_lock;
+ struct gxp_debug_dump_manager *debug_dump_mgr;
+ struct gxp_mapping_root *mappings; /* tree of user mappings */
+ u32 firmware_running; /* firmware status bitmap */
+ struct mutex vd_lock; /* synchronizes vd operations */
+ struct gxp_client *core_to_client[GXP_NUM_CORES];
+ struct gxp_client *debugfs_client;
+ struct gxp_dma_manager *dma_mgr;
+ struct gxp_fw_data_manager *data_mgr;
+ struct gxp_tpu_dev tpu_dev;
+};
+
+/* GXP device IO functions */
+
+static inline u32 gxp_read_32(struct gxp_dev *gxp, uint reg_offset)
+{
+ return readl(gxp->regs.vaddr + reg_offset);
+}
+
+static inline void gxp_write_32(struct gxp_dev *gxp, uint reg_offset, u32 value)
+{
+ writel(value, gxp->regs.vaddr + reg_offset);
+}
+
+static inline u32 gxp_read_32_core(struct gxp_dev *gxp, uint core,
+ uint reg_offset)
+{
+ uint offset = GXP_CORE_0_BASE + (GXP_CORE_SIZE * core) + reg_offset;
+
+ return gxp_read_32(gxp, offset);
+}
+
+static inline void gxp_write_32_core(struct gxp_dev *gxp, uint core,
+ uint reg_offset, u32 value)
+{
+ uint offset = GXP_CORE_0_BASE + (GXP_CORE_SIZE * core) + reg_offset;
+
+ gxp_write_32(gxp, offset, value);
+}
+
+static inline void gxp_acquire_sync_barrier(struct gxp_dev *gxp, uint index)
+{
+ uint barrier_reg_offset;
+
+ if (index >= SYNC_BARRIER_COUNT) {
+ dev_err(gxp->dev,
+ "Attempt to acquire non-existent sync barrier: %d\n",
+ index);
+ return;
+ }
+
+ barrier_reg_offset = SYNC_BARRIER_BLOCK + SYNC_BARRIER_BASE(index);
+ while (gxp_read_32(gxp, barrier_reg_offset) !=
+ SYNC_BARRIER_FREE_VALUE) {
+ /*
+ * Sleep for the minimum amount.
+ * msleep(1~20) may not do what the caller intends, and will
+ * often sleep longer (~20 ms actual sleep for any value given
+ * in the 1~20ms range).
+ */
+ msleep(20);
+ }
+}
+
+static inline void gxp_release_sync_barrier(struct gxp_dev *gxp, uint index)
+{
+ uint barrier_reg_offset;
+
+ if (index >= SYNC_BARRIER_COUNT) {
+ dev_err(gxp->dev,
+ "Attempt to acquire non-existent sync barrier: %d\n",
+ index);
+ return;
+ }
+
+ barrier_reg_offset = SYNC_BARRIER_BLOCK + SYNC_BARRIER_BASE(index);
+ gxp_write_32(gxp, barrier_reg_offset, 1);
+}
+static inline u32 gxp_read_sync_barrier_shadow(struct gxp_dev *gxp, uint index)
+{
+ uint barrier_reg_offset;
+
+ if (index >= SYNC_BARRIER_COUNT) {
+ dev_err(gxp->dev,
+ "Attempt to read non-existent sync barrier: %0u\n",
+ index);
+ return 0;
+ }
+
+ barrier_reg_offset = SYNC_BARRIER_BLOCK + SYNC_BARRIER_BASE(index) +
+ SYNC_BARRIER_SHADOW_OFFSET;
+
+ return gxp_read_32(gxp, barrier_reg_offset);
+}
+
+static inline int gxp_acquire_rmem_resource(struct gxp_dev *gxp,
+ struct resource *r, char *phandle)
+{
+ int ret;
+ struct device_node *np;
+
+ np = of_parse_phandle(gxp->dev->of_node, phandle, 0);
+ if (IS_ERR_OR_NULL(np)) {
+ dev_err(gxp->dev, "Failed to find \"%s\" reserved memory\n",
+ phandle);
+ return -ENODEV;
+ }
+
+ ret = of_address_to_resource(np, 0, r);
+ of_node_put(np);
+
+ return ret;
+}
+
+#endif /* __GXP_INTERNAL_H__ */
diff --git a/gxp-iova.h b/gxp-iova.h
new file mode 100644
index 0000000..e8a445f
--- /dev/null
+++ b/gxp-iova.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP IOVAs. The list of addresses for fixed device-side IOVAs
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_IOVAS_H__
+#define __GXP_IOVAS_H__
+
+#define GXP_IOVA_SYNC_BARRIERS (0x100000)
+#define GXP_IOVA_MAILBOX(_x_) (0x18390000 + (_x_) * 0x00020000)
+#define GXP_IOVA_EXT_TPU_MBX (0x1CEC0000)
+#define GXP_IOVA_AURORA_TOP (0x25C00000)
+#define GXP_IOVA_CORE_DUMP (0xF1C00000)
+#define GXP_IOVA_FIRMWARE(_x_) (0xFA000000 + (_x_) * 0x01000000)
+#define GXP_IOVA_FW_DATA (0xFE000000)
+#define GXP_IOVA_TPU_MBX_BUFFER(_x_) (0xFE100000 + (_x_) * 0x00040000)
+
+#endif /* __GXP_IOVAS_H__ */
diff --git a/gxp-lpm.c b/gxp-lpm.c
new file mode 100644
index 0000000..de0f74a
--- /dev/null
+++ b/gxp-lpm.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP power management interface.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/pm_runtime.h>
+
+#ifdef CONFIG_GXP_CLOUDRIPPER
+#include <linux/acpm_dvfs.h>
+#endif
+
+#include "gxp-bpm.h"
+#include "gxp-doorbell.h"
+#include "gxp-internal.h"
+#include "gxp-lpm.h"
+#include "gxp-tmp.h"
+
+static void enable_state(struct gxp_dev *gxp, uint psm, uint state)
+{
+ uint offset = LPM_REG_ENABLE_STATE_0 + (LPM_STATE_TABLE_SIZE * state);
+
+ /* PS0 should always be enabled */
+ WARN_ON(state == 0);
+
+ /* Disable all low power states */
+ lpm_write_32_psm(gxp, psm, LPM_REG_ENABLE_STATE_1, 0x0);
+ lpm_write_32_psm(gxp, psm, LPM_REG_ENABLE_STATE_2, 0x0);
+ lpm_write_32_psm(gxp, psm, LPM_REG_ENABLE_STATE_3, 0x0);
+
+ /* Enable the requested low power state */
+ lpm_write_32_psm(gxp, psm, offset, 0x1);
+}
+
+static bool is_initialized(struct gxp_dev *gxp, uint psm)
+{
+ u32 status = lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET);
+
+ /*
+ * state_valid bit goes active and stays high forever the first time you
+ * write the start register
+ */
+ if (status & PSM_STATE_VALID_MASK)
+ return true;
+
+ return false;
+}
+
+static uint get_state(struct gxp_dev *gxp, uint psm)
+{
+ u32 status = lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET);
+
+ return status & PSM_CURR_STATE_MASK;
+}
+
+int gxp_blk_set_state(struct gxp_dev *gxp, unsigned long state)
+{
+ int ret = 0;
+
+#ifdef CONFIG_GXP_CLOUDRIPPER
+ ret = exynos_acpm_set_rate(AUR_DVFS_DOMAIN, state);
+ dev_dbg(gxp->dev, "%s: state %lu, ret %d\n", __func__, state, ret);
+#endif
+ return ret;
+}
+
+int gxp_blk_get_state(struct gxp_dev *gxp)
+{
+ int ret = 0;
+
+#ifdef CONFIG_GXP_CLOUDRIPPER
+ ret = exynos_acpm_get_rate(AUR_DVFS_DOMAIN, AUR_DEBUG_CORE_FREQ);
+ dev_dbg(gxp->dev, "%s: state %d\n", __func__, ret);
+#endif
+ return ret;
+}
+
+static int set_state_internal(struct gxp_dev *gxp, uint psm, uint target_state)
+{
+ u32 val;
+ int i = 10000;
+
+ /* Set SW sequencing mode and PS target */
+ val = LPM_SW_PSM_MODE;
+ val |= target_state << LPM_CFG_SW_PS_TARGET_OFFSET;
+ lpm_write_32_psm(gxp, psm, PSM_CFG_OFFSET, val);
+
+ /* Start the SW sequence */
+ lpm_write_32_psm(gxp, psm, PSM_START_OFFSET, 0x1);
+
+ /* Wait for LPM init done (0x60041688) */
+ while (i && !(lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET)
+ & PSM_INIT_DONE_MASK)) {
+ cpu_relax();
+ i--;
+ }
+
+ if (!i) {
+ dev_err(gxp->dev, "Failed to switch to PS%u\n", target_state);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int set_state(struct gxp_dev *gxp, uint psm, uint target_state)
+{
+ uint curr_state = get_state(gxp, psm);
+
+ if (curr_state == target_state)
+ return 0;
+
+ dev_warn(gxp->dev, "Forcing a transition to PS%u on core%u, status: %x\n",
+ target_state, psm,
+ lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET));
+
+ enable_state(gxp, psm, target_state);
+
+ if ((curr_state != LPM_ACTIVE_STATE)
+ && (target_state != LPM_ACTIVE_STATE)) {
+ /* Switch to PS0 before switching to a low power state. */
+ set_state_internal(gxp, psm, LPM_ACTIVE_STATE);
+ }
+
+ set_state_internal(gxp, psm, target_state);
+
+ dev_warn(gxp->dev, "Finished forced transition on core %u. target: PS%u, actual: PS%u, status: %x\n",
+ psm, target_state, get_state(gxp, psm),
+ lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET));
+
+ /* Set HW sequencing mode */
+ lpm_write_32_psm(gxp, psm, PSM_CFG_OFFSET, LPM_HW_MODE);
+
+ return 0;
+}
+
+static int psm_enable(struct gxp_dev *gxp, uint psm)
+{
+ int i = 10000;
+
+ /* Return early if LPM is already initialized */
+ if (is_initialized(gxp, psm)) {
+ if (psm != LPM_TOP_PSM) {
+ /* Ensure core is in PS2 */
+ return set_state(gxp, psm, LPM_PG_W_RET_STATE);
+ }
+
+ return 0;
+ }
+
+ /* Write PSM start bit */
+ lpm_write_32_psm(gxp, psm, PSM_START_OFFSET, PSM_START);
+ msleep(20 * GXP_TIME_DELAY_FACTOR);
+
+ /* Wait for LPM init done (0x60041688) */
+ while (i && !(lpm_read_32_psm(gxp, psm, PSM_STATUS_OFFSET)
+ & PSM_INIT_DONE_MASK)) {
+ cpu_relax();
+ i--;
+ }
+
+ if (!i)
+ return 1;
+
+ /* Set PSM to HW mode (0x60041680) */
+ lpm_write_32_psm(gxp, psm, PSM_CFG_OFFSET, PSM_HW_MODE);
+
+ return 0;
+}
+
+void gxp_lpm_init(struct gxp_dev *gxp)
+{
+ u32 val;
+
+ /*
+ * Some LPM signals are not looped back in the current FPGA
+ * implementation, causing the PSM to time out waiting for a handshake
+ * signal from the host.
+ * TODO: This is to be fixed in the next version of FPGA build
+ * WORKAROUND: Patch LPM instruction to bypass the timeout for now
+ * FIXME: The patch is only for ML2.5 build, and is incompatible to
+ * other builds
+ */
+ val = lpm_read_32(gxp, LPM_INSTRUCTION_OFFSET);
+ val &= (~LPM_INSTRUCTION_MASK);
+ lpm_write_32(gxp, LPM_INSTRUCTION_OFFSET, val);
+
+ /* Local Access Path should not be enabled */
+#if 0
+ /*
+ * Enable CNOC to DNOC path in Provino for direct TOP access from Q7
+ * cores.
+ */
+ val = gxp_read_32(gxp, PROVINO_IXBAR1_ARL_CTRL);
+ val |= PROVINO_IXBAR1_ARL_EN;
+ gxp_write_32(gxp, PROVINO_IXBAR1_ARL_CTRL, val);
+#endif
+
+ /* Enable Top PSM */
+ dev_notice(gxp->dev, "Enabling Top PSM...\n");
+ if (psm_enable(gxp, LPM_TOP_PSM)) {
+ dev_notice(gxp->dev, "Timed out!\n");
+ return;
+ }
+ dev_notice(gxp->dev, "Enabled\n");
+}
+
+void gxp_lpm_destroy(struct gxp_dev *gxp)
+{
+ /* (b/171063370) Put Top PSM in ACTIVE state before block shutdown */
+ dev_notice(gxp->dev, "Kicking Top PSM out of ACG\n");
+
+ /* Disable all low-power states for TOP */
+ lpm_write_32_psm(gxp, LPM_TOP_PSM, LPM_REG_ENABLE_STATE_1, 0x0);
+ lpm_write_32_psm(gxp, LPM_TOP_PSM, LPM_REG_ENABLE_STATE_2, 0x0);
+}
+
+int gxp_lpm_up(struct gxp_dev *gxp, uint core)
+{
+ /* Clear wakeup doorbell */
+ gxp_doorbell_clear(gxp, CORE_WAKEUP_DOORBELL);
+
+ /* Enable core PSM */
+ dev_notice(gxp->dev, "Enabling Core%u PSM...\n", core);
+ if (psm_enable(gxp, core)) {
+ dev_notice(gxp->dev, "Timed out!\n");
+ return 0;
+ }
+ dev_notice(gxp->dev, "Enabled\n");
+
+ /* Enable PS1 (Clk Gated) */
+ enable_state(gxp, core, LPM_CG_STATE);
+
+ gxp_bpm_start(gxp, core);
+
+#ifdef CONFIG_GXP_USE_SW_MAILBOX
+ /*
+ * Enable doorbells [28-31] for SW mailbox.
+ * TODO (b/182526648): Enable doorbells required for SW mailbox in the
+ * driver's alloc function.
+ */
+ gxp_write_32_core(gxp, core, GXP_REG_COMMON_INT_MASK_0, BIT(31 - core));
+#endif // CONFIG_GXP_USE_SW_MAILBOX
+
+ return 0;
+}
+
+void gxp_lpm_down(struct gxp_dev *gxp, uint core)
+{
+ /* Enable PS2 (Pwr Gated w/Ret) */
+ enable_state(gxp, core, LPM_PG_W_RET_STATE);
+
+ /* Set wakeup doorbell to trigger an automatic transition to PS2 */
+ gxp_doorbell_set_listening_core(gxp, CORE_WAKEUP_DOORBELL, core);
+ gxp_doorbell_set(gxp, CORE_WAKEUP_DOORBELL);
+ msleep(25 * GXP_TIME_DELAY_FACTOR);
+
+ /* Reset doorbell mask */
+ gxp_write_32_core(gxp, core, GXP_REG_COMMON_INT_MASK_0, 0);
+
+ /* Ensure core is in PS2 */
+ set_state(gxp, core, LPM_PG_W_RET_STATE);
+}
diff --git a/gxp-lpm.h b/gxp-lpm.h
new file mode 100644
index 0000000..3a4fbb7
--- /dev/null
+++ b/gxp-lpm.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP power management interface.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_LPM_H__
+#define __GXP_LPM_H__
+
+#include <linux/types.h>
+
+#include "gxp.h"
+
+enum lpm_psm_csrs {
+ LPM_REG_ENABLE_STATE_0 = 0x080,
+ LPM_REG_ENABLE_STATE_1 = 0x180,
+ LPM_REG_ENABLE_STATE_2 = 0x280,
+ LPM_REG_ENABLE_STATE_3 = 0x380,
+};
+
+enum lpm_state {
+ LPM_ACTIVE_STATE = 0,
+ LPM_CG_STATE = 1,
+ LPM_PG_W_RET_STATE = 2,
+ LPM_PG_STATE = 3,
+};
+
+#define LPM_STATE_TABLE_SIZE (LPM_REG_ENABLE_STATE_1 - LPM_REG_ENABLE_STATE_0)
+
+#define LPM_INSTRUCTION_OFFSET 0x00000944
+#define LPM_INSTRUCTION_MASK 0x03000000
+#define LPM_TOP_PSM 4
+#define LPM_HW_MODE 0
+#define LPM_SW_PSM_MODE 1
+
+#define LPM_CFG_SW_PS_TARGET_OFFSET 2
+
+#define CORE_WAKEUP_DOORBELL 0
+
+#define AUR_DVFS_DOMAIN 17
+#define AUR_DVFS_DEBUG_REQ (1 << 31)
+#define AUR_DEBUG_CORE_FREQ (AUR_DVFS_DEBUG_REQ | (3 << 27))
+
+#define AUR_DVFS_MIN_STATE 178000
+
+/*
+ * Initializes the power manager for the first time after block power up.
+ * The function needs to be called once after a block power up event.
+ */
+void gxp_lpm_init(struct gxp_dev *gxp);
+/*
+ * Destroys the power manager in preparation for a block shutdown.
+ * The function needs to be called once before a block shutdown event.
+ */
+void gxp_lpm_destroy(struct gxp_dev *gxp);
+/*
+ * Turns on the power manager for a specific core. i.e. powers up the core.
+ */
+int gxp_lpm_up(struct gxp_dev *gxp, uint core);
+/*
+ * Turns off the power manager for a specific core. i.e. powers down the core.
+ */
+void gxp_lpm_down(struct gxp_dev *gxp, uint core);
+/*
+ * Sets the block-level DVFS state. This function can be called at any point
+ * after block power on.
+ */
+int gxp_blk_set_state(struct gxp_dev *gxp, unsigned long state);
+/*
+ * Returns the current DVFS state of the Aurora block.
+ */
+int gxp_blk_get_state(struct gxp_dev *gxp);
+
+static inline u32 lpm_read_32(struct gxp_dev *gxp, uint reg_offset)
+{
+ uint offset = GXP_LPM_BASE + reg_offset;
+
+ return gxp_read_32(gxp, offset);
+}
+
+static inline void lpm_write_32(struct gxp_dev *gxp, uint reg_offset, u32 value)
+{
+ uint offset = GXP_LPM_BASE + reg_offset;
+
+ gxp_write_32(gxp, offset, value);
+}
+
+static inline u32 lpm_read_32_psm(struct gxp_dev *gxp, uint psm,
+ uint reg_offset)
+{
+ uint offset =
+ GXP_LPM_PSM_0_BASE + (GXP_LPM_PSM_SIZE * psm) + reg_offset;
+
+ return gxp_read_32(gxp, offset);
+}
+
+static inline void lpm_write_32_psm(struct gxp_dev *gxp, uint psm,
+ uint reg_offset, u32 value)
+{
+ uint offset =
+ GXP_LPM_PSM_0_BASE + (GXP_LPM_PSM_SIZE * psm) + reg_offset;
+
+ gxp_write_32(gxp, offset, value);
+}
+
+#endif /* __GXP_LPM_H__ */
diff --git a/gxp-mailbox-driver.h b/gxp-mailbox-driver.h
new file mode 100644
index 0000000..6e10b18
--- /dev/null
+++ b/gxp-mailbox-driver.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP mailbox driver.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_MAILBOX_DRIVER_H__
+#define __GXP_MAILBOX_DRIVER_H__
+
+#include "gxp-mailbox.h"
+
+void gxp_mailbox_driver_init(struct gxp_mailbox *mailbox);
+void gxp_mailbox_driver_exit(struct gxp_mailbox *mailbox);
+
+void __iomem *gxp_mailbox_get_csr_base(struct gxp_dev *gxp, uint index);
+void __iomem *gxp_mailbox_get_data_base(struct gxp_dev *gxp, uint index);
+
+void gxp_mailbox_reset_hw(struct gxp_mailbox *mailbox);
+
+void gxp_mailbox_generate_device_interrupt(struct gxp_mailbox *mailbox,
+ u32 int_mask);
+u32 gxp_mailbox_get_device_mask_status(struct gxp_mailbox *mailbox);
+
+void gxp_mailbox_clear_host_interrupt(struct gxp_mailbox *mailbox,
+ u32 int_mask);
+void gxp_mailbox_mask_host_interrupt(struct gxp_mailbox *mailbox, u32 int_mask);
+u32 gxp_mailbox_get_host_mask_status(struct gxp_mailbox *mailbox);
+
+void gxp_mailbox_write_status(struct gxp_mailbox *mailbox, u32 status);
+void gxp_mailbox_write_descriptor(struct gxp_mailbox *mailbox,
+ dma_addr_t descriptor_addr);
+
+void gxp_mailbox_write_cmd_queue_tail(struct gxp_mailbox *mailbox, u16 val);
+void gxp_mailbox_write_resp_queue_head(struct gxp_mailbox *mailbox, u16 val);
+u16 gxp_mailbox_read_cmd_queue_head(struct gxp_mailbox *mailbox);
+u16 gxp_mailbox_read_resp_queue_tail(struct gxp_mailbox *mailbox);
+
+/*
+ * These functions are only used to initialize these values on mailbox startup.
+ * During normal use, the host must not write the command queue head or response
+ * queue tail, as the device sets those values.
+ */
+void gxp_mailbox_write_cmd_queue_head(struct gxp_mailbox *mailbox, u16 val);
+void gxp_mailbox_write_resp_queue_tail(struct gxp_mailbox *mailbox, u16 val);
+u16 gxp_mailbox_read_cmd_queue_tail(struct gxp_mailbox *mailbox);
+u16 gxp_mailbox_read_resp_queue_head(struct gxp_mailbox *mailbox);
+
+#endif /* __GXP_MAILBOX_DRIVER_H__ */
diff --git a/gxp-mailbox-regs.h b/gxp-mailbox-regs.h
new file mode 100644
index 0000000..5d83b5e
--- /dev/null
+++ b/gxp-mailbox-regs.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP mailbox registers.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_MAILBOX_REGS_H__
+#define __GXP_MAILBOX_REGS_H__
+
+/* Mailbox CSRs */
+#define MBOX_MCUCTLR_OFFSET 0x0000
+
+#define MBOX_INTGR0_OFFSET 0x0020
+#define MBOX_INTCR0_OFFSET 0x0024
+#define MBOX_INTMR0_OFFSET 0x0028
+#define MBOX_INTSR0_OFFSET 0x002C
+#define MBOX_INTMSR0_OFFSET 0x0030
+
+#define MBOX_INTGR1_OFFSET 0x0040
+#define MBOX_INTCR1_OFFSET 0x0044
+#define MBOX_INTMR1_OFFSET 0x0048
+#define MBOX_INTSR1_OFFSET 0x004C
+#define MBOX_INTMSR1_OFFSET 0x0050
+
+/* Mailbox Shared Data Registers */
+#define MBOX_DATA_REG_BASE 0x0080
+
+#define MBOX_STATUS_OFFSET 0x00
+#define MBOX_DESCRIPTOR_ADDR_OFFSET 0x04
+#define MBOX_CMD_TAIL_RESP_HEAD_OFFSET 0x08
+#define MBOX_CMD_HEAD_RESP_TAIL_OFFSET 0x0C
+
+#define MBOX_REGS_SIZE 0x180
+
+/*
+ * Macros for separating out the command queue tail and response queue head in
+ * the `MBOX_CMD_TAIL_RESP_HEAD_OFFSET` register.
+ */
+#define CMD_TAIL_SHIFT 16
+#define RESP_HEAD_SHIFT 0
+#define CMD_TAIL_MASK (0xFFFF << CMD_TAIL_SHIFT)
+#define RESP_HEAD_MASK (0xFFFF << RESP_HEAD_SHIFT)
+
+/*
+ * Macros for separating out the command queue head and response queue tail in
+ * the `MBOX_CMD_HEAD_RESP_TAIL_OFFSET` register.
+ */
+#define CMD_HEAD_SHIFT 16
+#define RESP_TAIL_SHIFT 0
+#define CMD_HEAD_MASK (0xFFFF << CMD_HEAD_SHIFT)
+#define RESP_TAIL_MASK (0xFFFF << RESP_TAIL_SHIFT)
+
+#endif /* __GXP_MAILBOX_REGS_H__ */
diff --git a/gxp-mailbox.c b/gxp-mailbox.c
new file mode 100644
index 0000000..c262923
--- /dev/null
+++ b/gxp-mailbox.c
@@ -0,0 +1,811 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP mailbox.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/iommu.h>
+#include <linux/kthread.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+
+#include "gxp-dma.h"
+#include "gxp-internal.h"
+#include "gxp-mailbox.h"
+#include "gxp-mailbox-driver.h"
+#include "gxp-tmp.h"
+
+/* Timeout of 8s by default to account for slower emulation platforms */
+static int mbx_timeout = 8000;
+module_param(mbx_timeout, int, 0660);
+
+#define MAILBOX_TIMEOUT (mbx_timeout * GXP_TIME_DELAY_FACTOR)
+
+/* Utilities of circular queue operations */
+
+#define CIRCULAR_QUEUE_WRAP_BIT BIT(15)
+#define CIRCULAR_QUEUE_INDEX_MASK (CIRCULAR_QUEUE_WRAP_BIT - 1)
+#define CIRCULAR_QUEUE_WRAPPED(idx) ((idx) & CIRCULAR_QUEUE_WRAP_BIT)
+#define CIRCULAR_QUEUE_REAL_INDEX(idx) ((idx) & CIRCULAR_QUEUE_INDEX_MASK)
+
+#define MBOX_CMD_QUEUE_NUM_ENTRIES 1024
+#define MBOX_CMD_QUEUE_SIZE \
+ (sizeof(struct gxp_command) * MBOX_CMD_QUEUE_NUM_ENTRIES)
+
+#define MBOX_RESP_QUEUE_NUM_ENTRIES 1024
+#define MBOX_RESP_QUEUE_SIZE \
+ (sizeof(struct gxp_response) * MBOX_RESP_QUEUE_NUM_ENTRIES)
+
+#ifndef CONFIG_GXP_HAS_SYSMMU
+/* Constants for static queues in systems without a SysMMU */
+
+/*
+ * Queues in scratchpad space start at 0x280 to allow 0x180 of space for
+ * emulated registers in systems using software mailboxes.
+ */
+#define MBOX_CMD_QUEUE_SCRATCHPAD_OFFSET 0x280
+#define MBOX_RESP_QUEUE_SCRATCHPAD_OFFSET \
+ (MBOX_CMD_QUEUE_SCRATCHPAD_OFFSET + MBOX_CMD_QUEUE_SIZE)
+#define MBOX_DESCRIPTOR_SCRATCHPAD_OFFSET \
+ (MBOX_RESP_QUEUE_SCRATCHPAD_OFFSET + MBOX_RESP_QUEUE_SIZE)
+#endif
+
+/*
+ * Returns the number of elements in a circular queue given its @head, @tail,
+ * and @queue_size.
+ */
+static inline u32 circular_queue_count(u32 head, u32 tail, u32 queue_size)
+{
+ if (CIRCULAR_QUEUE_WRAPPED(tail) != CIRCULAR_QUEUE_WRAPPED(head))
+ return queue_size - CIRCULAR_QUEUE_REAL_INDEX(head) +
+ CIRCULAR_QUEUE_REAL_INDEX(tail);
+ else
+ return tail - head;
+}
+
+/* Increases @index of a circular queue by @inc. */
+static inline u32 circular_queue_inc(u32 index, u32 inc, u32 queue_size)
+{
+ u32 new_index = CIRCULAR_QUEUE_REAL_INDEX(index) + inc;
+
+ if (new_index >= queue_size)
+ return (index + inc - queue_size) ^ CIRCULAR_QUEUE_WRAP_BIT;
+ else
+ return index + inc;
+}
+
+/* Sets mailbox->cmd_queue_tail and corresponding CSR on device. */
+static void gxp_mailbox_set_cmd_queue_tail(struct gxp_mailbox *mailbox,
+ u32 value)
+{
+ mailbox->cmd_queue_tail = value;
+ gxp_mailbox_write_cmd_queue_tail(mailbox, value);
+}
+
+/* Sets mailbox->resp_queue_head and corresponding CSR on device. */
+static void gxp_mailbox_set_resp_queue_head(struct gxp_mailbox *mailbox,
+ u32 value)
+{
+ mailbox->resp_queue_head = value;
+ gxp_mailbox_write_resp_queue_head(mailbox, value);
+}
+
+/*
+ * Increases the command queue tail by @inc.
+ *
+ * The queue uses the mirrored circular buffer arrangement. Each index (head and
+ * tail) has a wrap bit, represented by the constant CIRCULAR_QUEUE_WRAP_BIT.
+ * Whenever an index is increased and will exceed the end of the queue, the wrap
+ * bit is xor-ed.
+ *
+ * This method will update both mailbox->cmd_queue_tail and CSR on device.
+ *
+ * Returns 0 on success.
+ * If command queue tail will exceed command queue head after adding @inc,
+ * -EBUSY is returned and all fields remain unchanged. The caller should
+ * handle this case and implement a mechanism to wait until the consumer
+ * consumes commands.
+ */
+static int gxp_mailbox_inc_cmd_queue_tail(struct gxp_mailbox *mailbox, u32 inc)
+{
+ u32 head;
+ u32 remain_size;
+ u32 new_tail;
+
+ if (inc > mailbox->cmd_queue_size)
+ return -EINVAL;
+
+ head = gxp_mailbox_read_cmd_queue_head(mailbox);
+ remain_size = mailbox->cmd_queue_size -
+ circular_queue_count(head, mailbox->cmd_queue_tail,
+ mailbox->cmd_queue_size);
+ /* no enough space left */
+ if (inc > remain_size)
+ return -EBUSY;
+
+ new_tail = circular_queue_inc(mailbox->cmd_queue_tail, inc,
+ mailbox->cmd_queue_size);
+ gxp_mailbox_set_cmd_queue_tail(mailbox, new_tail);
+ return 0;
+}
+
+/*
+ * Increases the response queue head by @inc.
+ *
+ * The queue uses the mirrored circular buffer arrangement. Each index (head and
+ * tail) has a wrap bit, represented by the constant CIRCULAR_QUEUE_WRAP_BIT.
+ * Whenever an index is increased and will exceed the end of the queue, the wrap
+ * bit is xor-ed.
+ *
+ * This method will update both mailbox->resp_queue_head and CSR on device.
+ *
+ * Returns 0 on success.
+ * -EINVAL is returned if the queue head will exceed tail of queue, and no
+ * fields or CSR is updated in this case.
+ */
+static int gxp_mailbox_inc_resp_queue_head(struct gxp_mailbox *mailbox, u32 inc)
+{
+ u32 tail;
+ u32 size;
+ u32 new_head;
+
+ if (inc > mailbox->resp_queue_size)
+ return -EINVAL;
+
+ tail = gxp_mailbox_read_resp_queue_tail(mailbox);
+ size = circular_queue_count(mailbox->resp_queue_head, tail,
+ mailbox->resp_queue_size);
+ if (inc > size)
+ return -EINVAL;
+ new_head = circular_queue_inc(mailbox->resp_queue_head, inc,
+ mailbox->resp_queue_size);
+ gxp_mailbox_set_resp_queue_head(mailbox, new_head);
+
+ return 0;
+}
+
+struct gxp_mailbox_manager *gxp_mailbox_create_manager(struct gxp_dev *gxp,
+ uint num_cores)
+{
+ struct gxp_mailbox_manager *mgr;
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return ERR_PTR(-ENOMEM);
+
+ mgr->gxp = gxp;
+ mgr->num_cores = num_cores;
+ mgr->get_mailbox_csr_base = gxp_mailbox_get_csr_base;
+ mgr->get_mailbox_data_base = gxp_mailbox_get_data_base;
+
+ mgr->mailboxes = devm_kcalloc(gxp->dev, mgr->num_cores,
+ sizeof(*mgr->mailboxes), GFP_KERNEL);
+ if (!mgr->mailboxes)
+ return ERR_PTR(-ENOMEM);
+
+ rwlock_init(&mgr->mailboxes_lock);
+
+ return mgr;
+}
+
+/*
+ * Pops the wait_list until the sequence number of @resp is found, and copies
+ * @resp to the found entry.
+ *
+ * Entries in wait_list should have sequence number in increasing order, but
+ * the responses arriving and being handled may be out-of-order.
+ *
+ * Iterate over the wait_list, comparing #cur->resp->seq with @resp->seq:
+ * 1. #cur->resp->seq > @resp->seq:
+ * - Nothing to do, either @resp is invalid or its command timed out.
+ * - We're done.
+ * 2. #cur->resp->seq == @resp->seq:
+ * - Copy @resp, pop the head.
+ * - If #cur->resp has a destination queue, push it to that queue
+ * - We're done.
+ * 3. #cur->resp->seq < @resp->seq:
+ * - @resp has arrived out of sequence order.
+ * - Leave #cur->resp in the wait_list.
+ * - Keep iterating unless the list is exhausted.
+ */
+static void gxp_mailbox_handle_response(struct gxp_mailbox *mailbox,
+ const struct gxp_response *resp)
+{
+ struct gxp_mailbox_wait_list *cur, *nxt;
+ struct gxp_async_response *async_resp;
+ unsigned long flags;
+
+ mutex_lock(&mailbox->wait_list_lock);
+
+ list_for_each_entry_safe(cur, nxt, &mailbox->wait_list, list) {
+ if (cur->resp->seq > resp->seq) {
+ /*
+ * This response has already timed out and been removed
+ * from the wait list (or this is an invalid response).
+ * Drop it.
+ */
+ break;
+ }
+ if (cur->resp->seq == resp->seq) {
+ memcpy(cur->resp, resp, sizeof(*resp));
+ list_del(&cur->list);
+ if (cur->is_async) {
+ async_resp =
+ container_of(cur->resp,
+ struct gxp_async_response,
+ resp);
+ cancel_delayed_work(&async_resp->timeout_work);
+ spin_lock_irqsave(async_resp->dest_queue_lock,
+ flags);
+ list_add_tail(&async_resp->list_entry,
+ async_resp->dest_queue);
+ /*
+ * Marking the dest_queue as NULL indicates the
+ * response was handled in case its timeout
+ * handler fired between acquiring the
+ * wait_list_lock and cancelling the timeout.
+ */
+ async_resp->dest_queue = NULL;
+ spin_unlock_irqrestore(
+ async_resp->dest_queue_lock, flags);
+ wake_up(async_resp->dest_queue_waitq);
+ }
+ kfree(cur);
+ break;
+ }
+ }
+
+ mutex_unlock(&mailbox->wait_list_lock);
+}
+
+/*
+ * Fetches elements in the response queue.
+ *
+ * Returns the pointer of fetched response elements.
+ * @total_ptr will be the number of elements fetched.
+ *
+ * Returns -ENOMEM if failed on memory allocation.
+ * Returns NULL if the response queue is empty.
+ */
+static struct gxp_response *
+gxp_mailbox_fetch_responses(struct gxp_mailbox *mailbox, u32 *total_ptr)
+{
+ u32 head;
+ u32 tail;
+ u32 count;
+ u32 i;
+ u32 j;
+ u32 total = 0;
+ const u32 size = mailbox->resp_queue_size;
+ const struct gxp_response *queue = mailbox->resp_queue;
+ struct gxp_response *ret = NULL;
+ struct gxp_response *prev_ptr = NULL;
+
+ mutex_lock(&mailbox->resp_queue_lock);
+
+ head = mailbox->resp_queue_head;
+ /* loop until our head equals to CSR tail */
+ while (1) {
+ tail = gxp_mailbox_read_resp_queue_tail(mailbox);
+ count = circular_queue_count(head, tail, size);
+ if (count == 0)
+ break;
+
+ prev_ptr = ret;
+ ret = krealloc(prev_ptr, (total + count) * sizeof(*queue),
+ GFP_KERNEL);
+ /*
+ * Out-of-memory, we can return the previously fetched responses
+ * if any, or ENOMEM otherwise.
+ */
+ if (!ret) {
+ if (!prev_ptr)
+ ret = ERR_PTR(-ENOMEM);
+ else
+ ret = prev_ptr;
+ break;
+ }
+ /* copy responses */
+ j = CIRCULAR_QUEUE_REAL_INDEX(head);
+ for (i = 0; i < count; i++) {
+ memcpy(&ret[total], &queue[j], sizeof(*queue));
+ ret[total].status = GXP_RESP_OK;
+ j = (j + 1) % size;
+ total++;
+ }
+ head = circular_queue_inc(head, count, size);
+ }
+ gxp_mailbox_inc_resp_queue_head(mailbox, total);
+
+ mutex_unlock(&mailbox->resp_queue_lock);
+ /*
+ * Now that the response queue has been drained, send an interrupt
+ * to the device in case firmware was waiting for us to consume
+ * responses.
+ */
+ if (total == size) {
+ /* TODO(b/190868834) define interrupt bits */
+ gxp_mailbox_generate_device_interrupt(mailbox, BIT(0));
+ }
+
+ *total_ptr = total;
+ return ret;
+}
+
+/*
+ * Fetches and handles responses, then wakes up threads that are waiting for a
+ * response.
+ *
+ * Note: this worker is scheduled in the IRQ handler, to prevent use-after-free
+ * or race-condition bugs, gxp_mailbox_release() must be called before free the
+ * mailbox.
+ */
+static void gxp_mailbox_consume_responses_work(struct work_struct *work)
+{
+ struct gxp_mailbox *mailbox =
+ container_of(work, struct gxp_mailbox, response_work);
+ struct gxp_response *responses;
+ u32 i;
+ u32 count = 0;
+
+ /*
+ * TODO(b/177692488) Review if changes in edgetpu's consume response
+ * logic should be ported to the GXP driver as well.
+ */
+ /* fetch responses and bump RESP_QUEUE_HEAD */
+ responses = gxp_mailbox_fetch_responses(mailbox, &count);
+ if (IS_ERR(responses)) {
+ dev_err(mailbox->gxp->dev,
+ "GXP Mailbox failed on fetching responses: %ld",
+ PTR_ERR(responses));
+ return;
+ }
+
+ for (i = 0; i < count; i++)
+ gxp_mailbox_handle_response(mailbox, &responses[i]);
+ /*
+ * Responses handled, wake up threads that are waiting for a response.
+ */
+ wake_up(&mailbox->wait_list_waitq);
+ kfree(responses);
+}
+
+/*
+ * IRQ handler of GXP mailbox.
+ *
+ * Puts the gxp_mailbox_consume_responses_work() into the system work queue.
+ */
+static inline void gxp_mailbox_handle_irq(struct gxp_mailbox *mailbox)
+{
+ queue_work(mailbox->response_wq, &mailbox->response_work);
+}
+
+static inline void
+gxp_mailbox_handle_debug_dump_irq(struct gxp_mailbox *mailbox)
+{
+ schedule_work(&mailbox->debug_dump_work);
+}
+
+#define _RESPONSE_WORKQUEUE_NAME(_x_) "gxp_responses_" #_x_
+#define RESPONSE_WORKQUEUE_NAME(_x_) _RESPONSE_WORKQUEUE_NAME(_x_)
+static struct gxp_mailbox *create_mailbox(struct gxp_mailbox_manager *mgr,
+ u8 core_id)
+{
+ struct gxp_mailbox *mailbox;
+
+ mailbox = kzalloc(sizeof(*mailbox), GFP_KERNEL);
+ if (!mailbox)
+ goto err_mailbox;
+
+ mailbox->core_id = core_id;
+ mailbox->gxp = mgr->gxp;
+ mailbox->csr_reg_base = mgr->get_mailbox_csr_base(mgr->gxp, core_id);
+ mailbox->data_reg_base = mgr->get_mailbox_data_base(mgr->gxp, core_id);
+
+ /* Allocate and initialize the command queue */
+ mailbox->cmd_queue = (struct gxp_command *)gxp_dma_alloc_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_command) * MBOX_CMD_QUEUE_NUM_ENTRIES,
+ &(mailbox->cmd_queue_device_addr), GFP_KERNEL, 0);
+ if (!mailbox->cmd_queue)
+ goto err_cmd_queue;
+
+ mailbox->cmd_queue_size = MBOX_CMD_QUEUE_NUM_ENTRIES;
+ mailbox->cmd_queue_tail = 0;
+ mutex_init(&mailbox->cmd_queue_lock);
+
+ /* Allocate and initialize the response queue */
+ mailbox->resp_queue = (struct gxp_response *)gxp_dma_alloc_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_response) * MBOX_RESP_QUEUE_NUM_ENTRIES,
+ &(mailbox->resp_queue_device_addr), GFP_KERNEL, 0);
+ if (!mailbox->resp_queue)
+ goto err_resp_queue;
+
+ mailbox->resp_queue_size = MBOX_RESP_QUEUE_NUM_ENTRIES;
+ mailbox->resp_queue_head = 0;
+ mutex_init(&mailbox->resp_queue_lock);
+
+ /* Allocate and initialize the mailbox descriptor */
+ mailbox->descriptor =
+ (struct gxp_mailbox_descriptor *)gxp_dma_alloc_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_mailbox_descriptor),
+ &(mailbox->descriptor_device_addr), GFP_KERNEL, 0);
+ if (!mailbox->descriptor)
+ goto err_descriptor;
+
+ mailbox->descriptor->cmd_queue_device_addr =
+ mailbox->cmd_queue_device_addr;
+ mailbox->descriptor->resp_queue_device_addr =
+ mailbox->resp_queue_device_addr;
+ mailbox->descriptor->cmd_queue_size = mailbox->cmd_queue_size;
+ mailbox->descriptor->resp_queue_size = mailbox->resp_queue_size;
+
+ mailbox->response_wq =
+ create_singlethread_workqueue(RESPONSE_WORKQUEUE_NAME(i));
+ if (!mailbox->response_wq)
+ goto err_workqueue;
+
+ return mailbox;
+
+err_workqueue:
+ gxp_dma_free_coherent(mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_mailbox_descriptor),
+ mailbox->descriptor,
+ mailbox->descriptor_device_addr);
+err_descriptor:
+ gxp_dma_free_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_response) * mailbox->resp_queue_size,
+ mailbox->resp_queue, mailbox->resp_queue_device_addr);
+err_resp_queue:
+ gxp_dma_free_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_command) * mailbox->cmd_queue_size,
+ mailbox->cmd_queue, mailbox->cmd_queue_device_addr);
+err_cmd_queue:
+ kfree(mailbox);
+err_mailbox:
+ return ERR_PTR(-ENOMEM);
+}
+
+static void enable_mailbox(struct gxp_mailbox *mailbox)
+{
+ gxp_mailbox_driver_init(mailbox);
+ gxp_mailbox_write_descriptor(mailbox, mailbox->descriptor_device_addr);
+ gxp_mailbox_write_cmd_queue_head(mailbox, 0);
+ gxp_mailbox_write_cmd_queue_tail(mailbox, 0);
+ gxp_mailbox_write_resp_queue_head(mailbox, 0);
+ gxp_mailbox_write_resp_queue_tail(mailbox, 0);
+
+ mailbox->handle_irq = gxp_mailbox_handle_irq;
+ mailbox->cur_seq = 0;
+ init_waitqueue_head(&mailbox->wait_list_waitq);
+ INIT_LIST_HEAD(&mailbox->wait_list);
+ mutex_init(&mailbox->wait_list_lock);
+ INIT_WORK(&mailbox->response_work, gxp_mailbox_consume_responses_work);
+
+ /* Enable the mailbox */
+ gxp_mailbox_write_status(mailbox, 1);
+ /* TODO(b/190868834) define interrupt bits */
+ gxp_mailbox_generate_device_interrupt(mailbox, BIT(0));
+}
+
+struct gxp_mailbox *gxp_mailbox_alloc(struct gxp_mailbox_manager *mgr,
+ u8 core_id)
+{
+ struct gxp_mailbox *mailbox;
+ unsigned long flags;
+
+ /* Allocate a mailbox before locking */
+ mailbox = create_mailbox(mgr, core_id);
+ if (IS_ERR(mailbox))
+ return mailbox;
+
+ write_lock_irqsave(&mgr->mailboxes_lock, flags);
+
+ if (mgr->mailboxes[core_id])
+ goto busy;
+ else
+ mgr->mailboxes[core_id] = mailbox;
+
+ write_unlock_irqrestore(&mgr->mailboxes_lock, flags);
+
+ /* Once we've confirmed the mailbox will be used, enable it */
+ enable_mailbox(mailbox);
+
+ return mailbox;
+
+busy:
+ write_unlock_irqrestore(&mgr->mailboxes_lock, flags);
+
+ gxp_dma_free_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_command) * mailbox->cmd_queue_size,
+ mailbox->cmd_queue, mailbox->cmd_queue_device_addr);
+ gxp_dma_free_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_response) * mailbox->resp_queue_size,
+ mailbox->resp_queue, mailbox->resp_queue_device_addr);
+ gxp_dma_free_coherent(mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_mailbox_descriptor),
+ mailbox->descriptor,
+ mailbox->descriptor_device_addr);
+ destroy_workqueue(mailbox->response_wq);
+ kfree(mailbox);
+
+ return ERR_PTR(-EBUSY);
+}
+
+void gxp_mailbox_release(struct gxp_mailbox_manager *mgr,
+ struct gxp_mailbox *mailbox)
+{
+ unsigned long flags;
+
+ if (!mailbox) {
+ dev_err(mgr->gxp->dev,
+ "Attempt to release nonexistant mailbox\n");
+ return;
+ }
+
+ /* Halt the mailbox driver */
+ gxp_mailbox_driver_exit(mailbox);
+
+ /* TODO(b/189018271) Mailbox locking is broken */
+ write_lock_irqsave(&mgr->mailboxes_lock, flags);
+
+ /* Halt and flush any traffic */
+ cancel_work_sync(&mailbox->response_work);
+ cancel_work_sync(&mailbox->debug_dump_work);
+
+ /* Reset the mailbox HW */
+ gxp_mailbox_reset_hw(mailbox);
+ mgr->mailboxes[mailbox->core_id] = NULL;
+
+ write_unlock_irqrestore(&mgr->mailboxes_lock, flags);
+
+ /* Clean up resources */
+ gxp_dma_free_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_command) * mailbox->cmd_queue_size,
+ mailbox->cmd_queue, mailbox->cmd_queue_device_addr);
+ gxp_dma_free_coherent(
+ mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_response) * mailbox->resp_queue_size,
+ mailbox->resp_queue, mailbox->resp_queue_device_addr);
+ gxp_dma_free_coherent(mailbox->gxp, BIT(mailbox->core_id),
+ sizeof(struct gxp_mailbox_descriptor),
+ mailbox->descriptor,
+ mailbox->descriptor_device_addr);
+ destroy_workqueue(mailbox->response_wq);
+ kfree(mailbox);
+
+ return;
+}
+
+void gxp_mailbox_reset(struct gxp_mailbox *mailbox)
+{
+ dev_notice(mailbox->gxp->dev, "%s not yet implemented\n", __func__);
+}
+
+/*
+ * Adds @resp to @mailbox->wait_list.
+ *
+ * wait_list is a FIFO queue, with sequence number in increasing order.
+ *
+ * Returns 0 on success, or -ENOMEM if failed on allocation.
+ */
+static int gxp_mailbox_push_wait_resp(struct gxp_mailbox *mailbox,
+ struct gxp_response *resp, bool is_async)
+{
+ struct gxp_mailbox_wait_list *entry =
+ kzalloc(sizeof(*entry), GFP_KERNEL);
+
+ if (!entry)
+ return -ENOMEM;
+ entry->resp = resp;
+ entry->is_async = is_async;
+ mutex_lock(&mailbox->wait_list_lock);
+ list_add_tail(&entry->list, &mailbox->wait_list);
+ mutex_unlock(&mailbox->wait_list_lock);
+
+ return 0;
+}
+
+/*
+ * Removes the response previously pushed with gxp_mailbox_push_wait_resp().
+ *
+ * This is used when the kernel gives up waiting for the response.
+ */
+static void gxp_mailbox_del_wait_resp(struct gxp_mailbox *mailbox,
+ struct gxp_response *resp)
+{
+ struct gxp_mailbox_wait_list *cur;
+
+ mutex_lock(&mailbox->wait_list_lock);
+
+ list_for_each_entry(cur, &mailbox->wait_list, list) {
+ if (cur->resp->seq > resp->seq) {
+ /*
+ * Sequence numbers in wait_list are in increasing
+ * order. This case implies no entry in the list
+ * matches @resp's sequence number.
+ */
+ break;
+ }
+ if (cur->resp->seq == resp->seq) {
+ list_del(&cur->list);
+ kfree(cur);
+ break;
+ }
+ }
+
+ mutex_unlock(&mailbox->wait_list_lock);
+}
+
+static int gxp_mailbox_enqueue_cmd(struct gxp_mailbox *mailbox,
+ struct gxp_command *cmd,
+ struct gxp_response *resp,
+ bool resp_is_async)
+{
+ int ret;
+ u32 tail;
+
+ mutex_lock(&mailbox->cmd_queue_lock);
+
+ cmd->seq = mailbox->cur_seq;
+ /*
+ * The lock ensures mailbox->cmd_queue_tail cannot be changed by
+ * other processes (this method should be the only one to modify the
+ * value of tail), therefore we can remember its value here and use it
+ * in various places below.
+ */
+ tail = mailbox->cmd_queue_tail;
+
+ /*
+ * If the cmd queue is full, it's up to the caller to retry.
+ */
+ if (gxp_mailbox_read_cmd_queue_head(mailbox) ==
+ (tail ^ CIRCULAR_QUEUE_WRAP_BIT)) {
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ if (resp) {
+ /*
+ * Add @resp to the wait_list only if the cmd can be pushed
+ * successfully.
+ */
+ resp->seq = cmd->seq;
+ resp->status = GXP_RESP_WAITING;
+ ret = gxp_mailbox_push_wait_resp(mailbox, resp, resp_is_async);
+ if (ret)
+ goto out;
+ }
+ /* size of cmd_queue is a multiple of sizeof(*cmd) */
+ memcpy(mailbox->cmd_queue + CIRCULAR_QUEUE_REAL_INDEX(tail), cmd,
+ sizeof(*cmd));
+ gxp_mailbox_inc_cmd_queue_tail(mailbox, 1);
+ /* triggers doorbell */
+ /* TODO(b/190868834) define interrupt bits */
+ gxp_mailbox_generate_device_interrupt(mailbox, BIT(0));
+ /* bumps sequence number after the command is sent */
+ mailbox->cur_seq++;
+ ret = 0;
+out:
+ mutex_unlock(&mailbox->cmd_queue_lock);
+ if (ret)
+ dev_err(mailbox->gxp->dev, "%s: ret=%d", __func__, ret);
+
+ return ret;
+}
+
+int gxp_mailbox_execute_cmd(struct gxp_mailbox *mailbox,
+ struct gxp_command *cmd, struct gxp_response *resp)
+{
+ int ret;
+
+ ret = gxp_mailbox_enqueue_cmd(mailbox, cmd, resp,
+ /* resp_is_async = */ false);
+ if (ret)
+ return ret;
+ ret = wait_event_timeout(mailbox->wait_list_waitq,
+ resp->status != GXP_RESP_WAITING,
+ msecs_to_jiffies(MAILBOX_TIMEOUT));
+ if (!ret) {
+ dev_notice(mailbox->gxp->dev, "%s: event wait timeout",
+ __func__);
+ gxp_mailbox_del_wait_resp(mailbox, resp);
+ return -ETIMEDOUT;
+ }
+ if (resp->status != GXP_RESP_OK) {
+ dev_notice(mailbox->gxp->dev, "%s: resp status=%u", __func__,
+ resp->status);
+ return -ENOMSG;
+ }
+
+ return resp->retval;
+}
+
+static void async_cmd_timeout_work(struct work_struct *work)
+{
+ struct gxp_async_response *async_resp = container_of(
+ work, struct gxp_async_response, timeout_work.work);
+ unsigned long flags;
+
+ /*
+ * This function will acquire the mailbox wait_list_lock. This means if
+ * response processing is in progress, it will complete before this
+ * response can be removed from the wait list.
+ *
+ * Once this function has the wait_list_lock, no future response
+ * processing will begin until this response has been removed.
+ */
+ gxp_mailbox_del_wait_resp(async_resp->mailbox, &async_resp->resp);
+
+ /*
+ * Check if this response still has a valid destination queue, in case
+ * an in-progress call to `gxp_mailbox_handle_response()` completed
+ * the response while `gxp_mailbox_del_wait_resp()` was waiting for
+ * the wait_list_lock.
+ */
+ spin_lock_irqsave(async_resp->dest_queue_lock, flags);
+ if (async_resp->dest_queue) {
+ list_add_tail(&async_resp->list_entry, async_resp->dest_queue);
+ spin_unlock_irqrestore(async_resp->dest_queue_lock, flags);
+ wake_up(async_resp->dest_queue_waitq);
+ } else {
+ spin_unlock_irqrestore(async_resp->dest_queue_lock, flags);
+ }
+}
+
+int gxp_mailbox_execute_cmd_async(struct gxp_mailbox *mailbox,
+ struct gxp_command *cmd,
+ struct list_head *resp_queue,
+ spinlock_t *queue_lock,
+ wait_queue_head_t *queue_waitq)
+{
+ struct gxp_async_response *async_resp;
+ int ret;
+
+ async_resp = kzalloc(sizeof(*async_resp), GFP_KERNEL);
+ if (!async_resp)
+ return -ENOMEM;
+
+ async_resp->mailbox = mailbox;
+ async_resp->dest_queue = resp_queue;
+ async_resp->dest_queue_lock = queue_lock;
+ async_resp->dest_queue_waitq = queue_waitq;
+
+ INIT_DELAYED_WORK(&async_resp->timeout_work, async_cmd_timeout_work);
+ schedule_delayed_work(&async_resp->timeout_work,
+ msecs_to_jiffies(MAILBOX_TIMEOUT));
+
+ ret = gxp_mailbox_enqueue_cmd(mailbox, cmd, &async_resp->resp,
+ /* resp_is_async = */ true);
+ if (ret)
+ goto err_free_resp;
+
+ return 0;
+
+err_free_resp:
+ cancel_delayed_work_sync(&async_resp->timeout_work);
+ kfree(async_resp);
+ return ret;
+}
+
+void gxp_mailbox_register_debug_handler(struct gxp_mailbox *mailbox,
+ void (*debug_dump_process)
+ (struct work_struct *work),
+ u32 debug_dump_int_mask)
+{
+ mailbox->handle_debug_dump_irq = gxp_mailbox_handle_debug_dump_irq;
+ mailbox->debug_dump_int_mask = debug_dump_int_mask;
+
+ INIT_WORK(&mailbox->debug_dump_work, debug_dump_process);
+}
diff --git a/gxp-mailbox.h b/gxp-mailbox.h
new file mode 100644
index 0000000..d2cfee4
--- /dev/null
+++ b/gxp-mailbox.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP mailbox interface.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_MAILBOX_H__
+#define __GXP_MAILBOX_H__
+
+#include "gxp-internal.h"
+
+/* Command/Response Structures */
+
+enum gxp_mailbox_command_code {
+ GXP_MBOX_CODE_DISPATCH = 0,
+ GXP_MBOX_CODE_COREDUMP = 1,
+ GXP_MBOX_CODE_PINGPONG = 2,
+};
+
+/* Basic Buffer descriptor struct for message payloads. */
+struct buffer_descriptor {
+ /* Address in the device's virtual address space. */
+ u64 address;
+ /* Size in bytes. */
+ u32 size;
+ /* Flags can be used to indicate message type, etc. */
+ u32 flags;
+};
+
+/* Format used for mailbox command queues. */
+struct gxp_command {
+ /* Sequence number. Should match the corresponding response. */
+ u64 seq;
+ /*
+ * Identifies the type of command.
+ * Should be a value from `gxp_mailbox_command_code`
+ */
+ u16 code;
+ /*
+ * Priority level from 0 to 99, with 0 being the highest. Pending
+ * commands with higher priorities will be executed before lower
+ * priority ones.
+ */
+ u8 priority;
+ /*
+ * Insert spaces to make padding explicit. This does not affect
+ * alignment.
+ */
+ u8 reserved[5];
+ /* Struct describing the buffer containing the message payload */
+ struct buffer_descriptor buffer_descriptor;
+};
+
+/* Format used for mailbox response queues from kernel. */
+struct gxp_response {
+ /* Sequence number. Should match the corresponding command. */
+ u64 seq;
+ /* The status code. Either SUCCESS or an error. */
+ u16 status;
+ /* Padding. */
+ u16 reserved;
+ /* Return value, dependent on the command this responds to. */
+ u32 retval;
+};
+
+/*
+ * Wrapper struct for responses consumed by a thread other than the one which
+ * sent the command.
+ */
+struct gxp_async_response {
+ struct list_head list_entry;
+ struct gxp_response resp;
+ struct delayed_work timeout_work;
+ /*
+ * If this response times out, this pointer to the owning mailbox is
+ * needed to delete this response from the list of pending responses.
+ */
+ struct gxp_mailbox *mailbox;
+ /* Queue to add the response to once it is complete or timed out */
+ struct list_head *dest_queue;
+ spinlock_t *dest_queue_lock;
+ wait_queue_head_t *dest_queue_waitq;
+};
+
+enum gxp_response_status {
+ GXP_RESP_OK = 0,
+ GXP_RESP_WAITING = 1,
+ GXP_RESP_CANCELLED = 2,
+};
+
+struct gxp_mailbox_wait_list {
+ struct list_head list;
+ struct gxp_response *resp;
+ bool is_async;
+};
+
+/* Mailbox Structures */
+struct gxp_mailbox_descriptor {
+ u64 cmd_queue_device_addr;
+ u64 resp_queue_device_addr;
+ u32 cmd_queue_size;
+ u32 resp_queue_size;
+};
+
+struct gxp_mailbox {
+ uint core_id;
+ struct gxp_dev *gxp;
+ void __iomem *csr_reg_base;
+ void __iomem *data_reg_base;
+
+ void (*handle_irq)(struct gxp_mailbox *mailbox);
+ void (*handle_debug_dump_irq)(struct gxp_mailbox *mailbox);
+ unsigned int interrupt_virq;
+ u32 debug_dump_int_mask;
+
+ u64 cur_seq;
+
+ struct gxp_mailbox_descriptor *descriptor;
+ dma_addr_t descriptor_device_addr;
+
+ struct gxp_command *cmd_queue;
+ u32 cmd_queue_size; /* size of cmd queue */
+ u32 cmd_queue_tail; /* offset within the cmd queue */
+ dma_addr_t cmd_queue_device_addr; /* device address for cmd queue */
+ struct mutex cmd_queue_lock; /* protects cmd_queue */
+
+ struct gxp_response *resp_queue;
+ u32 resp_queue_size; /* size of resp queue */
+ u32 resp_queue_head; /* offset within the resp queue */
+ dma_addr_t resp_queue_device_addr; /* device address for resp queue */
+ struct mutex resp_queue_lock; /* protects resp_queue */
+
+ /* add to this list if a command needs to wait for a response */
+ struct list_head wait_list;
+ struct mutex wait_list_lock; /* protects wait_list */
+ /* queue for waiting for the wait_list to be consumed */
+ wait_queue_head_t wait_list_waitq;
+ struct workqueue_struct *response_wq;
+ struct work_struct response_work;
+ struct work_struct debug_dump_work;
+ struct task_struct *to_host_poll_task;
+};
+
+typedef void __iomem *(*get_mailbox_base_t)(struct gxp_dev *gxp, uint index);
+
+struct gxp_mailbox_manager {
+ struct gxp_dev *gxp;
+ u8 num_cores;
+ rwlock_t mailboxes_lock;
+ struct gxp_mailbox **mailboxes;
+ get_mailbox_base_t get_mailbox_csr_base;
+ get_mailbox_base_t get_mailbox_data_base;
+};
+
+/* Mailbox APIs */
+
+struct gxp_mailbox_manager *gxp_mailbox_create_manager(struct gxp_dev *gxp,
+ uint num_cores);
+
+struct gxp_mailbox *gxp_mailbox_alloc(struct gxp_mailbox_manager *mgr,
+ u8 core_id);
+void gxp_mailbox_release(struct gxp_mailbox_manager *mgr,
+ struct gxp_mailbox *mailbox);
+
+void gxp_mailbox_reset(struct gxp_mailbox *mailbox);
+
+int gxp_mailbox_execute_cmd(struct gxp_mailbox *mailbox,
+ struct gxp_command *cmd, struct gxp_response *resp);
+
+int gxp_mailbox_execute_cmd_async(struct gxp_mailbox *mailbox,
+ struct gxp_command *cmd,
+ struct list_head *resp_queue,
+ spinlock_t *queue_lock,
+ wait_queue_head_t *queue_waitq);
+
+void gxp_mailbox_register_debug_handler(struct gxp_mailbox *mailbox,
+ void (*debug_dump_process)
+ (struct work_struct *work),
+ u32 debug_dump_int_mask);
+
+#endif /* __GXP_MAILBOX_H__ */
diff --git a/gxp-mapping.c b/gxp-mapping.c
new file mode 100644
index 0000000..95873c0
--- /dev/null
+++ b/gxp-mapping.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Records the mapped device addresses.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include "gxp-dma.h"
+#include "gxp-internal.h"
+#include "gxp-mapping.h"
+#include "mm-backport.h"
+
+int gxp_mapping_init(struct gxp_dev *gxp)
+{
+ gxp->mappings =
+ devm_kzalloc(gxp->dev, sizeof(*gxp->mappings), GFP_KERNEL);
+ if (!gxp->mappings)
+ return -ENOMEM;
+
+ gxp->mappings->rb = RB_ROOT;
+ mutex_init(&gxp->mappings->lock);
+
+ return 0;
+}
+
+struct gxp_mapping *gxp_mapping_create(struct gxp_dev *gxp, uint core_list,
+ u64 user_address, size_t size, u32 flags,
+ enum dma_data_direction dir)
+{
+ struct gxp_mapping *mapping = NULL;
+ uint num_pages = 0;
+ struct page **pages;
+ ulong offset;
+ int ret, i;
+ struct vm_area_struct *vma;
+ unsigned int foll_flags = FOLL_LONGTERM | FOLL_WRITE;
+
+ /*
+ * The host pages might be read-only and could fail if we attempt to pin
+ * it with FOLL_WRITE.
+ * default to read/write if find_extend_vma returns NULL
+ */
+ vma = find_extend_vma(current->mm, user_address & PAGE_MASK);
+ if (vma && !(vma->vm_flags & VM_WRITE)) {
+ foll_flags &= ~FOLL_WRITE;
+ if (dir != DMA_TO_DEVICE) {
+ dev_err(gxp->dev,
+ "Unable to map read-only pages as anything but DMA_TO_DEVICE\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ /* Pin the user pages */
+ offset = user_address & (PAGE_SIZE - 1);
+ if (unlikely((size + offset) / PAGE_SIZE >= UINT_MAX - 1 ||
+ size + offset < size))
+ return ERR_PTR(-ENOMEM);
+ num_pages = (size + offset) / PAGE_SIZE;
+ if ((size + offset) % PAGE_SIZE)
+ num_pages++;
+
+ pages = kcalloc(num_pages, sizeof(*pages), GFP_KERNEL);
+ if (!pages)
+ return ERR_PTR(-ENOMEM);
+
+ /* Provide protection around `pin_user_pages_fast` since it fails if
+ * called by more than one thread simultaneously.
+ */
+ mutex_lock(&gxp->mappings->lock);
+ ret = pin_user_pages_fast(user_address & PAGE_MASK, num_pages,
+ foll_flags, pages);
+ mutex_unlock(&gxp->mappings->lock);
+ if (ret < 0 || ret < num_pages) {
+ dev_dbg(gxp->dev,
+ "Get user pages failed: user_add=%pK, num_pages=%u, ret=%d\n",
+ (void *)user_address, num_pages, ret);
+ num_pages = ret < 0 ? 0 : ret;
+ ret = ret >= 0 ? -EFAULT : ret;
+ goto error_unpin_pages;
+ }
+
+ /* Initialize mapping book-keeping */
+ mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
+ if (!mapping) {
+ ret = -ENOMEM;
+ goto error_unpin_pages;
+ }
+ mapping->host_address = user_address;
+ mapping->core_list = core_list;
+ mapping->size = size;
+ mapping->map_count = 1;
+ mapping->gxp_dma_flags = flags;
+ mapping->dir = dir;
+ ret = sg_alloc_table_from_pages(&mapping->sgt, pages, num_pages, 0,
+ num_pages * PAGE_SIZE, GFP_KERNEL);
+ if (ret) {
+ dev_dbg(gxp->dev, "Failed to alloc sgt for mapping (ret=%d)\n",
+ ret);
+ goto error_free_sgt;
+ }
+
+ /* map the user pages */
+ ret = gxp_dma_map_sg(gxp, mapping->core_list, mapping->sgt.sgl,
+ mapping->sgt.nents, mapping->dir,
+ DMA_ATTR_SKIP_CPU_SYNC, mapping->gxp_dma_flags);
+ if (!ret) {
+ dev_dbg(gxp->dev, "Failed to map sgt (ret=%d)\n", ret);
+ ret = -EINVAL;
+ goto error_free_sgt;
+ }
+ mapping->sgt.nents = ret;
+ mapping->device_address =
+ sg_dma_address(mapping->sgt.sgl) + offset;
+
+ kfree(pages);
+ return mapping;
+
+error_free_sgt:
+ sg_free_table(&mapping->sgt);
+ kfree(mapping);
+error_unpin_pages:
+ for (i = 0; i < num_pages; i++)
+ unpin_user_page(pages[i]);
+ kfree(pages);
+
+ return ERR_PTR(ret);
+}
+
+void gxp_mapping_destroy(struct gxp_dev *gxp, struct gxp_mapping *mapping)
+{
+ struct sg_page_iter sg_iter;
+ struct page *page;
+
+ /*
+ * Unmap the user pages
+ *
+ * Normally on unmap, the entire mapping is synced back to the CPU.
+ * Since mappings are made at a page granularity regardless of the
+ * underlying buffer's size, they can cover other data as well. If a
+ * user requires a mapping be synced before unmapping, they are
+ * responsible for calling `gxp_mapping_sync()` before hand.
+ */
+ gxp_dma_unmap_sg(gxp, mapping->core_list, mapping->sgt.sgl,
+ mapping->sgt.orig_nents, mapping->dir,
+ DMA_ATTR_SKIP_CPU_SYNC);
+
+ /* Unpin the user pages */
+ for_each_sg_page(mapping->sgt.sgl, &sg_iter, mapping->sgt.orig_nents,
+ 0) {
+ page = sg_page_iter_page(&sg_iter);
+ if (mapping->dir == DMA_FROM_DEVICE ||
+ mapping->dir == DMA_BIDIRECTIONAL) {
+ set_page_dirty(page);
+ }
+
+ unpin_user_page(page);
+ }
+
+ /* Free the mapping book-keeping */
+ sg_free_table(&mapping->sgt);
+ kfree(mapping);
+}
+
+int gxp_mapping_sync(struct gxp_dev *gxp, struct gxp_mapping *mapping,
+ u32 offset, u32 size, bool for_cpu)
+{
+ struct scatterlist *sg, *start_sg = NULL, *end_sg = NULL;
+ int nelems = 0, cur_offset = 0, ret = 0, i;
+ u64 start, end;
+ unsigned int start_diff = 0, end_diff = 0;
+
+ /*
+ * Valid input requires
+ * - size > 0 (offset + size != offset)
+ * - offset + size does not overflow (offset + size > offset)
+ * - the mapped range falls within [0 : mapping->size]
+ */
+ if (offset + size <= offset ||
+ offset + size > mapping->size)
+ return -EINVAL;
+
+ /*
+ * Mappings are created at a PAGE_SIZE granularity, however other data
+ * which is not part of the mapped buffer may be present in the first
+ * and last pages of the buffer's scattergather list.
+ *
+ * To ensure only the intended data is actually synced, iterate through
+ * the scattergather list, to find the first and last `scatterlist`s
+ * which contain the range of the buffer to sync.
+ *
+ * After those links are found, change their offset/lengths so that
+ * `dma_map_sg_for_*()` will only sync the requested region.
+ */
+ start = (mapping->host_address & ~PAGE_MASK) + offset;
+ end = start + size;
+ for_each_sg(mapping->sgt.sgl, sg, mapping->sgt.orig_nents, i) {
+ if (end <= cur_offset)
+ break;
+ if (cur_offset <= start && start < cur_offset + sg->length) {
+ start_sg = sg;
+ start_diff = start - cur_offset;
+ }
+ if (start_sg)
+ nelems++;
+ cur_offset += sg->length;
+ end_sg = sg;
+ }
+ end_diff = cur_offset - end;
+
+ /* Make sure a valid starting scatterlist was found for the start */
+ if (!start_sg)
+ return -EINVAL;
+
+ /*
+ * Since the scatter-gather list of the mapping is modified while it is
+ * being synced, only one sync for a given mapping can occur at a time.
+ * Rather than maintain a mutex for every mapping, lock the mapping list
+ * mutex, making all syncs mutually exclusive.
+ */
+ mutex_lock(&gxp->mappings->lock);
+
+ start_sg->offset += start_diff;
+ start_sg->dma_address += start_diff;
+ start_sg->length -= start_diff;
+ start_sg->dma_length -= start_diff;
+ end_sg->length -= end_diff;
+ end_sg->dma_length -= end_diff;
+
+ if (for_cpu)
+ gxp_dma_sync_sg_for_cpu(gxp, start_sg, nelems, mapping->dir);
+ else
+ gxp_dma_sync_sg_for_device(gxp, start_sg, nelems, mapping->dir);
+
+ /*
+ * Return the start and end scatterlists' offset/lengths to their
+ * original values for the next time they need to be synced/unmapped.
+ */
+ end_sg->length += end_diff;
+ end_sg->dma_length += end_diff;
+ start_sg->offset -= start_diff;
+ start_sg->dma_address -= start_diff;
+ start_sg->length += start_diff;
+ start_sg->dma_length += start_diff;
+
+ mutex_unlock(&gxp->mappings->lock);
+
+ return ret;
+}
+
+int gxp_mapping_put(struct gxp_dev *gxp, struct gxp_mapping *map)
+{
+ struct rb_node **link;
+ struct rb_node *parent = NULL;
+ u64 device_address = map->device_address;
+ struct gxp_mapping *this;
+
+ link = &gxp->mappings->rb.rb_node;
+
+ mutex_lock(&gxp->mappings->lock);
+
+ /* Figure out where to put new node */
+ while (*link) {
+ parent = *link;
+ this = rb_entry(parent, struct gxp_mapping, node);
+
+ if (this->device_address > device_address)
+ link = &(*link)->rb_left;
+ else if (this->device_address < device_address)
+ link = &(*link)->rb_right;
+ else
+ goto out;
+ }
+
+ /* Add new node and rebalance tree. */
+ rb_link_node(&map->node, parent, link);
+ rb_insert_color(&map->node, &gxp->mappings->rb);
+
+ mutex_unlock(&gxp->mappings->lock);
+
+ return 0;
+
+out:
+ mutex_unlock(&gxp->mappings->lock);
+ dev_err(gxp->dev, "Duplicate mapping: 0x%llx", map->device_address);
+ return -EINVAL;
+}
+
+struct gxp_mapping *gxp_mapping_get(struct gxp_dev *gxp, u64 device_address)
+{
+ struct rb_node *node;
+ struct gxp_mapping *this;
+
+ mutex_lock(&gxp->mappings->lock);
+
+ node = gxp->mappings->rb.rb_node;
+
+ while (node) {
+ this = rb_entry(node, struct gxp_mapping, node);
+
+ if (this->device_address > device_address) {
+ node = node->rb_left;
+ } else if (this->device_address < device_address) {
+ node = node->rb_right;
+ } else {
+ mutex_unlock(&gxp->mappings->lock);
+ return this; /* Found it */
+ }
+ }
+
+ mutex_unlock(&gxp->mappings->lock);
+
+ dev_err(gxp->dev, "Mapping not found: 0x%llx", device_address);
+ return NULL;
+}
+
+struct gxp_mapping *gxp_mapping_get_host(struct gxp_dev *gxp, u64 host_address)
+{
+ struct rb_node *node;
+ struct gxp_mapping *this;
+
+ mutex_lock(&gxp->mappings->lock);
+
+ /* Iterate through the elements in the rbtree */
+ for (node = rb_first(&gxp->mappings->rb); node; node = rb_next(node)) {
+ this = rb_entry(node, struct gxp_mapping, node);
+ if (this->host_address == host_address) {
+ mutex_unlock(&gxp->mappings->lock);
+ return this;
+ }
+ }
+
+ mutex_unlock(&gxp->mappings->lock);
+
+ return NULL;
+}
+
+void gxp_mapping_remove(struct gxp_dev *gxp, struct gxp_mapping *map)
+{
+ rb_erase(&map->node, &gxp->mappings->rb);
+}
diff --git a/gxp-mapping.h b/gxp-mapping.h
new file mode 100644
index 0000000..a0225ae
--- /dev/null
+++ b/gxp-mapping.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Records the mapped device addresses.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_MAPPING_H__
+#define __GXP_MAPPING_H__
+
+#include <linux/dma-direction.h>
+#include <linux/mutex.h>
+#include <linux/rbtree.h>
+#include <linux/scatterlist.h>
+#include <linux/types.h>
+
+#include "gxp-internal.h"
+
+struct gxp_mapping_root {
+ struct rb_root rb;
+ struct mutex lock;
+};
+
+struct gxp_mapping {
+ struct rb_node node;
+ u64 host_address;
+ uint core_list;
+ /*
+ * `device_address` and `size` are the base address and size of the
+ * user buffer a mapping represents.
+ *
+ * Due to alignment requirements from hardware, the actual IOVA space
+ * allocated may be larger and start at a different address, but that
+ * information is contained in the scatter-gather table, `sgt` below.
+ */
+ dma_addr_t device_address;
+ size_t size;
+ uint gxp_dma_flags;
+ enum dma_data_direction dir;
+ struct sg_table sgt;
+ u32 map_count;
+};
+
+int gxp_mapping_init(struct gxp_dev *gxp);
+struct gxp_mapping *gxp_mapping_create(struct gxp_dev *gxp, uint core_list,
+ u64 user_address, size_t size, u32 flags,
+ enum dma_data_direction dir);
+void gxp_mapping_destroy(struct gxp_dev *gxp, struct gxp_mapping *mapping);
+int gxp_mapping_sync(struct gxp_dev *gxp, struct gxp_mapping *mapping,
+ u32 offset, u32 size, bool for_cpu);
+int gxp_mapping_put(struct gxp_dev *gxp, struct gxp_mapping *map);
+struct gxp_mapping *gxp_mapping_get(struct gxp_dev *gxp, u64 device_address);
+struct gxp_mapping *gxp_mapping_get_host(struct gxp_dev *gxp, u64 host_address);
+void gxp_mapping_remove(struct gxp_dev *gxp, struct gxp_mapping *map);
+
+#endif /* __GXP_MAPPING_H__ */
diff --git a/gxp-platform.c b/gxp-platform.c
new file mode 100644
index 0000000..f8eaa5c
--- /dev/null
+++ b/gxp-platform.c
@@ -0,0 +1,700 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Platform device driver for GXP.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#ifdef CONFIG_ANDROID
+#include <linux/platform_data/sscoredump.h>
+#endif
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/genalloc.h>
+#include <linux/kthread.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/uaccess.h>
+
+#include "gxp.h"
+#include "gxp-debug-dump.h"
+#include "gxp-debugfs.h"
+#include "gxp-dma.h"
+#include "gxp-firmware.h"
+#include "gxp-firmware-data.h"
+#include "gxp-internal.h"
+#include "gxp-mailbox.h"
+#include "gxp-mailbox-driver.h"
+#include "gxp-mapping.h"
+#include "gxp-vd.h"
+
+#ifdef CONFIG_ANDROID
+static struct sscd_platform_data gxp_sscd_pdata;
+
+static void gxp_sscd_release(struct device *dev)
+{
+ pr_debug("%s\n", __func__);
+}
+
+static struct platform_device gxp_sscd_dev = {
+ .name = GXP_DRIVER_NAME,
+ .driver_override = SSCD_NAME,
+ .id = -1,
+ .dev = {
+ .platform_data = &gxp_sscd_pdata,
+ .release = gxp_sscd_release,
+ },
+};
+#endif // CONFIG_ANDROID
+
+static int gxp_open(struct inode *inode, struct file *file)
+{
+ struct gxp_client *client;
+ struct gxp_dev *gxp = container_of(file->private_data, struct gxp_dev,
+ misc_dev);
+
+ client = gxp_client_create(gxp);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ file->private_data = client;
+ return 0;
+}
+
+static int gxp_release(struct inode *inode, struct file *file)
+{
+ struct gxp_client *client = file->private_data;
+
+ /*
+ * TODO (b/184572070): Unmap buffers and drop mailbox responses
+ * belonging to the client
+ */
+ gxp_client_destroy(client);
+ return 0;
+}
+
+static inline enum dma_data_direction mapping_flags_to_dma_dir(u32 flags)
+{
+ switch (flags & 0x3) {
+ case 0x0: /* 0b00 */
+ return DMA_BIDIRECTIONAL;
+ case 0x1: /* 0b01 */
+ return DMA_TO_DEVICE;
+ case 0x2: /* 0b10 */
+ return DMA_FROM_DEVICE;
+ }
+
+ return DMA_NONE;
+}
+
+static int gxp_map_buffer(struct gxp_client *client,
+ struct gxp_map_ioctl __user *argp)
+{
+ struct gxp_dev *gxp = client->gxp;
+ struct gxp_map_ioctl ibuf;
+ struct gxp_mapping *map;
+ int ret = 0;
+ uint phys_core_list;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ phys_core_list = gxp_vd_virt_core_list_to_phys_core_list(
+ client, ibuf.virtual_core_list);
+ if (phys_core_list == 0)
+ return -EINVAL;
+
+ if (ibuf.size == 0)
+ return -EINVAL;
+
+ if (ibuf.host_address % L1_CACHE_BYTES || ibuf.size % L1_CACHE_BYTES) {
+ dev_err(gxp->dev,
+ "Mapped buffers must be cache line aligned and padded.\n");
+ return -EINVAL;
+ }
+
+#ifndef CONFIG_GXP_HAS_SYSMMU
+ /*
+ * TODO(b/193272602) On systems without a SysMMU, all attempts to map
+ * the same buffer must use the same mapping/bounce buffer or cores
+ * may corrupt each others' updates to the buffer. Once mirror mapping
+ * is supported, and a buffer can be mapped to multiple cores at once,
+ * attempting to remap a buffer can be considered an error and this
+ * check removed.
+ */
+ /* Check if this buffer has already been mapped */
+ map = gxp_mapping_get_host(gxp, ibuf.host_address);
+ if (map) {
+ ibuf.device_address = map->device_address;
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
+ return -EFAULT;
+
+ map->map_count++;
+ return ret;
+ }
+#endif
+
+ map = gxp_mapping_create(gxp, phys_core_list, ibuf.host_address,
+ ibuf.size, /*gxp_dma_flags=*/0,
+ mapping_flags_to_dma_dir(ibuf.flags));
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ ret = gxp_mapping_put(gxp, map);
+ if (ret)
+ goto error_destroy;
+
+ ibuf.device_address = map->device_address;
+
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf))) {
+ ret = -EFAULT;
+ goto error_remove;
+ }
+
+ return ret;
+
+error_remove:
+ gxp_mapping_remove(gxp, map);
+error_destroy:
+ gxp_mapping_destroy(gxp, map);
+ devm_kfree(gxp->dev, (void *)map);
+ return ret;
+}
+
+static int gxp_unmap_buffer(struct gxp_client *client,
+ struct gxp_map_ioctl __user *argp)
+{
+ struct gxp_dev *gxp = client->gxp;
+ struct gxp_map_ioctl ibuf;
+ struct gxp_mapping *map;
+ int ret = 0;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ map = gxp_mapping_get(gxp, ibuf.device_address);
+ if (!map)
+ return -EINVAL;
+
+ WARN_ON(map->host_address != ibuf.host_address);
+ if (--(map->map_count))
+ return ret;
+
+ gxp_mapping_remove(gxp, map);
+ gxp_mapping_destroy(gxp, map);
+
+ return ret;
+}
+
+static int gxp_sync_buffer(struct gxp_client *client,
+ struct gxp_sync_ioctl __user *argp)
+{
+ struct gxp_dev *gxp = client->gxp;
+ struct gxp_sync_ioctl ibuf;
+ struct gxp_mapping *map;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ map = gxp_mapping_get(gxp, ibuf.device_address);
+ if (!map)
+ return -EINVAL;
+
+ return gxp_mapping_sync(gxp, map, ibuf.offset, ibuf.size,
+ ibuf.flags == GXP_SYNC_FOR_CPU);
+}
+
+static int gxp_mailbox_command(struct gxp_client *client,
+ struct gxp_mailbox_command_ioctl __user *argp)
+{
+ struct gxp_dev *gxp = client->gxp;
+ struct gxp_mailbox_command_ioctl ibuf;
+ struct gxp_command cmd;
+ struct buffer_descriptor buffer;
+ int phys_core;
+ int ret = 0;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf))) {
+ dev_err(gxp->dev,
+ "Unable to copy ioctl data from user-space\n");
+ return -EFAULT;
+ }
+
+ phys_core = gxp_vd_virt_core_to_phys_core(client, ibuf.virtual_core_id);
+ if (phys_core < 0) {
+ dev_err(gxp->dev,
+ "Mailbox command failed: Invalid virtual core id (%u)\n",
+ ibuf.virtual_core_id);
+ return -EINVAL;
+ }
+
+ if (!gxp_is_fw_running(gxp, phys_core)) {
+ dev_err(gxp->dev,
+ "Cannot process mailbox command for core %d when firmware isn't running\n",
+ phys_core);
+ return -EINVAL;
+ }
+
+ if (gxp->mailbox_mgr == NULL || gxp->mailbox_mgr->mailboxes == NULL ||
+ gxp->mailbox_mgr->mailboxes[phys_core] == NULL) {
+ dev_err(gxp->dev, "Mailbox not initialized for core %d\n",
+ phys_core);
+ return -EIO;
+ }
+
+ /* Pack the command structure */
+ buffer.address = ibuf.device_address;
+ buffer.size = ibuf.size;
+ buffer.flags = ibuf.flags;
+ /* cmd.seq is assigned by mailbox implementation */
+ cmd.code = GXP_MBOX_CODE_DISPATCH; /* All IOCTL commands are dispatch */
+ cmd.priority = 0; /* currently unused */
+ cmd.buffer_descriptor = buffer;
+
+ ret = gxp_mailbox_execute_cmd_async(
+ gxp->mailbox_mgr->mailboxes[phys_core], &cmd,
+ &gxp->mailbox_resp_queues[phys_core], &gxp->mailbox_resps_lock,
+ &gxp->mailbox_resp_waitqs[phys_core]);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to enqueue mailbox command (ret=%d)\n",
+ ret);
+ return ret;
+ }
+
+ ibuf.sequence_number = cmd.seq;
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf))) {
+ dev_err(gxp->dev, "Failed to copy back sequence number!\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int gxp_mailbox_response(struct gxp_client *client,
+ struct gxp_mailbox_response_ioctl __user *argp)
+{
+ struct gxp_dev *gxp = client->gxp;
+ u16 virtual_core_id;
+ struct gxp_mailbox_response_ioctl ibuf;
+ struct gxp_async_response *resp_ptr;
+ int phys_core;
+ unsigned long flags;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ virtual_core_id = ibuf.virtual_core_id;
+ phys_core = gxp_vd_virt_core_to_phys_core(client, virtual_core_id);
+ if (phys_core < 0) {
+ dev_err(gxp->dev, "Mailbox response failed: Invalid virtual core id (%u)\n",
+ virtual_core_id);
+ return -EINVAL;
+ }
+
+ if (!gxp_is_fw_running(gxp, phys_core)) {
+ dev_err(gxp->dev, "Cannot process mailbox response for core %d when firmware isn't running\n",
+ phys_core);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&gxp->mailbox_resps_lock, flags);
+
+ /*
+ * No timeout is required since commands have a hard timeout after
+ * which the command is abandoned and a response with a failure
+ * status is added to the mailbox_resps queue.
+ *
+ * The "exclusive" version of wait_event is used since each wake
+ * corresponds to the addition of exactly one new response to be
+ * consumed. Therefore, only one waiting response ioctl can ever
+ * proceed per wake event.
+ */
+ wait_event_exclusive_cmd(
+ gxp->mailbox_resp_waitqs[phys_core],
+ !list_empty(&(gxp->mailbox_resp_queues[phys_core])),
+ /* Release the lock before sleeping */
+ spin_unlock_irqrestore(&gxp->mailbox_resps_lock, flags),
+ /* Reacquire the lock after waking */
+ spin_lock_irqsave(&gxp->mailbox_resps_lock, flags));
+
+ resp_ptr = list_first_entry(&(gxp->mailbox_resp_queues[phys_core]),
+ struct gxp_async_response, list_entry);
+
+ /* Pop the front of the response list */
+ list_del(&(resp_ptr->list_entry));
+
+ spin_unlock_irqrestore(&gxp->mailbox_resps_lock, flags);
+
+ ibuf.sequence_number = resp_ptr->resp.seq;
+ switch (resp_ptr->resp.status) {
+ case GXP_RESP_OK:
+ ibuf.error_code = GXP_RESPONSE_ERROR_NONE;
+ /* retval is only valid if status == GXP_RESP_OK */
+ ibuf.cmd_retval = resp_ptr->resp.retval;
+ break;
+ case GXP_RESP_CANCELLED:
+ ibuf.error_code = GXP_RESPONSE_ERROR_TIMEOUT;
+ break;
+ default:
+ /* No other status values are valid at this point */
+ WARN(true, "Completed response had invalid status %hu",
+ resp_ptr->resp.status);
+ ibuf.error_code = GXP_RESPONSE_ERROR_INTERNAL;
+ break;
+ }
+
+ /*
+ * We must be absolutely sure the timeout work has been cancelled
+ * and/or completed before freeing the `gxp_async_response`.
+ * There are 3 possible cases when we arrive at this point:
+ * 1) The response arrived normally and the timeout was cancelled
+ * 2) The response timedout and its timeout handler finished
+ * 3) The response handler and timeout handler raced, and the response
+ * handler "cancelled" the timeout handler while it was already in
+ * progress.
+ *
+ * This call handles case #3, and ensures any in-process timeout
+ * handler (which may reference the `gxp_async_response`) has
+ * been able to exit cleanly.
+ */
+ cancel_delayed_work_sync(&resp_ptr->timeout_work);
+ kfree(resp_ptr);
+
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int gxp_get_specs(struct gxp_client *client,
+ struct gxp_specs_ioctl __user *argp)
+{
+ struct gxp_specs_ioctl ibuf;
+
+ ibuf.core_count = GXP_NUM_CORES;
+ ibuf.version_major = 0;
+ ibuf.version_minor = 0;
+ ibuf.version_build = 1;
+ ibuf.threads_per_core = 1;
+ ibuf.memory_per_core = 0;
+
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int gxp_allocate_vd(struct gxp_client *client,
+ struct gxp_virtual_device_ioctl __user *argp)
+{
+ struct gxp_dev *gxp = client->gxp;
+ struct gxp_virtual_device_ioctl ibuf;
+ int ret = 0;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ if (ibuf.core_count == 0 || ibuf.core_count > GXP_NUM_CORES) {
+ dev_err(gxp->dev, "Invalid core count (%u)\n", ibuf.core_count);
+ return -EINVAL;
+ }
+
+ mutex_lock(&gxp->vd_lock);
+ if (client->vd_allocated) {
+ mutex_unlock(&gxp->vd_lock);
+ dev_err(gxp->dev, "Virtual device was already allocated for client\n");
+ return -EINVAL;
+ }
+
+ ret = gxp_vd_allocate(client, ibuf.core_count);
+ mutex_unlock(&gxp->vd_lock);
+
+ return ret;
+}
+
+static long gxp_ioctl(struct file *file, uint cmd, ulong arg)
+{
+ struct gxp_client *client = file->private_data;
+ void __user *argp = (void __user *)arg;
+ long ret;
+
+ switch (cmd) {
+ case GXP_MAP_BUFFER:
+ ret = gxp_map_buffer(client, argp);
+ break;
+ case GXP_UNMAP_BUFFER:
+ ret = gxp_unmap_buffer(client, argp);
+ break;
+ case GXP_SYNC_BUFFER:
+ ret = gxp_sync_buffer(client, argp);
+ break;
+ case GXP_MAILBOX_COMMAND:
+ ret = gxp_mailbox_command(client, argp);
+ break;
+ case GXP_MAILBOX_RESPONSE:
+ ret = gxp_mailbox_response(client, argp);
+ break;
+ case GXP_GET_SPECS:
+ ret = gxp_get_specs(client, argp);
+ break;
+ case GXP_ALLOCATE_VIRTUAL_DEVICE:
+ ret = gxp_allocate_vd(client, argp);
+ break;
+ default:
+ ret = -ENOTTY; /* unknown command */
+ }
+
+ return ret;
+}
+
+static const struct file_operations gxp_fops = {
+ .owner = THIS_MODULE,
+ .open = gxp_open,
+ .release = gxp_release,
+ .unlocked_ioctl = gxp_ioctl,
+};
+
+static int gxp_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gxp_dev *gxp;
+ struct resource *r;
+ phys_addr_t offset, base_addr;
+ struct device_node *np;
+ int ret;
+ int i __maybe_unused;
+ bool tpu_found __maybe_unused;
+
+ gxp = devm_kzalloc(dev, sizeof(*gxp), GFP_KERNEL);
+ if (!gxp)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gxp);
+ gxp->dev = dev;
+
+ gxp->misc_dev.minor = MISC_DYNAMIC_MINOR;
+ gxp->misc_dev.name = "gxp";
+ gxp->misc_dev.fops = &gxp_fops;
+
+ ret = misc_register(&gxp->misc_dev);
+ if (ret) {
+ dev_err(dev, "Failed to register misc device (ret = %d)\n",
+ ret);
+ devm_kfree(dev, (void *)gxp);
+ return ret;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(dev, "Failed to get memory resource\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ gxp->regs.paddr = r->start;
+ gxp->regs.size = resource_size(r);
+ gxp->regs.vaddr = devm_ioremap_resource(dev, r);
+ if (IS_ERR_OR_NULL(gxp->regs.vaddr)) {
+ dev_err(dev, "Failed to map registers\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+#ifdef CONFIG_GXP_CLOUDRIPPER
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret) {
+ dev_err(dev, "pm_runtime_get_sync returned %d\n", ret);
+ goto err;
+ }
+#endif
+
+#ifndef CONFIG_GXP_USE_SW_MAILBOX
+ for (i = 0; i < GXP_NUM_CORES; i++) {
+ r = platform_get_resource(pdev, IORESOURCE_MEM, i + 1);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(dev, "Failed to get mailbox%d resource\n", i);
+ ret = -ENODEV;
+ goto err;
+ }
+
+ gxp->mbx[i].paddr = r->start;
+ gxp->mbx[i].size = resource_size(r);
+ gxp->mbx[i].vaddr = devm_ioremap_resource(dev, r);
+ if (IS_ERR_OR_NULL(gxp->mbx[i].vaddr)) {
+ dev_err(dev, "Failed to map mailbox%d registers\n", i);
+ ret = -ENODEV;
+ goto err;
+ }
+ }
+
+ tpu_found = true;
+ /* Get TPU device from device tree */
+ np = of_parse_phandle(dev->of_node, "tpu-device", 0);
+ if (IS_ERR_OR_NULL(np)) {
+ dev_warn(dev, "No tpu-device in device tree\n");
+ tpu_found = false;
+ }
+ /* get tpu mailbox register base */
+ ret = of_property_read_u64_index(np, "reg", 0, &base_addr);
+ of_node_put(np);
+ if (ret) {
+ dev_warn(dev, "Unable to get tpu-device base address\n");
+ tpu_found = false;
+ }
+ /* get gxp-tpu mailbox register offset */
+ ret = of_property_read_u64(dev->of_node, "gxp-tpu-mbx-offset",
+ &offset);
+ if (ret) {
+ dev_warn(dev, "Unable to get tpu-device mailbox offset\n");
+ tpu_found = false;
+ }
+ if (tpu_found) {
+ gxp->tpu_dev.mbx_paddr = base_addr + offset;
+ } else {
+ dev_warn(dev, "TPU will not be available for interop\n");
+ gxp->tpu_dev.mbx_paddr = 0;
+ }
+#endif // !CONFIG_GXP_USE_SW_MAILBOX
+
+ ret = gxp_dma_init(gxp);
+ if (ret) {
+ dev_err(dev, "Failed to initialize GXP DMA interface\n");
+ goto err;
+ }
+
+ gxp->mailbox_mgr = gxp_mailbox_create_manager(gxp, GXP_NUM_CORES);
+ if (IS_ERR_OR_NULL(gxp->mailbox_mgr)) {
+ dev_err(dev, "Failed to create mailbox manager\n");
+ ret = -ENOMEM;
+ goto err_dma_exit;
+ }
+ spin_lock_init(&gxp->mailbox_resps_lock);
+
+#ifdef CONFIG_ANDROID
+ ret = gxp_debug_dump_init(gxp, &gxp_sscd_dev, &gxp_sscd_pdata);
+#else
+ ret = gxp_debug_dump_init(gxp, NULL, NULL);
+#endif // !CONFIG_ANDROID
+ if (ret) {
+ dev_err(dev, "Failed to initialize debug dump\n");
+ gxp_debug_dump_exit(gxp);
+ }
+
+ ret = gxp_mapping_init(gxp);
+ if (ret) {
+ dev_err(dev, "Failed to initialize mapping (ret=%d)\n", ret);
+ goto err_debug_dump_exit;
+ }
+
+ ret = gxp_vd_init(gxp);
+ if (ret) {
+ dev_err(dev,
+ "Failed to initialize virtual device manager (ret=%d)\n",
+ ret);
+ goto err_debug_dump_exit;
+ }
+
+ ret = gxp_dma_map_resources(gxp);
+ if (ret) {
+ dev_err(dev, "Failed to map resources for GXP cores (ret=%d)\n",
+ ret);
+ goto err_vd_destroy;
+ }
+
+ gxp_fw_data_init(gxp);
+ gxp_create_debugfs(gxp);
+ dev_dbg(dev, "Probe finished\n");
+
+ return 0;
+
+err_vd_destroy:
+ gxp_vd_destroy(gxp);
+err_debug_dump_exit:
+ gxp_debug_dump_exit(gxp);
+err_dma_exit:
+ gxp_dma_exit(gxp);
+err:
+ misc_deregister(&gxp->misc_dev);
+ devm_kfree(dev, (void *)gxp);
+ return ret;
+}
+
+static int gxp_platform_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gxp_dev *gxp = platform_get_drvdata(pdev);
+
+ gxp_debug_dump_exit(gxp);
+ gxp_remove_debugfs(gxp);
+ gxp_fw_data_destroy(gxp);
+ gxp_vd_destroy(gxp);
+ gxp_dma_unmap_resources(gxp);
+ gxp_dma_exit(gxp);
+ misc_deregister(&gxp->misc_dev);
+
+#ifdef CONFIG_GXP_CLOUDRIPPER
+ // Request to power off BLK_AUR
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+#endif
+
+ devm_kfree(dev, (void *)gxp);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id gxp_of_match[] = {
+ { .compatible = "google,gxp", },
+ { /* end of list */ },
+};
+MODULE_DEVICE_TABLE(of, gxp_of_match);
+#endif
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id gxp_acpi_match[] = {
+ { "CXRP0001", 0 },
+ { /* end of list */ },
+};
+MODULE_DEVICE_TABLE(acpi, gxp_acpi_match);
+#endif
+
+static struct platform_driver gxp_platform_driver = {
+ .probe = gxp_platform_probe,
+ .remove = gxp_platform_remove,
+ .driver = {
+ .name = GXP_DRIVER_NAME,
+ .of_match_table = of_match_ptr(gxp_of_match),
+ .acpi_match_table = ACPI_PTR(gxp_acpi_match),
+ },
+};
+
+static int __init gxp_platform_init(void)
+{
+#ifdef CONFIG_ANDROID
+ /* Registers SSCD platform device */
+ if (platform_device_register(&gxp_sscd_dev))
+ pr_err("Unable to register SSCD platform device\n");
+#endif
+ return platform_driver_register(&gxp_platform_driver);
+}
+
+static void __exit gxp_platform_exit(void)
+{
+ platform_driver_unregister(&gxp_platform_driver);
+#ifdef CONFIG_ANDROID
+ platform_device_unregister(&gxp_sscd_dev);
+#endif
+}
+
+MODULE_DESCRIPTION("Google GXP platform driver");
+MODULE_LICENSE("GPL v2");
+module_init(gxp_platform_init);
+module_exit(gxp_platform_exit);
diff --git a/gxp-range-alloc.c b/gxp-range-alloc.c
new file mode 100644
index 0000000..73aa6af
--- /dev/null
+++ b/gxp-range-alloc.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP ranged resource allocator.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include "gxp-range-alloc.h"
+
+struct range_alloc *range_alloc_create(int start, int end)
+{
+ struct range_alloc *ra;
+ int count;
+ int size;
+
+ count = end - start;
+ if (count <= 0)
+ return ERR_PTR(-EINVAL);
+
+ size = sizeof(struct range_alloc) + count * sizeof(int);
+ ra = kzalloc(size, GFP_KERNEL);
+ if (!ra)
+ return ERR_PTR(-ENOMEM);
+
+ ra->total_count = count;
+ ra->free_count = count;
+ ra->start_index = start;
+ mutex_init(&ra->lock);
+
+ return ra;
+}
+
+int range_alloc_get(struct range_alloc *r, int element)
+{
+ int index = element - r->start_index;
+
+ mutex_lock(&r->lock);
+ if (index < 0 || index >= r->total_count) {
+ mutex_unlock(&r->lock);
+ return -EINVAL;
+ }
+
+ if (r->elements[index]) {
+ mutex_unlock(&r->lock);
+ return -EBUSY;
+ }
+
+ r->elements[index] = 1;
+ r->free_count--;
+
+ mutex_unlock(&r->lock);
+ return 0;
+}
+
+int range_alloc_get_any(struct range_alloc *r, int *element)
+{
+ int i;
+
+ mutex_lock(&r->lock);
+ if (!r->free_count) {
+ mutex_unlock(&r->lock);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < r->total_count; i++) {
+ if (r->elements[i] == 0) {
+ r->elements[i] = 1;
+ r->free_count--;
+ *element = i + r->start_index;
+ mutex_unlock(&r->lock);
+ return 0;
+ }
+ }
+ mutex_unlock(&r->lock);
+ return -ENOMEM;
+}
+
+int range_alloc_put(struct range_alloc *r, int element)
+{
+ int index = element - r->start_index;
+
+ mutex_lock(&r->lock);
+ if (index < 0 || index >= r->total_count) {
+ mutex_unlock(&r->lock);
+ return -EINVAL;
+ }
+
+ if (r->elements[index] == 0) {
+ mutex_unlock(&r->lock);
+ return -EBUSY;
+ }
+
+ r->elements[index] = 0;
+ r->free_count++;
+
+ mutex_unlock(&r->lock);
+ return 0;
+}
+
+int range_alloc_num_free(struct range_alloc *r)
+{
+ int free_count;
+
+ mutex_lock(&r->lock);
+ free_count = r->free_count;
+ mutex_unlock(&r->lock);
+
+ return free_count;
+}
+
+int range_alloc_destroy(struct range_alloc *r)
+{
+ if (!r)
+ return -EFAULT;
+ kfree(r);
+
+ return 0;
+}
diff --git a/gxp-range-alloc.h b/gxp-range-alloc.h
new file mode 100644
index 0000000..ed8c2f0
--- /dev/null
+++ b/gxp-range-alloc.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP ranged resource allocator.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_RANGE_ALLOC_H__
+#define __GXP_RANGE_ALLOC_H__
+
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+struct range_alloc {
+ int total_count;
+ int free_count;
+ int start_index;
+ struct mutex lock;
+ int elements[];
+};
+
+/**
+ * range_alloc_create() - Creates a range allocator starting at the specified
+ * start (inclusive) and ends at the specified end
+ * (exclusive).
+ * @start: The start of the range (inclusive).
+ * @end: The end of the range (exclusive)
+ *
+ * Return:
+ * ptr - A pointer of the newly created allocator handle on success, an
+ * error pointer (PTR_ERR) otherwise.
+ * -EINVAL - Invalid start/end combination
+ * -ENOMEM - Insufficient memory to create the allocator
+ */
+struct range_alloc *range_alloc_create(int start, int end);
+
+/**
+ * range_alloc_get() - Gets the specified element from the range.
+ * @r: The range allocator
+ * @element: The element to acquire from the range
+ *
+ * The @element argument should be within the allocator's range and has not been
+ * allocated before.
+ *
+ * Return:
+ * 0 - Successfully reserved @element
+ * -EINVAL - Invalid element index (negative or outside allocator range)
+ * -EBUSY - Element is already allocated
+ */
+int range_alloc_get(struct range_alloc *r, int element);
+
+/**
+ * range_alloc_get_any() - Gets any free element in the range.
+ * @r: The range allocator
+ * @element: A pointer to use to store the allocated element
+ *
+ * Return:
+ * 0 - Successful reservation
+ * -ENOMEM - No elements left in the range to allocate
+ */
+int range_alloc_get_any(struct range_alloc *r, int *element);
+
+/**
+ * range_alloc_put() - Puts an element back into the range.
+ * @r: The range allocator
+ * @element: The element to put back into the range
+ *
+ * Return:
+ * 0 - Successful placement back into the range
+ * -EINVAL - Invalid element index (negative or outside allocator range)
+ * -EBUSY - The element is still present in the range
+ */
+int range_alloc_put(struct range_alloc *r, int element);
+
+/**
+ * range_alloc_num_free() - Returns the number of free elements in the range.
+ * @r: The range allocator
+ *
+ * Return: the number of free elements in the range
+ */
+int range_alloc_num_free(struct range_alloc *r);
+
+/**
+ * range_alloc_destroy() - Destroys the range allocator
+ * @r: The range allocator to destroy
+ *
+ * The destruction does not validate that the range is empty.
+ *
+ * Return:
+ * 0 - Successfully destroyed range allocator
+ * -EFAULT - Invalid allocator address
+ */
+int range_alloc_destroy(struct range_alloc *r);
+
+#endif /* __GXP_RANGE_ALLOC_H__ */
diff --git a/gxp-sw-mailbox-driver.c b/gxp-sw-mailbox-driver.c
new file mode 100644
index 0000000..43daf21
--- /dev/null
+++ b/gxp-sw-mailbox-driver.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP kernel-userspace interface definitions.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+
+#include <linux/kthread.h>
+
+#include "gxp-tmp.h"
+#include "gxp-mailbox.h"
+#include "gxp-mailbox-driver.h"
+#include "gxp-mailbox-regs.h"
+
+/* Doorbells for emulating Mailbox Interrupts to device */
+#define MBOX_DOORBELL_INDEX(__core__) (31 - __core__)
+#define MBOX_SET_INT_TO_DEVICE(__mailbox__) \
+ writel(ENABLE, __mailbox__->gxp->regs.vaddr + DOORBELL_BLOCK + \
+ DOORBELL_BASE(MBOX_DOORBELL_INDEX( \
+ __mailbox__->core_id)) + \
+ DOORBELL_SET_OFFSET)
+#define MBOX_CLEAR_INT_TO_DEVICE(__mailbox__) \
+ writel(ENABLE, __mailbox__->gxp->regs.vaddr + DOORBELL_BLOCK + \
+ DOORBELL_BASE(MBOX_DOORBELL_INDEX( \
+ __mailbox__->core_id)) + \
+ DOORBELL_CLEAR_OFFSET)
+
+/* Emulated Mailbox Register Macros */
+#define MBOX_CSR_SCRATCHPAD_OFFSET 0x100
+
+#define MBOX_ACCESS_SYNC_BARRIER 15 /* Start at the end of the sync barriers */
+
+/* Register Access */
+
+static u32 csr_read(struct gxp_mailbox *mailbox, uint reg_offset)
+{
+ u32 read_value;
+
+ gxp_acquire_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+
+ switch (reg_offset) {
+ case MBOX_MCUCTLR_OFFSET:
+ case MBOX_INTGR0_OFFSET:
+ case MBOX_INTMR0_OFFSET:
+ case MBOX_INTSR0_OFFSET:
+ case MBOX_INTMSR0_OFFSET:
+ case MBOX_INTGR1_OFFSET:
+ case MBOX_INTMR1_OFFSET:
+ case MBOX_INTSR1_OFFSET:
+ case MBOX_INTMSR1_OFFSET:
+ break;
+ case MBOX_INTCR0_OFFSET:
+ case MBOX_INTCR1_OFFSET:
+ dev_notice(mailbox->gxp->dev,
+ "Attempt to read write-only mailbox CSR 0x%X\n",
+ reg_offset);
+ read_value = 0;
+ goto csr_read_exit;
+ default:
+ dev_notice(mailbox->gxp->dev,
+ "Attempt to read non-existent mailbox CSR 0x%X\n",
+ reg_offset);
+ read_value = 0;
+ goto csr_read_exit;
+ }
+
+ read_value = readl(mailbox->csr_reg_base + reg_offset);
+
+csr_read_exit:
+ gxp_release_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+
+ return read_value;
+}
+
+static void csr_write(struct gxp_mailbox *mailbox, uint reg_offset, u32 value)
+{
+ bool send_interrupt = false;
+ u32 gen_val, clear_val, mask_val, status_val;
+
+ gxp_acquire_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+
+ /* Emulate any side effects for CSR writes */
+ switch (reg_offset) {
+ case MBOX_MCUCTLR_OFFSET:
+ /* side effects not implemented */
+ break;
+ case MBOX_INTGR0_OFFSET:
+ /*
+ * 1. Set interrupt status register
+ */
+ writel(value, mailbox->csr_reg_base + MBOX_INTSR0_OFFSET);
+ /*
+ * 2. Check interrupt mask-status and clear registers
+ */
+ mask_val = readl(mailbox->csr_reg_base + MBOX_INTMSR0_OFFSET);
+ clear_val = readl(mailbox->csr_reg_base + MBOX_INTCR0_OFFSET);
+
+ if ((value & mask_val) & clear_val) {
+ /*
+ * 3. Update the clear register to reflect outgoing
+ * interrupts
+ *
+ * A 0-bit in the clear register indicates an interrupt
+ * waiting to be serviced, and therefore masked from
+ * further generation.
+ *
+ * Set the bits of any newly-generated sources to 0.
+ * The only bits which shold remain set are those
+ * already 1 in the clear register and not being set
+ * (i.e. 0 in value & mask_val).
+ */
+ writel(~(value & mask_val) & clear_val,
+ mailbox->csr_reg_base + MBOX_INTCR0_OFFSET);
+ /*
+ * 4. If set interrupts aren't masked, trigger HW
+ * interrupt
+ */
+ send_interrupt = true;
+ }
+ break;
+ case MBOX_INTCR0_OFFSET:
+ /*
+ * 1. Clear interrupt generation register
+ */
+ gen_val = readl(mailbox->csr_reg_base + MBOX_INTGR0_OFFSET);
+ writel(~value & gen_val,
+ mailbox->csr_reg_base + MBOX_INTGR0_OFFSET);
+ /*
+ * 2. Clear interrupt status register
+ */
+ status_val = readl(mailbox->csr_reg_base + MBOX_INTSR0_OFFSET);
+ writel(~value & status_val,
+ mailbox->csr_reg_base + MBOX_INTSR0_OFFSET);
+ /*
+ * 3. Update the clear register unmask any cleared interrupts
+ *
+ * A 1 written to any bit should re-enable that interrupt,
+ * meaning the new value written should be 1 as well. OR.
+ */
+ clear_val = readl(mailbox->csr_reg_base + MBOX_INTCR0_OFFSET);
+ writel(value | clear_val,
+ mailbox->csr_reg_base + MBOX_INTCR0_OFFSET);
+ /*
+ * 4. Clear outgoing HW interrupt
+ */
+ MBOX_CLEAR_INT_TO_DEVICE(mailbox);
+
+ /*
+ * Value written to MBOX_INTCR0_OFFSET is not the actual
+ * value stored in memory, so bail here.
+ */
+ goto csr_write_exit;
+ case MBOX_INTMR0_OFFSET:
+ /*
+ * Update the interrupt mask status register
+ * In this register 1 = masked, but in the mask status register
+ * 1 = enabled, so the inverse value must be written.
+ */
+ writel(~value, mailbox->csr_reg_base + MBOX_INTMSR0_OFFSET);
+ break;
+ case MBOX_INTGR1_OFFSET:
+ dev_notice(
+ mailbox->gxp->dev,
+ "Writing to-host interrupt from host. Is this a mistake?\n");
+ /*
+ * 1. Set interrupt status register
+ */
+ writel(value, mailbox->csr_reg_base + MBOX_INTSR1_OFFSET);
+ /*
+ * 2. Check interrupt mask-status and clear registers
+ */
+ mask_val = readl(mailbox->csr_reg_base + MBOX_INTMSR1_OFFSET);
+ clear_val = readl(mailbox->csr_reg_base + MBOX_INTCR1_OFFSET);
+
+ if ((value & mask_val) & clear_val) {
+ /*
+ * 3. Update the clear register to reflect outgoing
+ * interrupts
+ *
+ * A 0-bit in the clear register indicates an interrupt
+ * waiting to be serviced, and therefore masked from
+ * further generation.
+ *
+ * Set the bits of any newly-generated sources to 0.
+ * The only bits which shold remain set are those
+ * already 1 in the clear register and not being set
+ * (i.e. 0 in value & mask_val).
+ */
+ writel(~(value & mask_val) & clear_val,
+ mailbox->csr_reg_base + MBOX_INTCR1_OFFSET);
+ /*
+ * 4. If set interrupts aren't masked, trigger HW
+ * interrupt
+ */
+ /*
+ * Software mailboxes don't have a to-host interrupt,
+ * so the host polls the status register and no
+ * further action is required.
+ */
+ }
+ break;
+ case MBOX_INTCR1_OFFSET:
+ /*
+ * 1. Clear interrupt generation register
+ */
+ gen_val = readl(mailbox->csr_reg_base + MBOX_INTGR1_OFFSET);
+ writel(~value & gen_val,
+ mailbox->csr_reg_base + MBOX_INTGR1_OFFSET);
+ /*
+ * 2. Clear interrupt status register
+ */
+ status_val = readl(mailbox->csr_reg_base + MBOX_INTSR1_OFFSET);
+ writel(~value & status_val,
+ mailbox->csr_reg_base + MBOX_INTSR1_OFFSET);
+ /*
+ * 3. Update the clear register unmask any cleared interrupts
+ *
+ * A 1 written to any bit should re-enable that interrupt,
+ * meaning the new value written should be 1 as well. OR.
+ */
+ clear_val = readl(mailbox->csr_reg_base + MBOX_INTCR1_OFFSET);
+ writel(value | clear_val,
+ mailbox->csr_reg_base + MBOX_INTCR1_OFFSET);
+ /*
+ * 4. Clear outgoing HW interrupt
+ */
+ /*
+ * Software mailboxes don't have a to-host interrupt, so the
+ * host polls the status register and no further action is
+ * required.
+ */
+
+ /*
+ * Value written to MBOX_INTCR1_OFFSET is not the actual
+ * value stored in memory, so bail here.
+ */
+ goto csr_write_exit;
+ case MBOX_INTMR1_OFFSET:
+ /*
+ * Update the interrupt mask status register
+ * In this register 1 = masked, but in the mask status register
+ * 1 = enabled, so the inverse value must be written.
+ */
+ writel(~value, mailbox->csr_reg_base + MBOX_INTMSR1_OFFSET);
+ break;
+ case MBOX_INTSR0_OFFSET:
+ case MBOX_INTMSR0_OFFSET:
+ case MBOX_INTSR1_OFFSET:
+ case MBOX_INTMSR1_OFFSET:
+ dev_notice(mailbox->gxp->dev,
+ "Attempt to write read-only mailbox CSR 0x%X\n",
+ reg_offset);
+ goto csr_write_exit;
+ default:
+ dev_notice(mailbox->gxp->dev,
+ "Attempt to write non-existent mailbox CSR 0x%X\n",
+ reg_offset);
+ goto csr_write_exit;
+ }
+
+ writel(value, mailbox->csr_reg_base + reg_offset);
+
+csr_write_exit:
+ if (send_interrupt)
+ MBOX_SET_INT_TO_DEVICE(mailbox);
+
+ gxp_release_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+}
+
+static u32 data_read(struct gxp_mailbox *mailbox, uint reg_offset)
+{
+ u32 read_value;
+
+ gxp_acquire_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+
+ read_value = readl(mailbox->data_reg_base + reg_offset);
+
+ gxp_release_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+
+ return read_value;
+}
+
+static void data_write(struct gxp_mailbox *mailbox, uint reg_offset, u32 value)
+{
+ gxp_acquire_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+
+ writel(value, mailbox->data_reg_base + reg_offset);
+
+ gxp_release_sync_barrier(mailbox->gxp, MBOX_ACCESS_SYNC_BARRIER);
+}
+
+/* IRQ Handling */
+
+static int poll_int_thread(void *data)
+{
+ u32 status_value, mask_value, masked_status_value;
+ struct gxp_mailbox *mailbox = (struct gxp_mailbox *)data;
+
+ while (!kthread_should_stop()) {
+ gxp_acquire_sync_barrier(mailbox->gxp,
+ MBOX_ACCESS_SYNC_BARRIER);
+ status_value =
+ readl(mailbox->csr_reg_base + MBOX_INTSR1_OFFSET);
+ mask_value = readl(mailbox->csr_reg_base + MBOX_INTMSR1_OFFSET);
+ gxp_release_sync_barrier(mailbox->gxp,
+ MBOX_ACCESS_SYNC_BARRIER);
+
+ masked_status_value = status_value & mask_value;
+ if (masked_status_value) {
+ if (masked_status_value & ~mailbox->debug_dump_int_mask)
+ mailbox->handle_irq(mailbox);
+
+ if (masked_status_value & mailbox->debug_dump_int_mask)
+ mailbox->handle_debug_dump_irq(mailbox);
+
+ gxp_mailbox_clear_host_interrupt(
+ mailbox, status_value & mask_value);
+ }
+
+ /* TODO(b/177701517): Polling frequency is untuned.*/
+ msleep(200);
+ }
+
+ return 0;
+}
+
+/* gxp-mailbox-driver.h interface */
+
+void gxp_mailbox_driver_init(struct gxp_mailbox *mailbox)
+{
+ /* Clear and unmask all to-device interrupts */
+ csr_write(mailbox, MBOX_INTCR0_OFFSET, 0xFFFFFFFF);
+ csr_write(mailbox, MBOX_INTMR0_OFFSET, 0x00000000);
+ /* Clear and unmask all to-host interrupts */
+ csr_write(mailbox, MBOX_INTCR1_OFFSET, 0xFFFFFFFF);
+ csr_write(mailbox, MBOX_INTMR1_OFFSET, 0x00000000);
+
+ /* Setup a polling thread to check for to-host "interrupts" */
+ mailbox->to_host_poll_task =
+ kthread_run(poll_int_thread, mailbox,
+ "gxp_poll_mailbox%d_to_host", mailbox->core_id);
+
+ if (IS_ERR(mailbox->to_host_poll_task)) {
+ dev_err(mailbox->gxp->dev,
+ "Failed to start polling for incoming updates from mailbox %d\n",
+ mailbox->core_id);
+ }
+}
+
+void gxp_mailbox_driver_exit(struct gxp_mailbox *mailbox)
+{
+ if (!IS_ERR_OR_NULL(mailbox->to_host_poll_task))
+ kthread_stop(mailbox->to_host_poll_task);
+}
+
+void __iomem *gxp_mailbox_get_csr_base(struct gxp_dev *gxp, uint index)
+{
+ return gxp->fwbufs[index].vaddr + AURORA_SCRATCHPAD_OFF +
+ MBOX_CSR_SCRATCHPAD_OFFSET;
+}
+
+void __iomem *gxp_mailbox_get_data_base(struct gxp_dev *gxp, uint index)
+{
+ return gxp->fwbufs[index].vaddr + AURORA_SCRATCHPAD_OFF +
+ MBOX_CSR_SCRATCHPAD_OFFSET + MBOX_DATA_REG_BASE;
+}
+
+/* gxp-mailbox-driver.h: CSR-based calls */
+
+void gxp_mailbox_reset_hw(struct gxp_mailbox *mailbox)
+{
+ csr_write(mailbox, MBOX_MCUCTLR_OFFSET, 1);
+}
+
+void gxp_mailbox_generate_device_interrupt(struct gxp_mailbox *mailbox,
+ u32 int_mask)
+{
+ csr_write(mailbox, MBOX_INTGR0_OFFSET, int_mask);
+}
+
+u32 gxp_mailbox_get_device_mask_status(struct gxp_mailbox *mailbox)
+{
+ return csr_read(mailbox, MBOX_INTMSR0_OFFSET);
+}
+
+void gxp_mailbox_clear_host_interrupt(struct gxp_mailbox *mailbox, u32 int_mask)
+{
+ csr_write(mailbox, MBOX_INTCR1_OFFSET, int_mask);
+}
+
+void gxp_mailbox_mask_host_interrupt(struct gxp_mailbox *mailbox, u32 int_mask)
+{
+ csr_write(mailbox, MBOX_INTMR1_OFFSET, int_mask);
+}
+
+u32 gxp_mailbox_get_host_mask_status(struct gxp_mailbox *mailbox)
+{
+ return csr_read(mailbox, MBOX_INTMSR1_OFFSET);
+}
+
+/* gxp-mailbox-driver.h: Data register-based callss */
+
+void gxp_mailbox_write_status(struct gxp_mailbox *mailbox, u32 status)
+{
+ data_write(mailbox, MBOX_STATUS_OFFSET, status);
+}
+
+void gxp_mailbox_write_descriptor(struct gxp_mailbox *mailbox,
+ dma_addr_t descriptor_addr)
+{
+ data_write(mailbox, MBOX_DESCRIPTOR_ADDR_OFFSET, (u32)descriptor_addr);
+}
+
+void gxp_mailbox_write_cmd_queue_tail(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_resp_head =
+ data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET) &
+ RESP_HEAD_MASK;
+ u32 new_cmd_tail = (u32)val << CMD_TAIL_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET,
+ new_cmd_tail | current_resp_head);
+}
+
+void gxp_mailbox_write_resp_queue_head(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_cmd_tail =
+ data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET) &
+ CMD_TAIL_MASK;
+ u32 new_resp_head = (u32)val << RESP_HEAD_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET,
+ current_cmd_tail | new_resp_head);
+}
+
+u16 gxp_mailbox_read_cmd_queue_head(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET);
+
+ return (u16)((reg_val & CMD_HEAD_MASK) >> CMD_HEAD_SHIFT);
+}
+
+u16 gxp_mailbox_read_resp_queue_tail(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET);
+
+ return (u16)((reg_val & RESP_TAIL_MASK) >> RESP_TAIL_SHIFT);
+}
+
+void gxp_mailbox_write_cmd_queue_head(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_resp_tail =
+ data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET) &
+ RESP_TAIL_MASK;
+ u32 new_cmd_head = (u32)val << CMD_HEAD_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET,
+ new_cmd_head | current_resp_tail);
+}
+
+void gxp_mailbox_write_resp_queue_tail(struct gxp_mailbox *mailbox, u16 val)
+{
+ u32 current_cmd_head =
+ data_read(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET) &
+ CMD_HEAD_MASK;
+ u32 new_resp_tail = (u32)val << RESP_TAIL_SHIFT;
+
+ data_write(mailbox, MBOX_CMD_HEAD_RESP_TAIL_OFFSET,
+ current_cmd_head | new_resp_tail);
+}
+
+u16 gxp_mailbox_read_cmd_queue_tail(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET);
+
+ return (u16)((reg_val & CMD_TAIL_MASK) >> CMD_TAIL_SHIFT);
+}
+
+u16 gxp_mailbox_read_resp_queue_head(struct gxp_mailbox *mailbox)
+{
+ u32 reg_val = data_read(mailbox, MBOX_CMD_TAIL_RESP_HEAD_OFFSET);
+
+ return (u16)((reg_val & RESP_HEAD_MASK) >> RESP_HEAD_SHIFT);
+}
diff --git a/gxp-tmp.h b/gxp-tmp.h
new file mode 100644
index 0000000..1e92420
--- /dev/null
+++ b/gxp-tmp.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Temporary configuration file fore GXP.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_TMP_H__
+#define __GXP_TMP_H__
+
+/* TODO (b/176979630): Delete gxp.tmp.h. Move definitions to gxp-config.h */
+
+#define AURORA_SCRATCHPAD_OFF 0x00F00000 /* Last 1M of ELF load region */
+#define AURORA_SCRATCHPAD_LEN 0x00100000 /* 1M */
+
+#define Q7_ALIVE_MAGIC 0x55555555
+
+#define LPM_BLOCK 0x040000
+#define DOORBELL_BLOCK 0x0C0000
+#define SYNC_BARRIER_BLOCK 0x00100000
+
+#define DOORBELL_BASE(_x_) ((_x_) << 12)
+#define DOORBELL_COUNT 32
+#define DOORBELL_STATUS_OFFSET 0x0
+#define DOORBELL_SET_OFFSET 0x4
+#define DOORBELL_CLEAR_OFFSET 0x8
+#define DOORBELL_EN_ALL_MASK 0xFFFFFFFF
+
+#define SYNC_BARRIER_BASE(_x_) ((_x_) << 12)
+#define SYNC_BARRIER_FREE_VALUE 0xF
+#define SYNC_BARRIER_COUNT 16
+
+#define CORE_PSM_BASE(_core_) ((_core_ + 1) << 12)
+#define TOP_PSM_BASE 0x5000
+
+#define PSM_INIT_DONE_MASK 0x80
+#define PSM_CURR_STATE_MASK 0x0F
+#define PSM_STATE_VALID_MASK 0x10
+
+#define PSM_HW_MODE 0x0
+#define PSM_START 0x1
+
+#define PSM_STATE_ACTIVE 0x0
+#define PSM_STATE_CLK_GATED 0x1
+
+#define PROVINO_IXBAR1_ARL_CTRL 0x1818
+#define PROVINO_IXBAR1_ARL_EN (0x1 << 31)
+
+#define DISABLE 0x0
+#define ENABLE 0x1
+
+#define CORE_SCRATCHPAD_BASE(_core_) (_core_ << 16)
+#define SCRATCHPAD_MSG_OFFSET(_msg_) (_msg_ << 2)
+
+enum aurora_msg {
+ MSG_CORE_ALIVE,
+ MSG_TOP_ACCESS_OK,
+ MSG_SCRATCHPAD_MAX,
+};
+
+#endif /* __GXP_TMP_H__ */
diff --git a/gxp-vd.c b/gxp-vd.c
new file mode 100644
index 0000000..2e82792
--- /dev/null
+++ b/gxp-vd.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP virtual device manager.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "gxp-firmware.h"
+#include "gxp-firmware-data.h"
+#include "gxp-internal.h"
+#include "gxp-vd.h"
+
+int gxp_vd_init(struct gxp_dev *gxp)
+{
+ uint core;
+ int ret;
+
+ mutex_init(&gxp->vd_lock);
+ mutex_lock(&gxp->vd_lock);
+
+ /* Mark all cores as free */
+ for (core = 0; core < GXP_NUM_CORES; core++)
+ gxp->core_to_client[core] = NULL;
+
+ ret = gxp_fw_init(gxp);
+ mutex_unlock(&gxp->vd_lock);
+ return ret;
+}
+
+void gxp_vd_destroy(struct gxp_dev *gxp)
+{
+ mutex_lock(&gxp->vd_lock);
+
+ gxp_fw_destroy(gxp);
+
+ mutex_unlock(&gxp->vd_lock);
+}
+
+/* Caller must hold gxp->vd_lock */
+static void gxp_vd_release(struct gxp_client *client)
+{
+ uint core;
+ struct gxp_dev *gxp = client->gxp;
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (gxp->core_to_client[core] == client) {
+ gxp->core_to_client[core] = NULL;
+ gxp_firmware_stop(gxp, core);
+ }
+ }
+ if (client->app) {
+ gxp_fw_data_destroy_app(gxp, client->app);
+ client->app = NULL;
+ }
+}
+
+/* Caller must hold gxp->vd_lock */
+int gxp_vd_allocate(struct gxp_client *client, u16 requested_cores)
+{
+ struct gxp_dev *gxp = client->gxp;
+ uint core;
+ int available_cores = 0;
+ int cores_remaining = requested_cores;
+ uint core_list = 0;
+ int ret = 0;
+
+ /* Assumes 0 < requested_cores <= GXP_NUM_CORES */
+ WARN_ON(requested_cores == 0 || requested_cores > GXP_NUM_CORES);
+ /* Assumes client has not called gxp_vd_allocate */
+ WARN_ON(client->vd_allocated);
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (gxp->core_to_client[core] == NULL) {
+ if (available_cores < requested_cores)
+ core_list |= BIT(core);
+ available_cores++;
+ }
+ }
+
+ if (available_cores < requested_cores) {
+ dev_err(gxp->dev, "Insufficient available cores. Available: %d. Requested: %u\n",
+ available_cores, requested_cores);
+ return -EBUSY;
+ }
+
+ client->app = gxp_fw_data_create_app(gxp, core_list);
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (cores_remaining == 0)
+ break;
+
+ if (core_list & BIT(core)) {
+ ret = gxp_firmware_run(gxp, core);
+ if (ret) {
+ dev_err(gxp->dev, "Failed to run firmware on core %u\n",
+ core);
+ goto out_vd_release;
+ }
+ gxp->core_to_client[core] = client;
+ cores_remaining--;
+ }
+ }
+
+ if (cores_remaining != 0) {
+ dev_err(gxp->dev, "Internal error: Failed to allocate %u requested cores. %d cores remaining\n",
+ requested_cores, cores_remaining);
+ /*
+ * Should never reach here. Previously verified that enough
+ * cores are available.
+ */
+ WARN_ON(true);
+ ret = -EIO;
+ goto out_vd_release;
+ }
+
+ client->vd_allocated = true;
+ return ret;
+
+out_vd_release:
+ gxp_vd_release(client);
+ return ret;
+}
+
+int gxp_vd_virt_core_to_phys_core(struct gxp_client *client, u16 virt_core)
+{
+ struct gxp_dev *gxp = client->gxp;
+ uint phys_core;
+ uint virt_core_index = 0;
+
+ mutex_lock(&gxp->vd_lock);
+
+ if (!client->vd_allocated) {
+ mutex_unlock(&gxp->vd_lock);
+ dev_dbg(gxp->dev, "Client has not allocated a virtual device\n");
+ return -EINVAL;
+ }
+
+ for (phys_core = 0; phys_core < GXP_NUM_CORES; phys_core++) {
+ if (gxp->core_to_client[phys_core] == client) {
+ if (virt_core_index == virt_core) {
+ /* Found virtual core */
+ mutex_unlock(&gxp->vd_lock);
+ return phys_core;
+ }
+
+ virt_core_index++;
+ }
+ }
+
+ mutex_unlock(&gxp->vd_lock);
+ dev_dbg(gxp->dev, "No mapping for virtual core %u\n", virt_core);
+ return -EINVAL;
+}
+
+uint gxp_vd_virt_core_list_to_phys_core_list(struct gxp_client *client,
+ u16 virt_core_list)
+{
+ uint phys_core_list = 0;
+ uint virt_core = 0;
+ int phys_core;
+
+ while (virt_core_list) {
+ /*
+ * Get the next virt core by finding the index of the first
+ * set bit in the core list.
+ *
+ * Subtract 1 since `ffs()` returns a 1-based index. Since
+ * virt_core_list cannot be 0 at this point, no need to worry
+ * about wrap-around.
+ */
+ virt_core = ffs(virt_core_list) - 1;
+
+ /* Any invalid virt cores invalidate the whole list */
+ phys_core = gxp_vd_virt_core_to_phys_core(client, virt_core);
+ if (phys_core < 0)
+ return 0;
+
+ phys_core_list |= BIT(phys_core);
+ virt_core_list &= ~BIT(virt_core);
+ }
+
+ return phys_core_list;
+}
+
+struct gxp_client *gxp_client_create(struct gxp_dev *gxp)
+{
+ struct gxp_client *client;
+
+ client = kmalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return ERR_PTR(-ENOMEM);
+
+ client->gxp = gxp;
+ client->vd_allocated = false;
+ client->app = NULL;
+ return client;
+}
+
+void gxp_client_destroy(struct gxp_client *client)
+{
+ struct gxp_dev *gxp = client->gxp;
+
+ mutex_lock(&gxp->vd_lock);
+ gxp_vd_release(client);
+ mutex_unlock(&gxp->vd_lock);
+ kfree(client);
+}
diff --git a/gxp-vd.h b/gxp-vd.h
new file mode 100644
index 0000000..8180669
--- /dev/null
+++ b/gxp-vd.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP virtual device manager.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __GXP_VD_H__
+#define __GXP_VD_H__
+
+#include <linux/types.h>
+
+#include "gxp-internal.h"
+
+
+/*
+ * TODO(b/193180931) cleanup the relationship between the internal GXP modules.
+ * For example, whether or not gxp_vd owns the gxp_fw module, and if so, if
+ * other modules are expected to access the gxp_fw directly or only via gxp_vd.
+ */
+/*
+ * Initializes the device management subsystem and allocates resources for it.
+ * This is expected to be called once per driver lifecycle.
+ */
+int gxp_vd_init(struct gxp_dev *gxp);
+/*
+ * Tears down the device management subsystem.
+ * This is expected to be called once per driver lifecycle.
+ */
+void gxp_vd_destroy(struct gxp_dev *gxp);
+/*
+ * Allocates a virtual device on the requested cores for the specified client.
+ * This will also load the FW on, and boot up, the requested cores.
+ */
+int gxp_vd_allocate(struct gxp_client *client, u16 requested_cores);
+/*
+ * Returns the physical core ID for the specified virtual_core belonging to
+ * this virtual device.
+ */
+int gxp_vd_virt_core_to_phys_core(struct gxp_client *client, u16 virt_core);
+/*
+ * Converts a bitfield of virtual core IDs to a bitfield of physical core IDs.
+ *
+ * If the virtual list contains any invalid IDs, the entire physical ID list
+ * will be considered invalid and this function will return 0.
+ */
+uint gxp_vd_virt_core_list_to_phys_core_list(struct gxp_client *client,
+ u16 virt_core_list);
+/*
+ * Allocates and initializes a client container to represent a virtual device.
+ */
+struct gxp_client *gxp_client_create(struct gxp_dev *gxp);
+/*
+ * Frees up the client container representing a virtual device.
+ */
+void gxp_client_destroy(struct gxp_client *client);
+
+#endif /* __GXP_VD_H__ */
diff --git a/gxp.h b/gxp.h
new file mode 100644
index 0000000..ef47f58
--- /dev/null
+++ b/gxp.h
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GXP kernel-userspace interface definitions.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+#ifndef __GXP_H__
+#define __GXP_H__
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define GXP_IOCTL_BASE 0xEE
+
+/* GXP map flag macros */
+
+/* The mask for specifying DMA direction in GXP map flag */
+#define GXP_MAP_DIR_MASK 3
+/* The targeted DMA direction for the buffer */
+#define GXP_MAP_DMA_BIDIRECTIONAL 0
+#define GXP_MAP_DMA_TO_DEVICE 1
+#define GXP_MAP_DMA_FROM_DEVICE 2
+
+struct gxp_map_ioctl {
+ /*
+ * Bitfield indicating which virtual cores to map the buffer for.
+ * To map for virtual core X, set bit X in this field, i.e. `1 << X`.
+ *
+ * This field is not used by the unmap IOCTL, which always unmaps a
+ * buffer for all cores it had been mapped for.
+ */
+ __u16 virtual_core_list;
+ __u64 host_address; /* virtual address in the process space */
+ __u32 size; /* size of mapping in bytes */
+ /*
+ * Flags indicating mapping attribute requests from the runtime.
+ * Set RESERVED bits to 0 to ensure backwards compatibility.
+ *
+ * Bitfields:
+ * [1:0] - DMA_DIRECTION:
+ * 00 = DMA_BIDIRECTIONAL (host/device can write buffer)
+ * 01 = DMA_TO_DEVICE (host can write buffer)
+ * 10 = DMA_FROM_DEVICE (device can write buffer)
+ * Note: DMA_DIRECTION is the direction in which data moves
+ * from the host's perspective.
+ * [31:2] - RESERVED
+ */
+ __u32 flags;
+ __u64 device_address; /* returned device address */
+};
+
+/* Map host buffer. */
+#define GXP_MAP_BUFFER \
+ _IOWR(GXP_IOCTL_BASE, 0, struct gxp_map_ioctl)
+
+/*
+ * Un-map host buffer previously mapped by GXP_MAP_BUFFER.
+ *
+ * Only the @device_address field will be used. Other fields will be fetched
+ * from the kernel's internal records. It is recommended to use the argument
+ * that was passed in GXP_MAP_BUFFER to un-map the buffer.
+ */
+#define GXP_UNMAP_BUFFER \
+ _IOW(GXP_IOCTL_BASE, 1, struct gxp_map_ioctl)
+
+/* GXP sync flag macros */
+#define GXP_SYNC_FOR_DEVICE (0)
+#define GXP_SYNC_FOR_CPU (1)
+
+struct gxp_sync_ioctl {
+ /*
+ * The starting address of the buffer to be synchronized. Must be a
+ * device address returned by GXP_MAP_BUFFER.
+ */
+ __u64 device_address;
+ /* size in bytes to be sync'ed */
+ __u32 size;
+ /*
+ * offset in bytes at which the sync operation is to begin from the
+ * start of the buffer
+ */
+ __u32 offset;
+ /*
+ * Flags indicating sync operation requested from the runtime.
+ * Set RESERVED bits to 0 to ensure backwards compatibility.
+ *
+ * Bitfields:
+ * [0:0] - Sync direction. Sync for device or CPU.
+ * 0 = sync for device
+ * 1 = sync for CPU
+ * [31:1] - RESERVED
+ */
+ __u32 flags;
+};
+
+/*
+ * Sync buffer previously mapped by GXP_MAP_BUFFER.
+ *
+ * EINVAL: If a mapping for @device_address is not found.
+ * EINVAL: If @size equals 0.
+ * EINVAL: If @offset plus @size exceeds the mapping size.
+ */
+#define GXP_SYNC_BUFFER \
+ _IOW(GXP_IOCTL_BASE, 2, struct gxp_sync_ioctl)
+
+struct gxp_mailbox_command_ioctl {
+ /*
+ * Input:
+ * The virtual core to dispatch the command to.
+ */
+ __u16 virtual_core_id;
+ /*
+ * Output:
+ * The sequence number assigned to this command. The caller can use
+ * this value to match responses fetched via `GXP_MAILBOX_RESPONSE`
+ * with this command.
+ */
+ __u64 sequence_number;
+ /*
+ * Input:
+ * Device address to the buffer containing a GXP command. The user
+ * should have obtained this address from the GXP_MAP_BUFFER ioctl.
+ */
+ __u64 device_address;
+ /*
+ * Input:
+ * Size of the buffer at `device_address` in bytes.
+ */
+ __u32 size;
+ /*
+ * Input:
+ * Flags describing the command, for use by the GXP device.
+ */
+ __u32 flags;
+};
+
+/* Push element to the mailbox commmand queue. */
+#define GXP_MAILBOX_COMMAND \
+ _IOW(GXP_IOCTL_BASE, 3, struct gxp_mailbox_command_ioctl)
+
+/* GXP mailbox response error code values */
+#define GXP_RESPONSE_ERROR_NONE (0)
+#define GXP_RESPONSE_ERROR_INTERNAL (1)
+#define GXP_RESPONSE_ERROR_TIMEOUT (2)
+
+struct gxp_mailbox_response_ioctl {
+ /*
+ * Input:
+ * The virtual core to fetch a response from.
+ */
+ __u16 virtual_core_id;
+ /*
+ * Output:
+ * Sequence number indicating which command this response is for.
+ */
+ __u64 sequence_number;
+ /*
+ * Output:
+ * Driver error code.
+ * Indicates if the response was obtained successfully,
+ * `GXP_RESPONSE_ERROR_NONE`, or what error prevented the command
+ * from completing successfully.
+ */
+ __u16 error_code;
+ /*
+ * Output:
+ * Value returned by firmware in response to a command.
+ * Only valid if `error_code` == GXP_RESPONSE_ERROR_NONE
+ */
+ __u32 cmd_retval;
+};
+
+/*
+ * Pop element from the mailbox response queue. Blocks until mailbox response
+ * is available.
+ */
+#define GXP_MAILBOX_RESPONSE \
+ _IOWR(GXP_IOCTL_BASE, 4, struct gxp_mailbox_response_ioctl)
+
+struct gxp_specs_ioctl {
+ __u8 core_count;
+ __u16 version_major;
+ __u16 version_minor;
+ __u16 version_build;
+ __u8 threads_per_core;
+ __u32 memory_per_core; /* measured in kB */
+};
+
+/* Query system specs. */
+#define GXP_GET_SPECS \
+ _IOR(GXP_IOCTL_BASE, 5, struct gxp_specs_ioctl)
+
+struct gxp_virtual_device_ioctl {
+ /*
+ * Input:
+ * The number of cores requested for the virtual device.
+ */
+ __u8 core_count;
+ /*
+ * Input:
+ * The number of threads requested per core.
+ */
+ __u16 threads_per_core;
+ /*
+ * Input:
+ * The amount of memory requested per core, in kB.
+ */
+ __u32 memory_per_core;
+ /*
+ * Output:
+ * The ID assigned to the virtual device and shared with its cores.
+ */
+ __u32 vdid;
+};
+
+/* Allocate virtual device. */
+#define GXP_ALLOCATE_VIRTUAL_DEVICE \
+ _IOWR(GXP_IOCTL_BASE, 6, struct gxp_virtual_device_ioctl)
+
+#endif /* __GXP_H__ */
diff --git a/mm-backport.h b/mm-backport.h
new file mode 100644
index 0000000..c435281
--- /dev/null
+++ b/mm-backport.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Backport mm APIs.
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+#ifndef __MM_BACKPORT_H__
+#define __MM_BACKPORT_H__
+
+#include <linux/mm.h>
+#include <linux/version.h>
+
+#if KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE
+
+/*
+ * Define pin_user_pages* which are introduced in Linux 5.6.
+ *
+ * We simply define pin_user_pages* as get_user_pages* here so our driver can
+ * prefer PIN over GET when possible.
+ */
+#ifndef FOLL_PIN
+
+/* define as zero to prevent older get_user_pages* returning EINVAL */
+#define FOLL_LONGTERM 0
+
+#define pin_user_pages_fast get_user_pages_fast
+#define unpin_user_page put_page
+
+#endif /* FOLL_PIN */
+
+#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(5,6,0) */
+
+#endif /* __MM_BACKPORT_H__ */