109e61dafSJames Morse // SPDX-License-Identifier: GPL-2.0 209e61dafSJames Morse // Copyright (C) 2025 Arm Ltd. 309e61dafSJames Morse 409e61dafSJames Morse #define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ 509e61dafSJames Morse 609e61dafSJames Morse #include <linux/arm_mpam.h> 709e61dafSJames Morse #include <linux/cacheinfo.h> 809e61dafSJames Morse #include <linux/cpu.h> 909e61dafSJames Morse #include <linux/cpumask.h> 1009e61dafSJames Morse #include <linux/errno.h> 1109e61dafSJames Morse #include <linux/list.h> 1209e61dafSJames Morse #include <linux/printk.h> 1309e61dafSJames Morse #include <linux/rculist.h> 1409e61dafSJames Morse #include <linux/resctrl.h> 1509e61dafSJames Morse #include <linux/slab.h> 1609e61dafSJames Morse #include <linux/types.h> 1709e61dafSJames Morse 1809e61dafSJames Morse #include <asm/mpam.h> 1909e61dafSJames Morse 2009e61dafSJames Morse #include "mpam_internal.h" 2109e61dafSJames Morse 2209e61dafSJames Morse /* 2309e61dafSJames Morse * The classes we've picked to map to resctrl resources, wrapped 2409e61dafSJames Morse * in with their resctrl structure. 2509e61dafSJames Morse * Class pointer may be NULL. 2609e61dafSJames Morse */ 2709e61dafSJames Morse static struct mpam_resctrl_res mpam_resctrl_controls[RDT_NUM_RESOURCES]; 2809e61dafSJames Morse 2909e61dafSJames Morse #define for_each_mpam_resctrl_control(res, rid) \ 3009e61dafSJames Morse for (rid = 0, res = &mpam_resctrl_controls[rid]; \ 3109e61dafSJames Morse rid < RDT_NUM_RESOURCES; \ 3209e61dafSJames Morse rid++, res = &mpam_resctrl_controls[rid]) 3309e61dafSJames Morse 3409e61dafSJames Morse /* The lock for modifying resctrl's domain lists from cpuhp callbacks. */ 3509e61dafSJames Morse static DEFINE_MUTEX(domain_list_lock); 3609e61dafSJames Morse 3709e61dafSJames Morse bool resctrl_arch_alloc_capable(void) 3809e61dafSJames Morse { 3909e61dafSJames Morse struct mpam_resctrl_res *res; 4009e61dafSJames Morse enum resctrl_res_level rid; 4109e61dafSJames Morse 4209e61dafSJames Morse for_each_mpam_resctrl_control(res, rid) { 4309e61dafSJames Morse if (res->resctrl_res.alloc_capable) 4409e61dafSJames Morse return true; 4509e61dafSJames Morse } 4609e61dafSJames Morse 4709e61dafSJames Morse return false; 4809e61dafSJames Morse } 4909e61dafSJames Morse 5009e61dafSJames Morse /* 5109e61dafSJames Morse * MSC may raise an error interrupt if it sees an out or range partid/pmg, 5209e61dafSJames Morse * and go on to truncate the value. Regardless of what the hardware supports, 5309e61dafSJames Morse * only the system wide safe value is safe to use. 5409e61dafSJames Morse */ 5509e61dafSJames Morse u32 resctrl_arch_get_num_closid(struct rdt_resource *ignored) 5609e61dafSJames Morse { 5709e61dafSJames Morse return mpam_partid_max + 1; 5809e61dafSJames Morse } 5909e61dafSJames Morse 6009e61dafSJames Morse struct rdt_resource *resctrl_arch_get_resource(enum resctrl_res_level l) 6109e61dafSJames Morse { 6209e61dafSJames Morse if (l >= RDT_NUM_RESOURCES) 6309e61dafSJames Morse return NULL; 6409e61dafSJames Morse 6509e61dafSJames Morse return &mpam_resctrl_controls[l].resctrl_res; 6609e61dafSJames Morse } 6709e61dafSJames Morse 68*52a4edb1SJames Morse static bool cache_has_usable_cpor(struct mpam_class *class) 69*52a4edb1SJames Morse { 70*52a4edb1SJames Morse struct mpam_props *cprops = &class->props; 71*52a4edb1SJames Morse 72*52a4edb1SJames Morse if (!mpam_has_feature(mpam_feat_cpor_part, cprops)) 73*52a4edb1SJames Morse return false; 74*52a4edb1SJames Morse 75*52a4edb1SJames Morse /* resctrl uses u32 for all bitmap configurations */ 76*52a4edb1SJames Morse return class->props.cpbm_wd <= 32; 77*52a4edb1SJames Morse } 78*52a4edb1SJames Morse 79*52a4edb1SJames Morse /* Test whether we can export MPAM_CLASS_CACHE:{2,3}? */ 80*52a4edb1SJames Morse static void mpam_resctrl_pick_caches(void) 81*52a4edb1SJames Morse { 82*52a4edb1SJames Morse struct mpam_class *class; 83*52a4edb1SJames Morse struct mpam_resctrl_res *res; 84*52a4edb1SJames Morse 85*52a4edb1SJames Morse lockdep_assert_cpus_held(); 86*52a4edb1SJames Morse 87*52a4edb1SJames Morse guard(srcu)(&mpam_srcu); 88*52a4edb1SJames Morse list_for_each_entry_srcu(class, &mpam_classes, classes_list, 89*52a4edb1SJames Morse srcu_read_lock_held(&mpam_srcu)) { 90*52a4edb1SJames Morse if (class->type != MPAM_CLASS_CACHE) { 91*52a4edb1SJames Morse pr_debug("class %u is not a cache\n", class->level); 92*52a4edb1SJames Morse continue; 93*52a4edb1SJames Morse } 94*52a4edb1SJames Morse 95*52a4edb1SJames Morse if (class->level != 2 && class->level != 3) { 96*52a4edb1SJames Morse pr_debug("class %u is not L2 or L3\n", class->level); 97*52a4edb1SJames Morse continue; 98*52a4edb1SJames Morse } 99*52a4edb1SJames Morse 100*52a4edb1SJames Morse if (!cache_has_usable_cpor(class)) { 101*52a4edb1SJames Morse pr_debug("class %u cache misses CPOR\n", class->level); 102*52a4edb1SJames Morse continue; 103*52a4edb1SJames Morse } 104*52a4edb1SJames Morse 105*52a4edb1SJames Morse if (!cpumask_equal(&class->affinity, cpu_possible_mask)) { 106*52a4edb1SJames Morse pr_debug("class %u has missing CPUs, mask %*pb != %*pb\n", class->level, 107*52a4edb1SJames Morse cpumask_pr_args(&class->affinity), 108*52a4edb1SJames Morse cpumask_pr_args(cpu_possible_mask)); 109*52a4edb1SJames Morse continue; 110*52a4edb1SJames Morse } 111*52a4edb1SJames Morse 112*52a4edb1SJames Morse if (class->level == 2) 113*52a4edb1SJames Morse res = &mpam_resctrl_controls[RDT_RESOURCE_L2]; 114*52a4edb1SJames Morse else 115*52a4edb1SJames Morse res = &mpam_resctrl_controls[RDT_RESOURCE_L3]; 116*52a4edb1SJames Morse res->class = class; 117*52a4edb1SJames Morse } 118*52a4edb1SJames Morse } 119*52a4edb1SJames Morse 12009e61dafSJames Morse static int mpam_resctrl_control_init(struct mpam_resctrl_res *res) 12109e61dafSJames Morse { 122*52a4edb1SJames Morse struct mpam_class *class = res->class; 123*52a4edb1SJames Morse struct rdt_resource *r = &res->resctrl_res; 124*52a4edb1SJames Morse 125*52a4edb1SJames Morse switch (r->rid) { 126*52a4edb1SJames Morse case RDT_RESOURCE_L2: 127*52a4edb1SJames Morse case RDT_RESOURCE_L3: 128*52a4edb1SJames Morse r->schema_fmt = RESCTRL_SCHEMA_BITMAP; 129*52a4edb1SJames Morse r->cache.arch_has_sparse_bitmasks = true; 130*52a4edb1SJames Morse 131*52a4edb1SJames Morse r->cache.cbm_len = class->props.cpbm_wd; 132*52a4edb1SJames Morse /* mpam_devices will reject empty bitmaps */ 133*52a4edb1SJames Morse r->cache.min_cbm_bits = 1; 134*52a4edb1SJames Morse 135*52a4edb1SJames Morse if (r->rid == RDT_RESOURCE_L2) { 136*52a4edb1SJames Morse r->name = "L2"; 137*52a4edb1SJames Morse r->ctrl_scope = RESCTRL_L2_CACHE; 138*52a4edb1SJames Morse r->cdp_capable = true; 139*52a4edb1SJames Morse } else { 140*52a4edb1SJames Morse r->name = "L3"; 141*52a4edb1SJames Morse r->ctrl_scope = RESCTRL_L3_CACHE; 142*52a4edb1SJames Morse r->cdp_capable = true; 143*52a4edb1SJames Morse } 144*52a4edb1SJames Morse 145*52a4edb1SJames Morse /* 146*52a4edb1SJames Morse * Which bits are shared with other ...things... Unknown 147*52a4edb1SJames Morse * devices use partid-0 which uses all the bitmap fields. Until 148*52a4edb1SJames Morse * we have configured the SMMU and GIC not to do this 'all the 149*52a4edb1SJames Morse * bits' is the correct answer here. 150*52a4edb1SJames Morse */ 151*52a4edb1SJames Morse r->cache.shareable_bits = resctrl_get_default_ctrl(r); 152*52a4edb1SJames Morse r->alloc_capable = true; 153*52a4edb1SJames Morse break; 154*52a4edb1SJames Morse default: 155*52a4edb1SJames Morse return -EINVAL; 156*52a4edb1SJames Morse } 15709e61dafSJames Morse 15809e61dafSJames Morse return 0; 15909e61dafSJames Morse } 16009e61dafSJames Morse 16109e61dafSJames Morse static int mpam_resctrl_pick_domain_id(int cpu, struct mpam_component *comp) 16209e61dafSJames Morse { 16309e61dafSJames Morse struct mpam_class *class = comp->class; 16409e61dafSJames Morse 16509e61dafSJames Morse if (class->type == MPAM_CLASS_CACHE) 16609e61dafSJames Morse return comp->comp_id; 16709e61dafSJames Morse 16809e61dafSJames Morse /* TODO: repaint domain ids to match the L3 domain ids */ 16909e61dafSJames Morse /* Otherwise, expose the ID used by the firmware table code. */ 17009e61dafSJames Morse return comp->comp_id; 17109e61dafSJames Morse } 17209e61dafSJames Morse 17309e61dafSJames Morse static void mpam_resctrl_domain_hdr_init(int cpu, struct mpam_component *comp, 17409e61dafSJames Morse enum resctrl_res_level rid, 17509e61dafSJames Morse struct rdt_domain_hdr *hdr) 17609e61dafSJames Morse { 17709e61dafSJames Morse lockdep_assert_cpus_held(); 17809e61dafSJames Morse 17909e61dafSJames Morse INIT_LIST_HEAD(&hdr->list); 18009e61dafSJames Morse hdr->id = mpam_resctrl_pick_domain_id(cpu, comp); 18109e61dafSJames Morse hdr->rid = rid; 18209e61dafSJames Morse cpumask_set_cpu(cpu, &hdr->cpu_mask); 18309e61dafSJames Morse } 18409e61dafSJames Morse 18509e61dafSJames Morse static void mpam_resctrl_online_domain_hdr(unsigned int cpu, 18609e61dafSJames Morse struct rdt_domain_hdr *hdr) 18709e61dafSJames Morse { 18809e61dafSJames Morse lockdep_assert_cpus_held(); 18909e61dafSJames Morse 19009e61dafSJames Morse cpumask_set_cpu(cpu, &hdr->cpu_mask); 19109e61dafSJames Morse } 19209e61dafSJames Morse 19309e61dafSJames Morse /** 19409e61dafSJames Morse * mpam_resctrl_offline_domain_hdr() - Update the domain header to remove a CPU. 19509e61dafSJames Morse * @cpu: The CPU to remove from the domain. 19609e61dafSJames Morse * @hdr: The domain's header. 19709e61dafSJames Morse * 19809e61dafSJames Morse * Removes @cpu from the header mask. If this was the last CPU in the domain, 19909e61dafSJames Morse * the domain header is removed from its parent list and true is returned, 20009e61dafSJames Morse * indicating the parent structure can be freed. 20109e61dafSJames Morse * If there are other CPUs in the domain, returns false. 20209e61dafSJames Morse */ 20309e61dafSJames Morse static bool mpam_resctrl_offline_domain_hdr(unsigned int cpu, 20409e61dafSJames Morse struct rdt_domain_hdr *hdr) 20509e61dafSJames Morse { 20609e61dafSJames Morse lockdep_assert_held(&domain_list_lock); 20709e61dafSJames Morse 20809e61dafSJames Morse cpumask_clear_cpu(cpu, &hdr->cpu_mask); 20909e61dafSJames Morse if (cpumask_empty(&hdr->cpu_mask)) { 21009e61dafSJames Morse list_del_rcu(&hdr->list); 21109e61dafSJames Morse synchronize_rcu(); 21209e61dafSJames Morse return true; 21309e61dafSJames Morse } 21409e61dafSJames Morse 21509e61dafSJames Morse return false; 21609e61dafSJames Morse } 21709e61dafSJames Morse 21809e61dafSJames Morse static void mpam_resctrl_domain_insert(struct list_head *list, 21909e61dafSJames Morse struct rdt_domain_hdr *new) 22009e61dafSJames Morse { 22109e61dafSJames Morse struct rdt_domain_hdr *err; 22209e61dafSJames Morse struct list_head *pos = NULL; 22309e61dafSJames Morse 22409e61dafSJames Morse lockdep_assert_held(&domain_list_lock); 22509e61dafSJames Morse 22609e61dafSJames Morse err = resctrl_find_domain(list, new->id, &pos); 22709e61dafSJames Morse if (WARN_ON_ONCE(err)) 22809e61dafSJames Morse return; 22909e61dafSJames Morse 23009e61dafSJames Morse list_add_tail_rcu(&new->list, pos); 23109e61dafSJames Morse } 23209e61dafSJames Morse 23309e61dafSJames Morse static struct mpam_resctrl_dom * 23409e61dafSJames Morse mpam_resctrl_alloc_domain(unsigned int cpu, struct mpam_resctrl_res *res) 23509e61dafSJames Morse { 23609e61dafSJames Morse int err; 23709e61dafSJames Morse struct mpam_resctrl_dom *dom; 23809e61dafSJames Morse struct rdt_ctrl_domain *ctrl_d; 23909e61dafSJames Morse struct mpam_class *class = res->class; 24009e61dafSJames Morse struct mpam_component *comp_iter, *ctrl_comp; 24109e61dafSJames Morse struct rdt_resource *r = &res->resctrl_res; 24209e61dafSJames Morse 24309e61dafSJames Morse lockdep_assert_held(&domain_list_lock); 24409e61dafSJames Morse 24509e61dafSJames Morse ctrl_comp = NULL; 24609e61dafSJames Morse guard(srcu)(&mpam_srcu); 24709e61dafSJames Morse list_for_each_entry_srcu(comp_iter, &class->components, class_list, 24809e61dafSJames Morse srcu_read_lock_held(&mpam_srcu)) { 24909e61dafSJames Morse if (cpumask_test_cpu(cpu, &comp_iter->affinity)) { 25009e61dafSJames Morse ctrl_comp = comp_iter; 25109e61dafSJames Morse break; 25209e61dafSJames Morse } 25309e61dafSJames Morse } 25409e61dafSJames Morse 25509e61dafSJames Morse /* class has no component for this CPU */ 25609e61dafSJames Morse if (WARN_ON_ONCE(!ctrl_comp)) 25709e61dafSJames Morse return ERR_PTR(-EINVAL); 25809e61dafSJames Morse 25909e61dafSJames Morse dom = kzalloc_node(sizeof(*dom), GFP_KERNEL, cpu_to_node(cpu)); 26009e61dafSJames Morse if (!dom) 26109e61dafSJames Morse return ERR_PTR(-ENOMEM); 26209e61dafSJames Morse 26309e61dafSJames Morse if (r->alloc_capable) { 26409e61dafSJames Morse dom->ctrl_comp = ctrl_comp; 26509e61dafSJames Morse 26609e61dafSJames Morse ctrl_d = &dom->resctrl_ctrl_dom; 26709e61dafSJames Morse mpam_resctrl_domain_hdr_init(cpu, ctrl_comp, r->rid, &ctrl_d->hdr); 26809e61dafSJames Morse ctrl_d->hdr.type = RESCTRL_CTRL_DOMAIN; 26909e61dafSJames Morse err = resctrl_online_ctrl_domain(r, ctrl_d); 27009e61dafSJames Morse if (err) 27109e61dafSJames Morse goto free_domain; 27209e61dafSJames Morse 27309e61dafSJames Morse mpam_resctrl_domain_insert(&r->ctrl_domains, &ctrl_d->hdr); 27409e61dafSJames Morse } else { 27509e61dafSJames Morse pr_debug("Skipped control domain online - no controls\n"); 27609e61dafSJames Morse } 27709e61dafSJames Morse return dom; 27809e61dafSJames Morse 27909e61dafSJames Morse free_domain: 28009e61dafSJames Morse kfree(dom); 28109e61dafSJames Morse dom = ERR_PTR(err); 28209e61dafSJames Morse 28309e61dafSJames Morse return dom; 28409e61dafSJames Morse } 28509e61dafSJames Morse 28609e61dafSJames Morse static struct mpam_resctrl_dom * 28709e61dafSJames Morse mpam_resctrl_get_domain_from_cpu(int cpu, struct mpam_resctrl_res *res) 28809e61dafSJames Morse { 28909e61dafSJames Morse struct mpam_resctrl_dom *dom; 29009e61dafSJames Morse struct rdt_resource *r = &res->resctrl_res; 29109e61dafSJames Morse 29209e61dafSJames Morse lockdep_assert_cpus_held(); 29309e61dafSJames Morse 29409e61dafSJames Morse list_for_each_entry_rcu(dom, &r->ctrl_domains, resctrl_ctrl_dom.hdr.list) { 29509e61dafSJames Morse if (cpumask_test_cpu(cpu, &dom->ctrl_comp->affinity)) 29609e61dafSJames Morse return dom; 29709e61dafSJames Morse } 29809e61dafSJames Morse 29909e61dafSJames Morse return NULL; 30009e61dafSJames Morse } 30109e61dafSJames Morse 30209e61dafSJames Morse int mpam_resctrl_online_cpu(unsigned int cpu) 30309e61dafSJames Morse { 30409e61dafSJames Morse struct mpam_resctrl_res *res; 30509e61dafSJames Morse enum resctrl_res_level rid; 30609e61dafSJames Morse 30709e61dafSJames Morse guard(mutex)(&domain_list_lock); 30809e61dafSJames Morse for_each_mpam_resctrl_control(res, rid) { 30909e61dafSJames Morse struct mpam_resctrl_dom *dom; 31009e61dafSJames Morse struct rdt_resource *r = &res->resctrl_res; 31109e61dafSJames Morse 31209e61dafSJames Morse if (!res->class) 31309e61dafSJames Morse continue; // dummy_resource; 31409e61dafSJames Morse 31509e61dafSJames Morse dom = mpam_resctrl_get_domain_from_cpu(cpu, res); 31609e61dafSJames Morse if (!dom) { 31709e61dafSJames Morse dom = mpam_resctrl_alloc_domain(cpu, res); 31809e61dafSJames Morse if (IS_ERR(dom)) 31909e61dafSJames Morse return PTR_ERR(dom); 32009e61dafSJames Morse } else { 32109e61dafSJames Morse if (r->alloc_capable) { 32209e61dafSJames Morse struct rdt_ctrl_domain *ctrl_d = &dom->resctrl_ctrl_dom; 32309e61dafSJames Morse 32409e61dafSJames Morse mpam_resctrl_online_domain_hdr(cpu, &ctrl_d->hdr); 32509e61dafSJames Morse } 32609e61dafSJames Morse } 32709e61dafSJames Morse } 32809e61dafSJames Morse 32909e61dafSJames Morse resctrl_online_cpu(cpu); 33009e61dafSJames Morse 33109e61dafSJames Morse return 0; 33209e61dafSJames Morse } 33309e61dafSJames Morse 33409e61dafSJames Morse void mpam_resctrl_offline_cpu(unsigned int cpu) 33509e61dafSJames Morse { 33609e61dafSJames Morse struct mpam_resctrl_res *res; 33709e61dafSJames Morse enum resctrl_res_level rid; 33809e61dafSJames Morse 33909e61dafSJames Morse resctrl_offline_cpu(cpu); 34009e61dafSJames Morse 34109e61dafSJames Morse guard(mutex)(&domain_list_lock); 34209e61dafSJames Morse for_each_mpam_resctrl_control(res, rid) { 34309e61dafSJames Morse struct mpam_resctrl_dom *dom; 34409e61dafSJames Morse struct rdt_ctrl_domain *ctrl_d; 34509e61dafSJames Morse bool ctrl_dom_empty; 34609e61dafSJames Morse struct rdt_resource *r = &res->resctrl_res; 34709e61dafSJames Morse 34809e61dafSJames Morse if (!res->class) 34909e61dafSJames Morse continue; // dummy resource 35009e61dafSJames Morse 35109e61dafSJames Morse dom = mpam_resctrl_get_domain_from_cpu(cpu, res); 35209e61dafSJames Morse if (WARN_ON_ONCE(!dom)) 35309e61dafSJames Morse continue; 35409e61dafSJames Morse 35509e61dafSJames Morse if (r->alloc_capable) { 35609e61dafSJames Morse ctrl_d = &dom->resctrl_ctrl_dom; 35709e61dafSJames Morse ctrl_dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &ctrl_d->hdr); 35809e61dafSJames Morse if (ctrl_dom_empty) 35909e61dafSJames Morse resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl_d); 36009e61dafSJames Morse } else { 36109e61dafSJames Morse ctrl_dom_empty = true; 36209e61dafSJames Morse } 36309e61dafSJames Morse 36409e61dafSJames Morse if (ctrl_dom_empty) 36509e61dafSJames Morse kfree(dom); 36609e61dafSJames Morse } 36709e61dafSJames Morse } 36809e61dafSJames Morse 36909e61dafSJames Morse int mpam_resctrl_setup(void) 37009e61dafSJames Morse { 37109e61dafSJames Morse int err = 0; 37209e61dafSJames Morse struct mpam_resctrl_res *res; 37309e61dafSJames Morse enum resctrl_res_level rid; 37409e61dafSJames Morse 37509e61dafSJames Morse cpus_read_lock(); 37609e61dafSJames Morse for_each_mpam_resctrl_control(res, rid) { 37709e61dafSJames Morse INIT_LIST_HEAD_RCU(&res->resctrl_res.ctrl_domains); 37809e61dafSJames Morse res->resctrl_res.rid = rid; 37909e61dafSJames Morse } 38009e61dafSJames Morse 381*52a4edb1SJames Morse /* Find some classes to use for controls */ 382*52a4edb1SJames Morse mpam_resctrl_pick_caches(); 38309e61dafSJames Morse 38409e61dafSJames Morse /* Initialise the resctrl structures from the classes */ 38509e61dafSJames Morse for_each_mpam_resctrl_control(res, rid) { 38609e61dafSJames Morse if (!res->class) 38709e61dafSJames Morse continue; // dummy resource 38809e61dafSJames Morse 38909e61dafSJames Morse err = mpam_resctrl_control_init(res); 39009e61dafSJames Morse if (err) { 39109e61dafSJames Morse pr_debug("Failed to initialise rid %u\n", rid); 39209e61dafSJames Morse break; 39309e61dafSJames Morse } 39409e61dafSJames Morse } 39509e61dafSJames Morse cpus_read_unlock(); 39609e61dafSJames Morse 39709e61dafSJames Morse if (err) { 39809e61dafSJames Morse pr_debug("Internal error %d - resctrl not supported\n", err); 39909e61dafSJames Morse return err; 40009e61dafSJames Morse } 40109e61dafSJames Morse 40209e61dafSJames Morse if (!resctrl_arch_alloc_capable()) { 40309e61dafSJames Morse pr_debug("No alloc(%u) found - resctrl not supported\n", 40409e61dafSJames Morse resctrl_arch_alloc_capable()); 40509e61dafSJames Morse return -EOPNOTSUPP; 40609e61dafSJames Morse } 40709e61dafSJames Morse 40809e61dafSJames Morse /* TODO: call resctrl_init() */ 40909e61dafSJames Morse 41009e61dafSJames Morse return 0; 41109e61dafSJames Morse } 412