diff options
author | John Scheible <johnscheible@google.com> | 2021-10-26 13:45:19 -0700 |
---|---|---|
committer | John Scheible <johnscheible@google.com> | 2021-10-28 16:14:12 -0700 |
commit | de16475969ca7dfaecf7144ece32b929cf193861 (patch) | |
tree | 962335a74133193541bf8c398a7cdf445f79a610 | |
parent | 497cbdab5ea4f505beac7bb6d686bb4c3c504e8c (diff) | |
download | gs201-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-format | 548 | ||||
-rw-r--r-- | .gitignore | 9 | ||||
-rw-r--r-- | Kconfig | 38 | ||||
-rw-r--r-- | Makefile | 59 | ||||
-rw-r--r-- | gxp-bpm.c | 53 | ||||
-rw-r--r-- | gxp-bpm.h | 37 | ||||
-rw-r--r-- | gxp-config.h | 31 | ||||
-rw-r--r-- | gxp-csrs.h | 115 | ||||
-rw-r--r-- | gxp-debug-dump.c | 551 | ||||
-rw-r--r-- | gxp-debug-dump.h | 164 | ||||
-rw-r--r-- | gxp-debugfs.c | 220 | ||||
-rw-r--r-- | gxp-debugfs.h | 15 | ||||
-rw-r--r-- | gxp-dma-iommu.c | 826 | ||||
-rw-r--r-- | gxp-dma-iommu.h | 20 | ||||
-rw-r--r-- | gxp-dma-rmem.c | 576 | ||||
-rw-r--r-- | gxp-dma.h | 264 | ||||
-rw-r--r-- | gxp-doorbell.c | 57 | ||||
-rw-r--r-- | gxp-doorbell.h | 18 | ||||
-rw-r--r-- | gxp-firmware-data.c | 710 | ||||
-rw-r--r-- | gxp-firmware-data.h | 56 | ||||
-rw-r--r-- | gxp-firmware.c | 454 | ||||
-rw-r--r-- | gxp-firmware.h | 40 | ||||
-rw-r--r-- | gxp-host-device-structs.h | 262 | ||||
-rw-r--r-- | gxp-hw-mailbox-driver.c | 247 | ||||
-rw-r--r-- | gxp-internal.h | 186 | ||||
-rw-r--r-- | gxp-iova.h | 19 | ||||
-rw-r--r-- | gxp-lpm.c | 267 | ||||
-rw-r--r-- | gxp-lpm.h | 106 | ||||
-rw-r--r-- | gxp-mailbox-driver.h | 48 | ||||
-rw-r--r-- | gxp-mailbox-regs.h | 53 | ||||
-rw-r--r-- | gxp-mailbox.c | 811 | ||||
-rw-r--r-- | gxp-mailbox.h | 181 | ||||
-rw-r--r-- | gxp-mapping.c | 344 | ||||
-rw-r--r-- | gxp-mapping.h | 55 | ||||
-rw-r--r-- | gxp-platform.c | 700 | ||||
-rw-r--r-- | gxp-range-alloc.c | 118 | ||||
-rw-r--r-- | gxp-range-alloc.h | 94 | ||||
-rw-r--r-- | gxp-sw-mailbox-driver.c | 484 | ||||
-rw-r--r-- | gxp-tmp.h | 60 | ||||
-rw-r--r-- | gxp-vd.c | 212 | ||||
-rw-r--r-- | gxp-vd.h | 57 | ||||
-rw-r--r-- | gxp.h | 220 | ||||
-rw-r--r-- | mm-backport.h | 33 |
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/ @@ -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__ */ @@ -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__ */ |